Question

I am applying the principles of Hexagonal Architecture (Ports and Adapters) and one aspect is slightly bothering me. In my opinion, the ports and adapters of the secondary actors should completely be isolated from each other. No dependencies between them. Maybe this is a false expectation but it instinctively feels right to me.

I am having a hard time enforcing this separation. A very simple example is the following use case (don't worry about the details):

  • There is Logging secondary port that is responsible for creating/handling the logs of the application. The adapters are not that important but let's just assume that there is StandardOutputLogging adapter for now; it just sends the logs to standard output.

  • There is also another port named EmailSending which is responsible for sending e-mails. Again, the adapters don't matter too much.

  • The issue happens when I want to log what happens in the e-mail sending functionality. The EmailSending adapters need a handle on the Logging port. This does create a dependency between the two secondary ports of the application.

  • It is also an issue during the wiring of the system as well. Logging adapter should be created first and then passed to the EmailSending adapter. That also creates an implicit dependency.

How do we solve this?

Was it helpful?

Solution

Let's keep this pragmatic and simple:

  • The hexagonal architecture was laid down by Alistair Cockburn in a 2005 article. A single article (and a couple of talks) that let a lot of aspects unaddressed. Since then, it inspired a lot of people, such as Jeffrey Palermo with his Onion Architecture in 2008, Uncle Bob with his Clean Architecture in 2012, and more recently, authors in in the microservice area.
  • The intent of the architecture is to isolate the application from its surroundings, using ports, and adapters to plug the outside world (or test mocks). The key to success is therefore a clear API for the adapters.
  • Since Alisatir is also a big promoter of Use Cases, he described this architecture to fit in use case driven approaches. But the actors are just a special case for ports and adapters. They should not distract you from what you're doing.

So, let's not make this simple architecture more complex than it is:

  • One of the nicest recent article describing more concretely this architecture is here
  • An adapter MUST clearly NOT depend on another adapter. Because such a dependency would impose an unjustifiable coupling in the surrounding of the application. If email adapter would depend on the logging adapter, you could no longer freely exchange the logging adapter :-/
  • As you said, ideally, a port should not be coupled to another port either. So the email port should foresee something for the logging. The application constructor should make then plug the pieces.
  • But in the special case of the logging (or any other monitoring feature), the application does really not add value in the process. And the logging needs of the email adapter would probably be very close to the consumer side of the loging port. So let's keep it simple: allow for this dependency to exist. It will avoid you unnecessary overhead, it would be transparent about the real dependencies, and most over:
  • the port is an abstraction. You would still be in a case where an abstraction dependent on an abstraction. If it's justified, it's not a bad thing (plenty of patterns are constructed on abstract dependencies).
Licensed under: CC-BY-SA with attribution
scroll top