I have a question regarding best practices for the design of informal interfaces.
Let’s say that I want to implement a service that continuously reads in data from a source, processes the data, and publishes the result somewhere. The service should be configurable, such that it can read in data from different sources (e.g., filesystem, database, Kafka topic, etc.). The same goes for publishing the output.
Initially I would have implemented this with abstract types along these lines (and how it is described in this tutorial: Getting Started With Julia : Interfaces | packtpub.com - YouTube):
# A reader reads in data from some input # # Required methods: # # read_data(reader) - provide the next data object of the reader abstract type Reader end # Provide the next data object function read_data end struct FileSystemReader <: Reader path_to_files::String ... end struct KafkaReader <: Reader address::String ... end
The publisher interface would be implemented analogously. Finally, I would have kept the reader and the publisher of a service in another type:
struct Service reader::Reader publisher::Publisher ... end
reader and the
publisher attributes would rarely get accessed, I am not worried about performance and thus using abstract types for those fields is fine.
However, at this point I am wondering what the actual benefit of having the abstract types
Publisher would be. Actually, I think it would only be a restriction. For example, if I want to have a type that implements both interfaces, this type could only be a subtype of either
Therefore, I am currently leaning towards this implementation without using abstract types:
# A reader reads in data from some input # # Required methods: # # read_data(reader) - provide the next data object of the reader # Provide the next data object function read_data end struct FileSystemReader # implements the reader interface path_to_files::String ... end function read_data(reader::FileSystemReader) ... end struct KafkaClient # implements the reader interface and the publisher interface address::String ... end function read_data(client::KafkaClient) ... end function publish(client::KafkaClient) ... end ... struct Service reader # must implement the reader interface publisher # must implement the publisher interface ... end
It looks a bit odd to not have a type here associated with the attributes of
Service, but again, I am not worried about performance here. Of course, I could also parameterize the
The only benefit of having abstract types that I can think of is dynamic dispatch when there was some sort of hierarchy associated with readers and publisher (which I don’t expect to have in my service). For example, maybe I want to treat “remote” and “local” readers differently.
abstract type Reader end abstract type RemoteReader <: Reader end # supertype of KafkaReader abstract type LocalReader <: Reader end # supertype of FileSystemReader
However, I could also achieve this differentiation using Traits without having the issue that a type cannot be a reader and a publisher at the same time.
My question now is: Am I missing something here? Are there additional benefits of using abstract types when defining informal interfaces that I am currently not seeing?
Thanks a lot in advance for your feedback.