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”:
“A function either integrates or operates, it either only calls other functions or it does not call other functions but contains only logic.”
Basically, you have code that interacts with the outside world, and you have logic (read: conditionals and branching). Don’t mix the two.
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.
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.
To me this article looks a lot like the kind of principle that looks very nice in articles but immediately breaks down in practice with even slight complexity.
First of all, the difference between operations and integrations makes no sense, even in his examples. All of his functions are calling other functions. All of his integrations are doing some logic (passing results between functions in particular ways). There is no fundamental difference between 'if a then b else c' and 'other_function(a, quote(b), quote(c))'.
Second of all, this idea that you don't have dependencies between Presentation and Business only looks like that because he's passing very simplistic data between them. But in real code you have complex data structures and complex interactions and complex invariants and they almost unavoidably introduce dependencies between 'strata'. Even if you force yourself to use maps and strings and lists so you don't have explicit type dependencies, you still get implicit structure dependencies.
The premise is not that there are no dependencies of any sort, or that you aren't allowed to pass complex structures from one to the other. Its more about splitting logic (and by logic, he is referring to code with multiple, branching paths) from side-effects.
When you mix effectful code (rendering to the UI, accessing a database) with conditional logic, you need to resort to mocking (potentially complex) objects just to perform basic unit testing. He's simply stating that if you split those out into different functions (operations in his case) and use a simple function to integrate them together, it will make life easier, and I agree.
Its a very functional approach; get data, perform calculation, return data.
Like I said, I thought article did a good job of distilling an important idea into a simple pattern. It doesn't have to be an overarching architectural design; even just using it locally in a larger code base can make life easier.
*As an aside, I'd say there is a fundamental difference between:
'if a then b else c'
and
'other_function(a, quote(b), quote(c))`
The first, when tested inside the scope of its parent function needs multiple unit tests for full coverage, the second only needs one.
I think the last point is the most important - if there really is a difference between those two pieces of code, then the article does make some sense. If there isn't, the the article's pout is weakened.
The reason why I think they are equivalent is that other_function may well be really reusable and have many different use cases, of which only one is right in my current use case. For example, it could be the 'if' special form in Lisp. That means, I can know for sure that other_function is itself perfectly correct, but still be calling it badly, so I still need to test whether my function does the right thing for any possible value of a, b and c.
I actually thought about the same 'if' special form when formulating my response (I've been playing a lot with Clojure lately, and your other_function looks a lot like a functional conditional when compared to an if/else), but figured that most people would be viewing this stuff through a more typical imperative lens, in which case, those constructs (if/else vs other_function) would likely be different.
I don't understand what the article means by "logic". "Ask_for_text() and Display_word_count() contain only logic" but "Count_words{} does not contain any logic". The requirement to remove stop words is accomplished by a piece of "logic". If that requirement were removed, a test would change, and the place you'd go to fix that test would be Count_words.
If "logic" means "stateful" or "I/O", then OK, I get it, even if I find the terminology unfamiliar. But in familiar terms it looks a lot like MVC.
The semantic symmetry of the OSI layers is clearly an 'interaction': there is a computer talking to another computer. The stack diagram is obviously only considering a single object, a system. Would not be surprising that should we introduce the 'user' as the peer to the system that we can in fact derive a similar symmetry in layers. Yes, the user side of the layers would be mostly logical 'organizational' concerns.
I disagree. The author's point is that if you have a network connection, you can view it as:
* A medium with a series of photons or EM waves propagating in one or both directions.
* An exchange of packets between two boxes.
* …
* A conversation between two applications.
It's the all the same thing, but it's an increasingly holistic or reductive view of that one thing as you take the thing in from the perspective from increasing or decreasing layers of the OSI stack.
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 (https://www.enterpriseintegrationpatterns.com/patterns/messa...). I guess you'd call what I use a hybrid of the EIP style and the Hexagonal style.
This sounds really interesting, and reminds me of "Domain Modeling Made Functional".
But could you describe the pipes a bit more? EIP from the link your provided says that any interactor/filter in a pipe is interchangeable with another. Based on the model you described you say you'd have each interactor take in an entity and output a possibly different entity (which seems reasonable). But if you did that and had a chain of interactions, then removed one from the middle wouldn't you now have a type mismatch where you try to connect the remaining interactions?
Good question. You're right in that the Interactors can only be chained according to their types. Let's say I had an A->B, C->D, B->C, and C->E. I could form a chain (A->B)->(B->C)->(C->D), but I couldn't do (A->B)->(C->D). The Pipes come into play for routing, splitting, and combining. I can use [] as syntax for a splitter and => for a combiner. The combiner is used in conjunction with {} to represent an input that's a combination of the inputs inside of it. I can then represent textually a pipeline to produce F from A with functors (the 1-1 Interactors), a combiner pipe and a splitter pipe. (A->B)->(B->C)->[(C->E), (C->D)]=>({E,D}->F). As a side note, you can tell I like to make sure I can have a textual syntax for describing things..let's you process it with sw and I am horrible at graphics.
Regarding interchanging the different, I think it means you can swap a router for an interactor, or vice versa, as long as the types match. It's basically enterprise architecture level functional programming, kind of.
I like your approach to problem solving - it's very similar to mine and in domains I also think about.
Your syntax is very convenient thanks for sharing.
And finally, I assume you're familiar with Scott Wlashin (of F# for fun and profit & "Domain Modeling Made Functional")[1][2]. If not you 100% should read it as it is right up your alley. It's the intersection of Functional Programing and DDD/EIP. (I have no affiliation).
Thanks, for the compliment and for the links. I wasn't aware of him until now. I've little experience with the X# family, other than some Mono hacking on an OpenSim clone. Sounds interesting and will definitely read at least the Domain Modeling one, possibly the F#s too (never too late to add a piece of kit to your toolbox).
I love the idea, and maybe this works well for them in a position of relatively lax performance.
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.
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.
I always push this approach when developing applications, but I work in large enterprise where performance often isn’t critical, but applications can live on for _decades_.
Should you use this approach when developing something like nginx? Probably not. But if you are developing a line-of-business application that has to interact with an ever-evolving mish-mash of other systems over an embarrassingly long time frame, it’s a great approach.
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.
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.
Much finer grained then repos. Also allows for finer grained control over which data source you use for each interaction.
It depends. Sometimes a repository can cover all needs with relatively few and relatively generic methods, sometimes specialized database interactions can be application logic layered on top of a much less specialized repository, sometimes a repository becomes unpleasant because it should be split up, etc.
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?
My understanding is that only the storage layer should care about that, and only if it's a relational store. The concept of looking things up by "id" will obviously make it to the application layer but the application layer "id" isn't directly linked to the PK column of a particular SQL table, even if it's going to be the most common use case.
Hexagonal, Domain-driven, Onion, Clean architecture are all synonyms at this point.
Core libraries for shared utilities and domain objects, next layer is business logic, then outer layer of interfaces to everything else like databases and webservers.
> Hexagonal, Domain-driven, Onion, Clean architecture are all synonyms at this point.
There are important differences between, say, hexagonal architecture and clean architecture. For instance, in clean architecture, what the Netflix guys described as repositories would actually be the data source itself and represent yet another interface on the system's outer layer.
Additionally, what the Netflix guys described as interactors would actually be the application layer, and it would be the only layer above the entity layer (i.e., activities don't depend on data source components)
Microsoft also published a good series of articles on DDD / CQRS / Hexagonal architectures [1]. I especially like the diagrams of dependencies between layers on that linked page.
I prefer to have an additional layer that is usually called the service layer. You use it to express and orchestrate the actions of your application without hard dependencies on specific tech.
So the application layer is the one that manages specific dependencies and injects them into the service layer and the service layer then goes:
- okay, when asked to disburse a loan
- I construct a domain object for the loan
- If the object is valid, I make a sync call to deposit the money
- I then persist the loan
- I then send a notification that a loan is disbursed
And it doesn't really know how the RPC, the notification, or the persistence layer actually work.
* There is COLA framework https://github.com/alibaba/COLA based on spring boot from Alibaba. Description is in Chinese but google translate does good enough translation. There is some pretty interesting things happening in Chinese universe that are hidden for language barrier.
I occasionally check trending repositories of chinse language in github https://github.com/trending?spoken_language_code=zh and github repositories of Alibaba, tencent etc. From the look of it they are very focused on tooling/tutorials on highly salable distributed systems architecture. Java is very popular and spring cloud is their preferred choice. One thing I found interesting is that hibernate ORM is not used anywhere, instead they prefer MyBatis data mapper framework. Some of those projects are moved to apache foundation.
On top of my head I found these projects interesting,
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????"
(Because of course, I created something like this more than a decade ago that is still in use)
It is in fact relevant to your claim that it sounds like somebody came up with it as a career advancement technique. Rather than it being part of a decades-long dialog about how to build software.
Anything can be used for politics. But I've met Cockburn a few times. Ditto Fowler and Evans, all of whom have related work in software architecture. They are all people sincerely trying to figure out better ways to build software.
By the way, making spurious accusations is very much a political technique. So bravo you.
Sorry, what truth do you think I should be seeking here? My point was that you were needlessly (and apparently ignorantly) trash-talking a serious contribution to the long, ongoing discussion of software architecture. If you've decided to stop doing that, great.
I’m old enough to remember people saying this exact thing about XP.
I also recall C and C++ developers saying Java wasn’t special because you can do all of those things in C. Yeah well, “can” and “will” are two very different experiences.
My comment seems to strike people with the wrong intention. I'm not making a negative judgement on politically astute developers. Being politically astute is a necessity in most high-tech places.
A politically astute developer can take an idea and build a movement around it, whether or not it is their own idea for career advancement. This struck me as one of those things.
I was a politically astute developer at one point, I built movements. But they were not intended for career advancement and thus had the backing of technical people more than non-technical people. Quite often I came across less technically strong, but more politically astute developers than I who were able to take weak ideas and sell them to non-technical people in decision making positions purely as a career advancement move.
Glad I'm not the only one, looks pretty much the same as the [1] "repository pattern". Basically a simple data collection interface which abstracts the datasource's type/implementation.
There's more to it than that; repositories are a subpart of hexagonal architecture, and are used in a specific way. That being said, there's not a lot more to it than that. It's a collection of recognisable parts.
“A function either integrates or operates, it either only calls other functions or it does not call other functions but contains only logic.”
Basically, you have code that interacts with the outside world, and you have logic (read: conditionals and branching). Don’t mix the two.
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.
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.
[1] https://medium.com/clean-code-development/stratified-design-...