Search code examples
javaspring-bootonion-architecturehexagonal-architecture

How InboundPorts in the Hexagonal increase testability


In Ports and Adapters (Hexagonal) Architecture you have:

DrivingAdapter -> InboundPort <- [ domain ] -> OutboundPort <- DrivenAdapter

note: of course the ports are part of the domain

A concrete example of this could be:

WebController -> OrderServicePort [ order domain ] -> OrderRepositoryPort <- MongoDbOrderRepositoryAdapter

So the idea behind Ports and Adapters Architecture is the you test your domain separately from the concrete adapters at the edges of the application.

What I don't get is how exposing an interface Port on the left side is useful for testability?

WebController has no significant logic so would not be a test target per se and would be tested as part of end to end testing. So I would not mock the OrderServicePort as it normally would be my test subject.

Of course there is value in InboundPorts in that they only expose a simplified view of the concrete domain class that implements it.

But I can't see where the increased testability comes from by using InboundPorts.

Agree?


Solution

  • TL;DR;

    Yes, I agree.


    I see ports as an abstract concept, not tied to a language construct. I mean that a port doesn't mean an interface, and maybe doesn't even need an interface. In some languages there aren't even interfaces, so the ports need to be done using other language constructs.

    I also see the primary/driving (inbound) ports and the secondary/driven (outbound) ports as artifacts with different concrete goals, although sharing the same idea of being an entry/exit point from the application core.

    The way i see it, the secondary ports are a "prototype" of a tool the core needs, in the way it needs it. In many contexts this "prototype" is just an interface, but it is limiting to always think of it as an interface. The secondary adapter will be the concrete implementation of that port, wrapping around a library. This is powerful cozz it makes it easy to swap the library used, be it for testing or convenience.

    However, the primary port its an entry point to the application, it starts a use case, it tells the application what to do, and how. Its a concrete thing, not a prototype. It IS the application core. So if we replace it, we are replacing the application core itself. A primary port is not a prototype like the secondary port, therefore, in itself, it doesn't need something like an interface.

    The only reason to replace it, would be to test the primary adapters themselves, just to make sure they point to the correct use case. In such case, we would need something like an interface. But in such case we need to boot up the application anyway, which is usually the most expensive part or the test, so we might as well do it as an e2e, functional or integration test, where we also test the core, or part of it.

    In some languages, or applications, or contexts, in some edge cases, maybe this needs to be done, maybe its the best way. We should not say something dogmatic like "never do ..." or "always do ...", it always depends on the context.

    However, generally speaking, I agree with you: An interface on a primary port does not bring much advantage for testing, and i would go further and say that a primary port does not need an interface.