I came across an article a few weeks ago [1] that distils the idea of this architecture into a very simple pattern, stratified design, or the “Integration Operation Segregation Principal”:<p>“A function either integrates or operates, it either only calls other functions or it does not call other functions but contains only logic.”<p>Basically, you have code that interacts with the outside world, and you have logic (read: conditionals and branching). Don’t mix the two.<p>It’s such a simple concept that it seems obvious in hind-sight (so maybe that’s why nobody ever talks about it, because it is obvious), but I don’t see code written this way all that often.<p>If there’s one thing I could teach every new developer it would be this. It’s such a little thing, but it would make a lot of code much easier to work with.<p>[1] <a href="https://medium.com/clean-code-development/stratified-design-over-layered-design-125727c7e15" rel="nofollow">https://medium.com/clean-code-development/stratified-design-...</a>
I was disappointed that the article didn't mention Alistair Cockburn, who came up with this architecture style, which is also sometimes called ports and adapters. I like hexagonal architecture a lot, but I change one aspect. Instead of Interactors I use composable Functors and Pipes. A Functor is an Interactor that takes in one Entity and produces an Entity. A Pipe is software that implements one of the different Router patterns from Enterprise Integration Patterns (<a href="https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageRoutingIntro.html" rel="nofollow">https://www.enterpriseintegrationpatterns.com/patterns/messa...</a>). I guess you'd call what I use a hybrid of the EIP style and the Hexagonal style.
I love the idea, and maybe this works well for them in a position of relatively lax performance.<p>The truth is that all abstractions around data fetching are inherently leaky; making an API call does _not_ have the same runtime characteristics as reading from a file. Different access patterns have different performance implications across different data sources.<p>I would love if somehow we could put runtime performance characteristics in the type systems. Implementing readFile<T extends Readable, S extends SomeRuntimeCharacteristic> would go a long way towards sealing those leaks.
People have been using this for years. One of things I recommend is not using the repository pattern. You end up with a repo with lots of methods on and overly generic.<p>For each of the use cases/interactors, any database action should be wrapped up in a specific interface for that database action. Then implement those database interaction by implementing that interface. Query and command objects instead of repos.<p>Much finer grained then repos. Also allows for finer grained control over which data source you use for each interaction.
Some thing I was wondering about this kind of architecture, how would you express some constrains, like foreign key constraints or unique key constraint? Would they still be in the database, or would you put them into some inner layer?
I guess I’ve been staring at LLVM CMake output for too long, because I thought this had something to do with <a href="https://en.wikipedia.org/wiki/Qualcomm_Hexagon" rel="nofollow">https://en.wikipedia.org/wiki/Qualcomm_Hexagon</a>
This sounds like something a politically astute developer came up with and built short-term career growth on it at Netflix while the rest of the developers at Netflix look on and say "yeah ok but that's just <FOO METHOD> drawn like a hexagaon. AM I THE ONLY ONE WHO SEES THIS ISNT SPECIAL????"<p>(Because of course, I created something like this more than a decade ago that is still in use)