Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Hexagonal Architecture #71

Conversation

MahatmaFatalError
Copy link
Contributor

@MahatmaFatalError
Copy link
Contributor Author

Any feedback @StephanPirnbaum ?

@StephanPirnbaum
Copy link
Collaborator

Sorry for taking so long. There was a lot to finish for this year.

Looks good to me. Let's ping

@odrotbohm @hschwentner

if they have any objections and if not, we merge it.

Thanks for your contribution!

@StephanPirnbaum
Copy link
Collaborator

Maybe a thought: The hexagon annotations are now in the onion package and module. Although highly related, I don't know if this might be confusing for the end user.

@odrotbohm
Copy link
Member

odrotbohm commented Dec 23, 2021

I think this definitely needs some preliminary discussion. The terminology seems not to be very well-grounded, meaning that different resources use different terms, and I had a hard time finding canonical references for them. The source closest to Alistair I could find was this one: https://web.archive.org/web/20060711221010/http://alistair.cockburn.us:80/index.php/Hexagonal_architecture

The problem of naming becomes particularly apparent in the PrimaryAdapter/SecondaryAdapter space that doesn't reveal much semantics ("What does first, second mean at all?"). Also, I couldn't find an official reference for the CoreLogic annotation.

I generally wonder whether we should do this at all because P&A is pretty much identical to the simplified flavor of Onion Architecture we already have in place. There's value in distinguishing between input and output ports, although things can get messy here, too: is a connector to a broker both sending and receiving messages and input or output port? Trying to arrange them by package would force that integration to be split up among packages, wouldn't it?

@MahatmaFatalError
Copy link
Contributor Author

Thanks for your feedback.

I think this definitely needs some preliminary discussion.

Definitely, that's why I started #70

The terminology seems not to be very well-grounded, meaning that different resources use different terms, and I had a hard time finding canonical references for them. The source closest to Alistair I could find was this one: https://web.archive.org/web/20060711221010/http://alistair.cockburn.us:80/index.php/Hexagonal_architecture

I agree, multiple terms waft around hexagonal architecture. (Basic Port, Adapter + some attempts to be more specific). Nevertheless, I see multiple teams going to hexagonal architecture but due to lack of uniquely defined standard terms, the mess begins. Actually, that was one of the motivations to deal with that topic.

The problem of naming becomes particularly apparent in the PrimaryAdapter/SecondaryAdapter space that doesn't reveal much semantics ("What does first, second mean at all?"). Also, I couldn't find an official reference for the CoreLogic annotation.

I agree 100%.

I generally wonder whether we should do this at all because P&A is pretty much identical to the simplified flavor of Onion Architecture we already have in place.

IMHO hexagonal as well as clean architecture are kind of onionish, similar ideas but different in wording and details. Stating a clear taxonomy would be a goal as well.

There's value in distinguishing between input and output ports, although things can get messy here, too: is a connector to a broker both sending and receiving messages and input or output port? Trying to arrange them by package would force that integration to be split up among packages, wouldn't it?

To address your particular example, I would consider a message listener as input port and message producer as output port for the logically same broker.

In essence, I see a general need for clarification about this topic among developers and jmolecules could give guidance here.

Concrete open topics are:

  1. Classification of onion-like architectures. Side by side or sub-itimization.
  2. Distinctive terms.

Should we continue in #70 ?

@sirius322
Copy link

I gave some input in #70 (comment)

@odrotbohm
Copy link
Member

I took the contribution here and pushed a polished up variant of this to issue/71-hexagonal. I've done the following:

  • move the annotations into their own JAR jmolecules-hexagonal-architecture
  • removed the @CoreLogic annotation as only the port and adapter terms are described throughout the sources I found

It seems generally ready for merge and thus inclusion into 1.5. I'd just need a real name or alias to be used in the @author tag for you @MahatmaFatalError.

@sirius322
Copy link

sirius322 commented May 8, 2022

An annotation like @CoreLogic or @ApplicationCore would be helpful for defining an ArchUnit-Rule asserting that dependencies only point inwards. Something like

public static ArchRule ensureHexagon() {
        return hexagonArchitecture()
                .whereLayer(PRIMARY_ADAPTERS)
                .mayNotBeAccessedByAnyLayer()
                
                .whereLayer(SECONDARY_ADAPTERS)
                .mayNotBeAccessedByAnyLayer()

                .whereLayer(APPLICATION_CORE)
                .mayOnlyBeAccessedByLayers(PRIMARY_ADAPTERS, SECONDARY_ADAPTERS);
}

private static LayeredArchitecture hexagonArchitecture() {
        return Architectures.layeredArchitecture()
                .layer(APPLICATION_CORE).definedBy(layerType(ApplicationCore.class))
                .layer(PRIMARY_ADAPTERS).definedBy(layerType(PrimaryAdapters.class))                
                .layer(SECONDARY_ADAPTERS).definedBy(layerType(SecondaryAdapters.class));
}

The input-/output-naming for the ports is a bit odd. Would an application fetch data from an external system or a database by means of an @OutputPort ?

@odrotbohm
Copy link
Member

I am not sure I can follow. The primary concepts of this architectural approach are ports and adapters. Defining constraints for those without referring to those (esp. the ports) doesn't seem appropriate. I guess adapters can be defined to only refer to ports. Primary ones in terms of dependencies, secondary ones in terms of implementation.

I guess we could introduce meta-annotations @Port and @Adapter but that would prevent exactly the distinguishing part between which adapters have to depend on ports and which ones have to refer to them?

@sirius322
Copy link

Concerning the constraints:
You are right that it would be more appropriate to replace the constraints "primary/secondary adapters may only depend on core" with "adapters my only depend on corresponding port types" . This means that parameter and return types of ports also have to be annotated as ports, right? (I suppose that this is one reason why whole packages can be annotated as ports)

How can I formulate a constraint that elements from the "core" may not depend on any adapter? Isolating the core from delivery mechanisms and technology - which belong in the realm of adapters - is one of the goals of hexagon architecture.
The constraint "dependencies may only point inwards" is shared by hexagon, onion, and clean architecture even though the exact definition of layers differs for each one.

Concerning the naming of the port annotations:
I agree with having two kinds of ports. For me, the terms "input" and "output" imply direction of data flow. Hexagon cares more about who ist starting the conversation between application and the outside, rather than data flow direction. Admittedly it does not sound very creative but I guess that is why Cockburn (https://alistair.cockburn.us/hexagonal-architecture/) used the terms "primary port" and "secondary port"

Primary actor (outside SUD) -> primary adapter -> primary port -> application
Application -> secondary port -> secondary adapter -> secondary actor (outside SUD)

@odrotbohm
Copy link
Member

First: great input! I like where this is going.

You are right that it would be more appropriate to replace the constraints "primary/secondary adapters may only depend on core" with "adapters my only depend on corresponding port types" . This means that parameter and return types of ports also have to be annotated as ports, right? (I suppose that this is one reason why whole packages can be annotated as ports)

That depends on the implementation of the validation. One could argue (and thus implement the validation that way) that all types referred to by a type annotated with @Port "belong" to the port, as they're exposed by it.

The ability to annotate packages is primarily for consistency reasons with other architecture annotations. Personally, I am not the greatest fan of that pattern as it create incentives to structure packages around technical abstractions, which usually leads to package arrangements driven by organization not for encapsulation. That said, I can imagine an arrangement that declares adapter packages but a single package with @…Port annotated types.

How can I formulate a constraint that elements from the "core" may not depend on any adapter? Isolating the core from delivery mechanisms and technology - which belong in the realm of adapters - is one of the goals of hexagon architecture.
The constraint "dependencies may only point inwards" is shared by hexagon, onion, and clean architecture, even though the exact definition of layers differs for each one.

Any other technical arrangements like frameworks out of the picture, no code, maybe except other adapters must depend on adapters. Alistair uses the term Application in his graphics a lot, which we could turn in an annotation to give more control over what code is considered that. I just didn't like that really as, in fact, "application" is a very generic term and usually refers to the entire arrangement including the adapters (as in "running the application"). I am not generally opposed to introducing it, though, I just wanted to start small, as we can always add stuff but hardly take anything back.

I agree with having two kinds of ports. For me, the terms "input" and "output" imply direction of data flow. Hexagon cares more about who ist starting the conversation between application and the outside, rather than data flow direction. Admittedly it does not sound very creative but I guess that is why Cockburn (https://alistair.cockburn.us/hexagonal-architecture/) used the terms "primary port" and "secondary port"

We should move to the "primary" / "secondary" terminology for the ports, too, then. I like how it takes the terminology away from data flow.

@sirius322
Copy link

sirius322 commented May 10, 2022

The ability to annotate packages is primarily for consistency reasons with other architecture annotations. Personally, I am not the greatest fan of that pattern as it create incentives to structure packages around technical abstractions, which usually leads to package arrangements driven by organization not for encapsulation. That said, I can imagine an arrangement that declares adapter packages but a single package with @…Port annotated types.

A single port package becomes essentially a port layer separating adapters from the application core. A valid approach and one that is useful when core components share ports. Another approach would be to structure the application core into business-oriented slices, each bringing their own ports with them. This works in practice quite well in conjunction with the moduliths extension for spring boot. Each module is either a primary/secondary adapter or an application core module.

I like the fact that there is still room for design choices within the archtitecture.

@odrotbohm
Copy link
Member

Yeah, I just wanted to get across that especially secondary port package bear the risk of code being able to bypass primary ones as all types need to be public in them so that the application code can refer to them. That unfortunately opens them up for reference unless. As you indicated, it'd work fine in a Moduliths arrangement, though.

@MahatmaFatalError
Copy link
Contributor Author

Big thank you for the progress on this topic 👍

Regarding

I'd just need a real name or alias to be used in the @author tag for you @MahatmaFatalError.

I would not mind if you leave me out. I mean, almost everything came from you guys.

@odrotbohm
Copy link
Member

Fine with me. You took the time to initiate the discussion and first draft and that kind of contribution should be visible. It is still through this discussion here, so I'll proceed without you being mentioned as author in the source files as you suggested.

odrotbohm added a commit that referenced this pull request May 16, 2022
Move hexagonal architecture abstractions into separate JAR. Also remove the @corelogic annotation in favor of @Application. Revamped the Javadoc and added a bit more metadata. Introduces @adapter and @PORT annotations for use as meta-annotations primarily.
@odrotbohm
Copy link
Member

This is in place now feel free to give the snapshots a try. I wouldn't mind seeing a PR implementing the corresponding violation checks using ArchUnit in xmolecules/jmolecules-integrations. 🙃

@odrotbohm odrotbohm closed this May 16, 2022
@odrotbohm odrotbohm added this to the 1.5 milestone May 16, 2022
@odrotbohm odrotbohm self-assigned this May 16, 2022
@odrotbohm odrotbohm added type: enhancement New feature or request module: architecture Architectural style related support labels May 16, 2022
@odrotbohm odrotbohm changed the title adds annotations for hexagonal architecture Add support for Hexagonal Architecture May 16, 2022
@odrotbohm
Copy link
Member

I've just created #79 for additional metadata in the annotations to support extracting developer documentation.

odrotbohm added a commit that referenced this pull request May 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module: architecture Architectural style related support type: enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants