Week #5 — Ports and Adapters

Another week has come to a pass and unfortunately, I did not manage to work on a project at all — again. But I have some ideas that may push the progress a bit forward. As I have been on conference week ago I heard some new buzzwords, saw few interesting technologies and architecture patterns so it would be good to test them out. I’m thinking about using Consumer-Driven Contracts for designing REST API for Commutee, but it is still far from that stage of the project. For now, let’s try Hexagonal architecture! The ports and adapters approach. I will probably overengineer it, but hell, better here than in production :)

First things first, that is, what is this all about? I’ve written a bit about this approach in my last post 4Developers 2017 — summary. But I’ll write about it again.

In this approach, the most important part of the system is its core, a domain containing all the business logic. But that’s not something unique, we may say, that in the layered architecture, a domain also has all this logic separated in some kind of core, for example, we have one layer with all this, and other layers are above and below. And here is a big difference. Layers below — that means, layers on wich our domain “layer” depends on. In Hexagonal architecture, our domain does not depend on any other part of the system. Domain specifies what it needs and in what form. Other parts of the system must conform to the requirements.

The way to implement this architecture is pretty simple, we just create interfaces for our ports — things that domain need from other parts of the system or the entry points to our domain. Implementation of said interfaces — adapters, is provided by outside components that provide specific functionality, entry points, database access etc. As you see, it is not much different from the layered approach, but the key difference here is that these interfaces are specified by and reside within the domain. As a result of structure like this, the domain has no dependencies and this means that we can easily test it in isolation. We can mock all the interfaces any way we want, without the need to create complex mocks that simulate whole layers.

Loading the timetables

So the theory is simple, now, how to make it work? I don’t know. I’m writing this live as I am thinking about this :)

First, let’s think about entry points. There will be at least two. First one will be for loading the data from outside source. I want to keep obtaining and parsing timetable data outside of the domain because that is a secondary concern. It does not matter how I will get this data, how I will parse it etc. All that is important for me is to get it in a format that my domain understands.

So let’s call this interface a TimetableDataLoader. Names are always hard, but this sounds good and it describes the functionality pretty well. Now let’s move to the methods. Here I have a bit of a problem because I see two different solutions.

First one is to have one method, that will have a single argument, an object with all the timetable data, all the connections etc. This may be a good solution because I could thoroughly analyze all the data on the domain side and decide how I will import all of this. At the same time, I don’t like this solution because of this exact reason. Domain will become much more complicated.

public interface TimetableDataLoader {
void updateData(TimetableData data);
}

The second option is to have separate methods for updating different data. So we will have few methods:

  • update a bus stop description, its coordinates etc.
  • update a line connection between bus stops, with departure times
  • delete a bus stop
  • delete a connection between bus stops

I think that is all, these four methods. The good part of this option is that it is very easy to test. The bad part is that it requires a bit more effort to delete old data that is not available in new timetable. I should probably also add methods for querying bus stops and connections so that provider could find what has to be deleted and delete it. I don’t like it. I could also just create one method that deletes all old data as when the new timetable is available it has all the actual data from the time it is published and I do not need timetables that are no longer valid.

Another thought that just occurred to me. At the start of the data loading, I could archive all the old data by tagging it somehow as not valid anymore. And during updating of each object, I could mark it as valid again. That way I would be able to easily delete all the not valid data later without the need for the provider to decide what is deleted and what is not.

public interface TimetableDataLoader {
void updateBusStop(String id, BusStopData busStop);

void updateConnection(String from, String to, ConnectionData data);

void start();

void finish();
}

So let’s assume that first, the start method has to be called, then all the data has to be updated, and at the end, the finish method. Like a transaction. That way we could decide later if we want to delete all the data at the start or just mark it invalid.

Finding connections

Now the most important part. The core functionality of the system! The route finding. Well, this part is really complicated, but on the entry point level, the interface is really simple. It will have only one method that just finds a route and few to view timetable data.

public interface Timetables {
Route findRoute(QueryParameters query);

Timetable findTimetable(String busStopId, String lineId);

Route findLineRoute(String lineId);
}

As you see, it is really simple. But that is really good! As the API to the domain should be as simple as possible.

For now, that is all when it comes to the entry point ports, next time I plan to write about outgoing ports from the domain, that means connecting to the database or databases — I have not decided yet how this part will be done, we will see :)

If you want to read more about Hexagonal architecture head to Alistair Cockburn’s great article about this topic.


Originally published at DEVelopments.