Ready for changes with Hexagonal Architecture
netflixtechblog.com
netflixtechblog.com
“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-...
https://www.destroyallsoftware.com/screencasts/catalog/funct...
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.
* 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.
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.
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.
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.
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.
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.
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).
[1] https://fsharpforfunandprofit.com/ddd/
[2] https://www.amazon.com/Domain-Modeling-Made-Functional-Domai...
[3] https://fsharpforfunandprofit.com/rop/ (Railway oriented programming)
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.
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.
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.
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)
* This article https://herbertograca.com/2017/11/16/explicit-architecture-0... have detailed explanation on the components of hexagonal architecture.
* 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.
* Another sample application in .NET https://github.com/kgrzybek/modular-monolith-with-ddd which gives good overview of code architecture.
Are there any other things you can point to that you think are overlooked?
On top of my head I found these projects interesting,
* Dolphin Scheduler https://github.com/apache/incubator-dolphinscheduler
* Echarts https://github.com/apache/incubator-echarts
* Ant Design https://ant.design/
* Sky walking APM https://github.com/apache/skywalking
* Seata distributed transaction system https://github.com/seata/seata
* EasyTransaction distributed transaction system https://github.com/QNJR-GROUP/EasyTransaction
* EShop-SOA https://github.com/songxinjianqwe/EShop-SOA
* RocketMQ https://github.com/apache/rocketmq
* Sharding Sphere https://github.com/apache/incubator-shardingsphere
* Jeecg-Boot https://github.com/zhangdaiscott/jeecg-boot
* SOFA Stack https://github.com/sofastack
* Dubbo RPC framework https://dubbo.apache.org/en-us/ . Also it seems that every big companies in China have their own RPC framework.
Good talk: https://www.destroyallsoftware.com/talks/boundaries
It works well with OOP as well as with FP. Highly recommend it.
[1] https://docs.microsoft.com/en-us/dotnet/architecture/microse...
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.
(Because of course, I created something like this more than a decade ago that is still in use)
You might like this version better
By the way, making spurious accusations is very much a political technique. So bravo you.
One of the simplest patterns...
What is important is this likely politically astute developer saw an in with this and made it a thing.
You've seen it often enough, I'm sure.
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.
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.
So politically astute is a neutral term.