Hi everyone,
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
Since the 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 Reader
and 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 Reader
or Publisher
.
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 Service
type.
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.