The Ports and Adapters Pattern

Most software solutions today are part of an active ecosystem where calling external services is an integrated part of its business processes. However, external integrations come with all kinds of headaches, from keeping your code clean, to executing automated tests within a reasonable timeframe, to the tight integration of external services into your code which makes switching vendors hard, if not impossible.

The ports and adapters pattern (also known as hexagonal architecture) is an effective way of addressing these problems. The problem for existing applications is that re-architecting them may not be economically feasible. So a minimalist approach is to use ports and adapters when you call external services, which you do not control, but not necessarily when using external libraries or products, which you do control.

The basic concept behind ports and adapters is very simple: You define a port that can be used with various adapters as long as the adapters conform to the port’s interface. An example from the physical world is the HDMI port in your laptop, which you can connect to any monitor as long as the monitor obeys the HDMI standard.

Moving back to software, then a simplified example of using ports and adapters is shown in the Java-based newsreader below:

The core package contains the core logic of the newsreader. I know that the feedreader will need to connect to various newsfeeds, but I don’t want to pollute my core code with all kind of implementation details, such as storing Twitter client secrets, obeying rate limiting in the API calls, dealing with broken XML in RSS feeds, so I define the Feed interface that defines the methods that I need a feed to offer.

The Feed interface can be something really simple like the code snippet below:

public interface Feed {
    List<Post> getPosts();
}

All the implementation details, I keep in the infrastructure package where I keep the implementations of different news feeds, such as RSS and Twitter. To avoid polluting the core code with implementation details, the key is to use inversion of control, so the core package has no dependencies on the infrastructure package.

When your application needs to connect the core code with the infrastructure code, you can use dependency injection in your application:

// Initialize the feeds from the infrastructure package
List<Feed> feeds = new ArrayList<>();
feeds.add(new RssFeed("http://feeds.feedburner.com/KennethLange"));
feeds.add(new TwitterFeed("@KennethLange"));

// Inject the feeds into the FeedReader
FeedReader reader = new FeedReader(feeds);
for(Post post : reader.getRecentPosts()) {
    System.out.println(post.getTitle());
}

Beside the benefit of keeping the concerns of core and infrastructure code separate, you can add new feeds, such as Atom or Google News, without touching the core code. If Twitter shutdowns their APIs then you do not end up with dead Twitter code scattered throughout your application, and you can provide other feeds that provide similar content.

Another benefit is that your unit tests of the core code become much more reliable as you can easily make a fake (or stub) implementation of the Feed interface for testing purposes. This both means that you no longer need to rely on unreliable infrastructure in your tests and the unit tests will most likely become a lot faster to execute, which will also benefit your continuous integration.

Beyond Java
The basic idea of ports and adapters is that you define an interface and can collaborate with anyone who implements the interface. The concept is not limited to Java or other languages that offer an interface type. You can even define the interface as a service contract, perhaps in Swagger, and can then collaborate with any third-party service that implements the contract. This is similar to how webhooks work and would result in a very loose coupling where you would not even need to redeploy your code to support a new service. The difficulty would obviously be to get third parties to support your service contract.

When you call external services as part of your business processes, it is usually a good idea to stick in circuit breaker to avoid instability from a third-party service to spread to your system, so if the third-party service goes down, then your system doesn’t go down with it. The circuit breaker pattern will be the topic of my next blog post to improve improve the way we use external services in our code.