4

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?

  • 3
    Configuration classes are an orthogonal concern; they don't have anything to do with the onion architecture proper. Feel free to organize those any way you like. – Robert Harvey Nov 16 '17 at 17:38
  • I would imagine that configuration merely provides values to your object model, which is still the core of the onion architecture. – Berin Loritsch Nov 16 '17 at 17:58
  • @BerinLoritsch an example of our configuration would be connection strings for the repository classes. Since the repository depends on it, would it make sense to create an interface for the configuration on the core layer? – InstilledBee Nov 16 '17 at 18:47
  • Each technology has its own guidance. For example, the .NET Framework uses XML configuration files to store connection strings, the guidance for which can be found here. Consider reading and following this guidance for your specific technology. – Robert Harvey Nov 16 '17 at 18:55
  • 1
    @InstilledBee, I agree. However since Onion architecture is really dependent on Dependency Injection, configuration values are just a dependency that gets injected. That's how Sprint Boot works, at least. In other words your core classes or services should not be looking up configuration values. They should be provided at initialization. – Berin Loritsch Nov 17 '17 at 13:32
12

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.

  • 5
    Cool story bro. But how does this relate to where one should put their configuration classes? :) – Robert Harvey Nov 17 '17 at 18:52
  • @RobertHarvey better? – candied_orange Nov 17 '17 at 19:29
  • Yep. (upvoted). – Robert Harvey Nov 17 '17 at 19:41
  • So, based on this, should I try to segregate my configuration classes on which layer/feature needs it, to adhere to "what knows about what"? e.g. Only the repository needs the connection strings, so should I just bundle the class building the connection strings with it? – InstilledBee Nov 18 '17 at 14:41
  • 2
    Essentially yes. I've added a link that should take you to some articles that explore the idea more in depth. The value you actually get from separation and organization largely depends on the future, which is hard to predict. The basic idea is to separate things that are likely to change from things that likely won't while grouping together things that will likely change together. – candied_orange Nov 18 '17 at 14:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.