Awesome ! Thanks for sharing that knoweldge with us and because it is tremendously simpler (not simple) that any other pattern at first sight it is hard to understand why PA made Developer’s life so much better.
This is a nice intro. Ports and adapters is such an awesome pattern, but you see it again and again that the terminology confuses people (it was evident in the live chat, too). And while I understand the hand-waving away of specific concerns, given that AC will have been using ports and adapters for the best part of a decade now, some real world practical “recipes” and model examples for different stacks could really help.
Yes, many developers have been requesting guidance regarding specific concerns about implementing Hexagonal Architecture. Alongside the TDD in Legacy Code series, there are also plans for Hexagonal Architecture in Legacy Code series - to give specific guidance about introducing Hexagonal Architecture. journal.optivem.com/p/tdd-in-legacy-code-transformation
Thanks Alistair. Nice one, appreciate it. Though you drew an ideal world picture and problems emerge when abstractions start to leak or when a port abstraction boundary is wrong
Huge props for Alistair for everything he has done (and doing) for the industry. However, this hexagonal thing hardly can be named an architecture. IMO that is plus/minus the same egg from a different perspective as the "clean" architecture, or showcase of dependency injection. On the other side, some important concerns are not tackled (and cannot be tackled) with such an approach - state management and persistence, temporal coupling, transactionality. He claims I/O is not leaking - but it's leaking everywhere, as soon as one makes a step away from the "happy path". Switched event feed from JMS to Kafka - and here you go, find yourself scratching your head as to where to place commit. Want asynch jdbc - learn that you've got to redo your db adaptor from scratch, as the original abstraction was a synch one. Drawing parallels with hardware world is more an illusion than abstraction. And in hardware world you've got 1 or 2 complex components with a lot of simple stuff (plumbing) around it - it's not what we expect from scalable software architecture IMO. Testability looks simple on the paper, but as long as you consider that each of the "mocked" components have an associated, independently maintained state, the complexity explodes. And to conclude, I (personally) have big troubles to see how this can be married with modern CBA (cloud-based) and EDA (event-driven). On the paper CBA should be a better match, as it also about components and composition, but in reality those components are infra level abstractions, and lifting some vendor component attractions into the code (like Java) is a huge deal (don't confuse it with IaC vendor libs which are different thing) and doesn't add any value. and EDA also breaks it along the temporal coupling matters (hexagonal is mainly synchronous).
Can one adapter connect to multiple port ? Lets say I have an app that I have split into severalt driving ports... Lets say I have one port called "for_managing_customers", and another one called "for_managing_cars". Then I want to make a rest api that manages both Can I then make 1 ApiAdapter that connects to both ports ? Or should I then make 2 ApiAdapters, and somehow merge them in an api gateway ?
I asked about optimistic locking and transactions and @Rob van der Linden Vooren asked a similar thing about propagating context from/to adapters. Unfortunately, the answer is "I don't care". This sounds like an "ivory tower" architecture to me...
Hi! I'm not really used to this specific approach, but I do try to isolate interfaces and their responsibilities in my work, so what Alistair says landed nicely in my mind. To try to figure out some working design of the interfaces, I'd start by asking questions like this to myself: For passing of the context: is it an important part that should be accessed and used in the app itself? I'll make it a part of each interface then (both driving and driven). Or is it an implementation detail? Then I'd try to delegate this to the supporting libraries, to make it a sideband context and then work on supporting it on all types of the adapters (which can be a tedious task and it would be an easy thing to lose). If there is no way to do that in the language/ecosystem, then only option A is left. For supporting a transaction or distributed transaction: Is it an important part of the app itself? I'd expose the transaction control on the driven interfaces and make some kind of transaction manager your required dependency. Then wire it up in the app configuration, so it would control all the driven interfaces that need that management. Or is it just an implementation detail? Then I'd make the transaction control a part of your adapter that would expose only "Save" method on the complex object to the app and do all the work without exposing the complexity of the actual storage. For optimistic locking: Is it important to the application? I'd expose the "Update" method with both expected and desired state of the object as the parameters and make it properly return the updated state with failure status or success status as a result Is it an implementation detail for which I can figure out the automatic resolver of the conflicts? Then I'd expose the "Update" method with the set of desired changes to the object state What do you think, should these approaches and this way of thinking work?
I'd think that the question was a bit confusing in the way that it was worded. This thing can be tricky depending on your needs and there might be different takes on it. For example, if you need a transaction for 2 different write operations of the same adapter, you can provide startTransaction() and endTransaction() methods in the port.
I got one question... When you have driven adapters outside the app, and they are mocked up in the tests... Should not the driven adapter-code be tested ? Or should they be tested seperatly ?
Yes, you definitely want to test the adapters. I normally have integrations test that test each adapter. I also usually have some integration test that covers a complete happy path (but it it's important to point out here that it's a happy path and the goal is not really to test the logic, but just to make sure that all the plumbing works end-to-end).
13:04 "Literally the only way I know to prevent leaks..." Really? Is he serious? What about: - code reviews, so your peers can catch the I/O being added before the code is even checked in - const correctness (in languages that support it) to prevent the UI from modifying the state of the business code in the wrong way or the wrong place - using OS memory management functionality to mark pages that contain data that should not be modified as read-only - linters that scan UI code modules for I/O-using functions and failing the build if any are detected - using link-time seams and a test harness to replace I/O-using functions with functions that throw when called from the wrong place in a test Is it that none of that works or is it that he knows about none of these ideas, all of which I can throw in off the top of my head? Literally, with this "architecture", what stops someone from just putting a random fopen (or Java equivalent) in the constructor of TaxCalculator or whatever UI code references it?
Awesome ! Thanks for sharing that knoweldge with us and because it is tremendously simpler (not simple) that any other pattern at first sight it is hard to understand why PA made Developer’s life so much better.
What a co-incidence - I got interested in Hexagonal Architecture today and this was streamed yesterday
Same, i was reading about this one week ago.
This is a nice intro. Ports and adapters is such an awesome pattern, but you see it again and again that the terminology confuses people (it was evident in the live chat, too). And while I understand the hand-waving away of specific concerns, given that AC will have been using ports and adapters for the best part of a decade now, some real world practical “recipes” and model examples for different stacks could really help.
Yes, many developers have been requesting guidance regarding specific concerns about implementing Hexagonal Architecture.
Alongside the TDD in Legacy Code series, there are also plans for Hexagonal Architecture in Legacy Code series - to give specific guidance about introducing Hexagonal Architecture.
journal.optivem.com/p/tdd-in-legacy-code-transformation
brilliant channel
Thanks Alistair. Nice one, appreciate it. Though you drew an ideal world picture and problems emerge when abstractions start to leak or when a port abstraction boundary is wrong
Huge props for Alistair for everything he has done (and doing) for the industry. However, this hexagonal thing hardly can be named an architecture. IMO that is plus/minus the same egg from a different perspective as the "clean" architecture, or showcase of dependency injection. On the other side, some important concerns are not tackled (and cannot be tackled) with such an approach - state management and persistence, temporal coupling, transactionality. He claims I/O is not leaking - but it's leaking everywhere, as soon as one makes a step away from the "happy path". Switched event feed from JMS to Kafka - and here you go, find yourself scratching your head as to where to place commit. Want asynch jdbc - learn that you've got to redo your db adaptor from scratch, as the original abstraction was a synch one. Drawing parallels with hardware world is more an illusion than abstraction. And in hardware world you've got 1 or 2 complex components with a lot of simple stuff (plumbing) around it - it's not what we expect from scalable software architecture IMO. Testability looks simple on the paper, but as long as you consider that each of the "mocked" components have an associated, independently maintained state, the complexity explodes. And to conclude, I (personally) have big troubles to see how this can be married with modern CBA (cloud-based) and EDA (event-driven). On the paper CBA should be a better match, as it also about components and composition, but in reality those components are infra level abstractions, and lifting some vendor component attractions into the code (like Java) is a huge deal (don't confuse it with IaC vendor libs which are different thing) and doesn't add any value. and EDA also breaks it along the temporal coupling matters (hexagonal is mainly synchronous).
Can one adapter connect to multiple port ?
Lets say I have an app that I have split into severalt driving ports...
Lets say I have one port called
"for_managing_customers", and another one called "for_managing_cars".
Then I want to make a rest api that manages both
Can I then make 1 ApiAdapter that connects to both ports ?
Or should I then make 2 ApiAdapters, and somehow merge them in an api gateway ?
I asked about optimistic locking and transactions and @Rob van der Linden Vooren asked a similar thing about propagating context from/to adapters. Unfortunately, the answer is "I don't care". This sounds like an "ivory tower" architecture to me...
Hi! I'm not really used to this specific approach, but I do try to isolate interfaces and their responsibilities in my work, so what Alistair says landed nicely in my mind.
To try to figure out some working design of the interfaces, I'd start by asking questions like this to myself:
For passing of the context: is it an important part that should be accessed and used in the app itself? I'll make it a part of each interface then (both driving and driven).
Or is it an implementation detail? Then I'd try to delegate this to the supporting libraries, to make it a sideband context and then work on supporting it on all types of the adapters (which can be a tedious task and it would be an easy thing to lose). If there is no way to do that in the language/ecosystem, then only option A is left.
For supporting a transaction or distributed transaction:
Is it an important part of the app itself? I'd expose the transaction control on the driven interfaces and make some kind of transaction manager your required dependency. Then wire it up in the app configuration, so it would control all the driven interfaces that need that management.
Or is it just an implementation detail? Then I'd make the transaction control a part of your adapter that would expose only "Save" method on the complex object to the app and do all the work without exposing the complexity of the actual storage.
For optimistic locking:
Is it important to the application? I'd expose the "Update" method with both expected and desired state of the object as the parameters and make it properly return the updated state with failure status or success status as a result
Is it an implementation detail for which I can figure out the automatic resolver of the conflicts? Then I'd expose the "Update" method with the set of desired changes to the object state
What do you think, should these approaches and this way of thinking work?
I'd think that the question was a bit confusing in the way that it was worded.
This thing can be tricky depending on your needs and there might be different takes on it.
For example, if you need a transaction for 2 different write operations of the same adapter, you can provide startTransaction() and endTransaction() methods in the port.
I got one question...
When you have driven adapters outside the app, and they are mocked up in the tests...
Should not the driven adapter-code be tested ?
Or should they be tested seperatly ?
Yes, you definitely want to test the adapters. I normally have integrations test that test each adapter. I also usually have some integration test that covers a complete happy path (but it it's important to point out here that it's a happy path and the goal is not really to test the logic, but just to make sure that all the plumbing works end-to-end).
13:04 "Literally the only way I know to prevent leaks..." Really? Is he serious? What about:
- code reviews, so your peers can catch the I/O being added before the code is even checked in
- const correctness (in languages that support it) to prevent the UI from modifying the state of the business code in the wrong way or the wrong place
- using OS memory management functionality to mark pages that contain data that should not be modified as read-only
- linters that scan UI code modules for I/O-using functions and failing the build if any are detected
- using link-time seams and a test harness to replace I/O-using functions with functions that throw when called from the wrong place in a test
Is it that none of that works or is it that he knows about none of these ideas, all of which I can throw in off the top of my head? Literally, with this "architecture", what stops someone from just putting a random fopen (or Java equivalent) in the constructor of TaxCalculator or whatever UI code references it?