“High level modules should not depend on low level modules. Both should depend on abstractions.”
The first three circles starting with the central Domain define the application core. Each of these is a collection of high level modules.
The outer-most blue circle is about the details. This is where everything that’s less important goes. This includes all the tools, frameworks and models that support the application core. […]
The green circle is the glue that connects the application core to the details that support it. Adapters are user code implementations that are specific to various details in the blue circle and that hide these away from the inner core circles.
[You define adapters in the green circle, reusing technology details in your blue circle and calling Presentation service from the inner yellow circle. But what if your app also needs to use a delivery framework detail?]
Every piece of code in the core that needs some external service defines an abstraction for that service. […] This abstraction is called a port.
Later, an adapter for the port is defined in the green circle […]. [It] implements the core abstraction and forwards the calls to the specific technology in the blue circle.
I’d still call this “onion architecture” as I think it must remain very clear that the core is to be hosted inside of any executable package, but the points of the linked article are all valid. And indeed, from my point of view too, core reusability is a must for all successful software projects – since their very first version, or even their PoC.
Of course, following the dependency inversion principles in real projects is really difficult. Both because many times it’s not intuitive (especially if you are really tech-focused and you know very well what certain contemporary technologies can and cannot do and you are eager to use one or another to implement what you need) and because you could often end up with thick (or very thick) adapters, for which you’ll need to trade off their implementation cost and the benefits of better core reusability.
The first issue is usually resolved by just sitting down with a pen and a piece of paper and thinking deeply while you draw your diagrams.
For the second, the things are more difficult because many times this would seem to increase costs. But do note that reusability is implicitly required if you’d have a long term, successful project because technologies – i.e. details – will change over time outside of your team’s control. And you don’t want your project to become legacy too early, as the cost of continuing to maintain it into the “new world” would have become too high. (Think about the recent rise of the cloud, mobile devices, and maybe that of the 3D holography in the future, and about the applications that you needed or you’ll need to rewrite from scratch to ensure they’ll live on.)
Finally, it’s OK if you’ll also get multiple other partial onions within some services, such as presentation or adapters. (For example, if your main domain includes a hierarchy of objects, and your presentation services expose visibility values for items based on expansion/collapsing parent items, that part may be extracted out into yet another separate domain that is unrelated to the main one: a domain of tree nodes with expansion state, and items with computed visibility under them, reused by main presentation servies for preparing the main domain hierarchy for user output, and does this in a generic fashion, that later can be further adapted to 2D screens, large or small, read by Alexa devices, or exposed in a 3D diagram.)
Or maybe you’d want to also remove the dependencies between the core services themselves – i.e. having the application and generic presentation services totally disconnected, using an app controller that would just manage the required interactions and orchestrating everything (acting as more than adapters), and overall you’d still benefit of the same dependency inversion idea that brings life to the onion concept (or be it cone, if you wish, to get better mnemonics, as Adi advocates?)