hexagonal-architecture

Hexagonal architecture: Dependency between core and outbound adapters?


An excerpt on hexagonal architecture from a book:

...instead of a data persistence tier, the application has one or more outbound adapters that are invoked by the business logic and invoke external applications. A key characteristic and benefit of this architecture is that the business logic doesn’t depend on the adapters. Instead, they depend upon it.

It's a bit confusing to me. First it says 'outbound adapters that are invoked by the business logic', and then it says 'the business logic doesn’t depend on the adapters. Instead, they depend upon it.'.

Are not these 2 contradictory? If business logic has to invoke outbound adapters, then it means it depends on those adapters, not the other way round.


Solution

  • The business logic depends on abstractions, not the implementations.

    The business logic only has a dependency to an interface. So the business logic has a "uses" dependency on that interface. This interface is called a secondary port in the hexagonal architecture. The interface is also often called Repository. A term that is more common in the clean architecture.

    The adapter has an "implements" dependency on that interface.

     +----------------+  uses    +------------+   implements  +--------------+
     | business logic | ------>  | Repository | <------------ | DBRepository | 
     +----------------+          +------------+               +--------------+
    

    The princliple that is applied here is called the dependency inversion principle:

    1. High-level modules should not import anything from low-level modules. Both should depend on abstractions

    2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

    As a result you can easily replace the repository impelemtation, e.g. for testing purposes. So that you don't need to startup a database to test the business logic. The test will be a lot faster then.

    In order to have a good abstraction you have to keep the interface technology agnostic. E.g.

    public interface BadRepository {
          public List<Offer> findOffers(String where); // Upps we exposed the 
                                                       // implementation technology 
                                                       // to the client.
                                                       // because the "where"
                                                       // parameter is
                                                       // usually a SQL where statement.
    }
    

    So you should either hide the details:

     public interface GoodReository {
          public List<Order> findOrders(OrderCriteria oc);
     }