Informal interfaces and abstract types

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.

3 Likes

I saw that a similar question has already been asked here: Why use subtypes instead of traits and duck typing?