5

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?

9
  • 2
    Are you actually having trouble with how Logging fits into your architecture or are you using it as an example? Logging is a really weird beast. Nov 28, 2019 at 11:39
  • Dependencies within a layer aren't as much of a problem. That said, there are a couple of things to consider. If it's the adapters that have the dependency on the port, then the ports themselves aren't coupled, unless the needs of the email adapters can change the Logging port interface. In that case, you can make a new abstraction for the email adapters within the same layer, or if that isn't really necessary, just make the email adapters depend directly on the logging library, which is fine, because, well, they are adapters. Nov 28, 2019 at 11:39
  • In other words: [LoggingAdapter]--->[LogLib]<---[EmailAdapter]--->[EmailLib] Nov 28, 2019 at 11:43
  • @BartvanIngenSchenau I initially thought Logging was just an example but thinking about it now, I think Logging is a weird case. For all other such dependencies, I can update the flow so that the domain coordinates the data flow and the adapters don't have to depend on each other. Is that what you meant as well?
    – Guven
    Nov 28, 2019 at 12:48
  • @Guven: Yes. One logging adapter could even depend on another logging adapter. Nov 28, 2019 at 13:15

1 Answer 1

8

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).
3
  • The way I understood the OP is that currently there's an adapter that depends on a port - I could be mistaken about that, but if so, that scenario is likely due to, as you said, logging needs of the email adapter being met by the same logging port abstraction. This is perhaps a bit clunky, but not that bad, because it doesn't force anything to use a specific logging adapter (it even lets the application and the email adapter use two different loggers). The sneakier problem is that, if the needs of the email adapter change, it can effect the logging port if the devs are not careful. Nov 28, 2019 at 23:37
  • @FilipMilovanović indeed two possibilities: 1) the email port may foresee logging and route that part of the protocol directly to the logging port (on the application side). Advantage: by contract the same logging is used. 2) no logging is foreseen in the email port and it’s the adapter to take care (choosing to reuse the existing logging abstraction). Advantage: hidden detail of the email adapter; could deliberately or accidentally log its own stuff somewhere else without interfering with app log. Inconvenience: if unfified log id needed, how to initialize it properly in adapter?
    – Christophe
    Nov 29, 2019 at 9:30
  • This debate occurs for any technical feature you'd like to add within an adapter implementation, without changing its general behavior, nor the port: logging, instrumentation, monitoring, or even caching.
    – mathieu
    Nov 30, 2021 at 7:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.