Question

We are redesigning one of our API projects from an n-layered architecture into onion architecture. In the previous design, we had configuration classes in a common library project being referenced across all the layers (UI, Logic and Data Access layers). How should these configuration classes be fit into an onion architecture?

Was it helpful?

Solution

My guiding principle here is to ask what I consider the most important design question:

What knows about what?

enter image description here

Here's an onion diagram.

It's not that much different from this:

enter image description here

But this one shows the flow of control in and out of layers. That's important here because the flow of control dictates the order of construction if your objects are immutable.

The Controller can call the Use Case Interactor which can call the Presenter. To do that they need references to what they call. That's what they know. Those references only exist once those objects have been constructed. So what must be constructed first? The last object in the flow of control.

This dictates that construction of an onion of immutable objects must start and end with the outside layer. But what if your flow of control is... interesting?

enter image description here

Here we see control dive into the middle, come out the other side, only to dive back in. Do we have to construct everything here step by step following the flow backwards?

No, You can fold that flow until it only dives in and comes out once. You don't have to follow every possible flow of control. Just ensure that EVERYTHING that needs it's reference passed down to the lower layer has been constructed as you descend and EVERYTHING that needs it's reference passed up the the higher layer as you ascend has been constructed.

If we diagram the construction so it can be visualized with the flow of control it might look like this:

enter image description here

I've separated the layers based on the order in which persistent immutable objects can be built due to their dependencies. Once built that horizontal line dividing the layers in two is no longer needed. But if you want immutable objects then this is required so that everything gets the references they need so they can talk.

That does put a design restriction on your immutable objects. But this pattern works well with Command Query Responsibility Segregation.

Of course none of this dictates what you do with your transitive objects that pop in and out of existence as your program runs. Also, mutable objects are allowed to learn and retain references to objects that were created after them. That's how events work in the observer pattern. No, this is only about having the ability to construct a persistent (lives throughout the run time of the program), immutable (unchanging), object graph that can communicate from end to end yet still follow the dependency inversion principle so inner layers won't need to change just because the outer layers did.

Now you can do all of this as a pile of procedural code in main. Or you can break it up a little using every tool in your language. That's a good idea because the point of these layers is that they can be pealed off and replaced without touching the other layers. It's nice if the constructing code can do something similar.

You can even use some DI library that will force you to separate construction from behavior code by making you write it in another language. You just don't have to.

If you'll forgive some psudo code, my basic DI pattern is this:

void main() {

    InterfaceAdapterOutputs iao = makeDescendingFrameworksAndDrivers();
    ApplicationBROutputs abro = makeDescendingInterfaceAdapters(iao);
    EnterpriseBROutputs ebro = makeDescendingApplicationBR(abro);
    EnterpriseBRInputs ebri = makeEnterpriseBR(ebro);
    ApplicationBRInputs abri = makeAscendingApplicationBR(ebri);
    InterfaceAdapterInputs iai = makeAscendingInterfaceAdapters(abri);
    FrameworksAndDriverInputs fadi = makeAscendingFrameworksAndDrivers(iai);

    fadi.start();
}

Now sure that's way too abstract but that's the pattern. Normally I'd have this broken up as more than just layers but also feature segments (kinda like my icon). But this should make you feel like your own language has the power to make all this work without any special magic needing to be added.

I only mention all this because these needs may limit where you can reasonably put your creational code. The better you understand these needs the more flexibility you find in meeting them.

So where do you put your configuration classes? Again, ask "What knows about what?"

The Entities have business rule classes, input and output interfaces that change together within the layer. The use cases know about that layer, and must change according to it's whims. And so must the business rules configuration classes (and tests, and logging, and ...). There are actually many things that slave to the inner layer but don't care about the rest of your application. This means grouping them together so they could be easily separated from the rest of the system is likely a good idea. The same can be said for each layer.

There are also configuration classes that know about multiple layers. Those should be grouped with the most outside layer that they know about. But other than these you could organize your configuration code into onion layers much the same as the rest of your code. Just don't group Use Case code with Entity construction code. While the dependency relationship is the same that's very different code. It should be separated.

enter image description here

However, I am NOT a fan of organizing exclusively by layer. As I said, my code base would diagram more like my icon, with feature sections. This is the package by feature not by layer principle. But within my features things are separated by layer. So for me, any one onion diagram could be one feature.

Licensed under: CC-BY-SA with attribution
scroll top