Chop Onions Instead of Layers in Software Architecture
Daniel Marbach, @danielmarbach, http://www.planetgeek.ch
Chopping onions usually makes you cry. This is not the case in software architecture. On the contrary! The onion architecture, introduced by Jeffrey Palermo, puts the widely known layered architecture onto its head. Get to know the onion architecture and its merits with simple and practical examples. Combined with code structuring by feature your software is easy to understand, changeable and extendable. Turn your tears of sorrow into tears of delight. For a very long time the standard answer to the question how components and classes should be organized in the software architecture was layers. Before we explore the promised benefits of layers and how they represent themselves in software architecture, we need to get rid of a common misconception regarding layers vs. tiers.
Layers vs. Tiers
When we talk about layers, we mean the logical separation or division of components and functionality and not the physical location of components in different servers or places. The term "tiers" refers to the physical distribution of components and functionality in separate servers, including the network topology and remote locations. Tiers are usually used to refer to physical distribution patterns such as "2 Tier", "3 Tier" and "N Tier". Unfortunately, both layers and tiers often use similar names. In this article, we talk about layers and not tiers! Nevertheless, what is a layer besides a logical separation and when was it introduced?
In the year 1996 Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad and Michael Stal analyzed different software systems. They asked themselves what patterns make software systems successful and allow us to evolve systems without developing a big ball of mud. Their knowledge was published in a book called Pattern-oriented Software Architecture – A System of Patterns. [12]
In that book they came to the conclusion that large systems need to be decomposed in order to keep structural sanity. The so-called Layer pattern should help to structure applications that can be decomposed into groups of subtasks in which each group of subtasks is at a particular level of abstraction. The initial inspiration came from the OSI 7-layer Model defined by the International Standardization Organization. This inspired the original N-Layer model.
N-Layer model
The layer higher in the hierarchy (Layer N+ 1) only uses services of a layer N. No further, direct dependencies are allowed between layers. Therefore, each individual layer shields all lower layers from directly being access by higher layers (information hiding). It is essential that within an individual layer all components work at the same level of abstraction. This approach is also called strict layering. The relaxed or flexible layering is less restrictive about the relationships between layers. Each layer may use the services of all layers below it. The advantage of this approach is usually more flexibility and performance (less mappings between layers) but this is paid for by a loss of maintainability.
In order to be able to define layers and put component into layers you have to define the abstraction criterion. For example, the lower levels can be defined by the distance from the hardware on the upper levels by the conceptual complexity. Possible layering could be chosen (top to bottom):
- User-visible elements
- Specific application modules
- Common services level
- Operating system interface level
- Operation System
- Hardware
The common layers they defined were
Other books or articles may name it differently but we will stick to that definition. We have the presentation or client layer, the process or service layer, the domain or business logic layer, the data access or infrastructure layer. Sometimes you see the layers above extended with another layer sitting on the left side spawning all layers. This layer is often called crosscutting layer which handles tracing, logging and more.
The advantages of this approach are:
Layer reuse
If an individual layer embodies a well-defined abstraction and has a well-defined and documented interface, the layer can be reused in multiple contexts. Naturally the data access seems a nice fit for layer reuse.
Supports standards
Clearly defined and commonly accepted levels of abstraction enable the development of standardized tasks and interfaces. After many years of layered architecture a lot of tools and helpers have been invented to automatically map from one layer to another for example.
Local dependencies
Standardized interfaces between layers usually confine the effect of code changes to the layer that is changed.
Layer exchange
Individual layer implementations can be replaced by semantically equivalent implementations without too great of an effort.
Upon first sight the layer model seems straightforward and easy to adopt. Unfortunately developers often take the layering literally. Sometime later, this happens…
Instead of getting the best out of the benefits of the layered architecture style, we end up with several layers dependent on the layers below it. For example giving the previous layering structure the presentation layer depends on the application layer and then on the domain layer and finally on the database layer. This means that each layer is coupled to the layers below it and often those layers end up being coupled to various infrastructure concerns. It is clear that coupling is necessary in order for an application to be able to do anything meaningful but this architecture pattern creates unnecessary coupling.
The biggest offender is the coupling of the UI and business logic to the data access. Wait a moment. Did I just say that the UI is coupled to the data access? Yes indeed. Transitive dependencies are still dependencies. No matter how anyone else tries to formulate it. The UI cannot function if the business logic is not available. The business logic in return cannot function if the data access is not available. We gracefully ignore the infrastructure because typically it varies from system to system. When we analyze the architecture above in retrospective, we detect that the database layer is becoming the core foundation of the whole application structure. It is becoming the critical layer. Any change on the data access / infrastructure layer will affect all other layer of the application and therefore changes ripple through from the bottom to the top of the application.
This architecture pattern is heavily leaning on the infrastructure. The business code fills in the gaps left by the infrastructural bits and pieces. If a process or domain layer couples itself with infrastructure concerns, it is doing too much and becomes difficult to test. Especially this layer should know close to nothing about infrastructure. Infrastructure is only a plumbing support to the business layer, not the other way around. Development efforts should start from designing the domain-code and not the data-access, the necessary plumbing should be an implementation detail.
Layer.Factory sample
To illustrate the layer architecture, we will analyze a Layer.Factory code sample that is available on github [13]. The Layer.Factory sample is a very simple Domain Driven Design sample application which follows the layered architecture pattern. The idea is that the domain model behind it represents a factory which produces layers (what a coincidence). In order to be able to create layers a factory responsible for creating layers must be created first. In order to analyze our sample application, we use a tool called Structure101 Studio. Structure101 Studio is a commercial tool which helps to visualize and organize large code bases in various programming languages. When we analyze the sample application with Structure101 Studio we see that the application is well structured. It contains no namespace or class tangles and no fat namespaces or classes. From a structural complexity perspective our application is in good shape. The dependencies are only going down from layer to layer. Therefore the sample adheres the strict layering principles.
In order to see how the application structures itself internally we need to drill down deeper.
The presentation layer entry point is the LayerProductionPresenter. The LayerProductionPresenter uses the ILayerProductionApplicationService to open a factory and produces layers by using the previously opened factory. Because the application follows the strict layering pattern, the process layer has to translate domain objects into data transfer objects residing in the process layer (FactoryInfo and LayerInfo). The presentation layer can only use these data transfer objects to present information on the views. Data held by the domain objects has to be translated from layer to layer.
Drilling down deeper into the domain layer makes this issue more apparent. The LayerProductionApplicationService uses a set of Domain Services. These Domain Services implement the core business logic of the application and directly expose the domain model’s aggregates, entities and value objects (i.e. Factory, FactoryId, FactoryName, Layer, LayerQuantity).
As we can see, there is a bunch of translation from top to bottom and from bottom to top going on. Input information floating down from the presentation to the domain layer has to be translated from the presentation data transfer objects to the process data transfer objects and ultimately to the domain objects. Output information going up from the domain to the process layer has to be translated from the domain objects to the process data transfer objects and ultimately to the presentation data transfer objects. As long as only data is transferred the mapping process is tedious but manageable. As soon as the presentation layer would like to reuse business rules from the core domain model this approach’s drawbacks outweigh its benefits.
How do we get around the drawbacks of the layered architecture? With onions! What else?
Onion Architecture
We simply move all infrastructure and data access concerns to the external of the application and not into the center. Jeffrey Palermo proposed this approach called Onion Architecture on his blog 2008. The approach is nothing new. However, Jeffrey liked to have an easy to remember name, which allows communicating the architecture pattern more effectively. Similar approaches have been mentioned in Ports & Adapters (Cockburn), Screaming Architecture (Robert C. Martin), DCI (Data Context Interaction) from James Coplien, and Trygve Reenskaug and BCE (A Use Case Driven Approach) by Ivar Jacobson. Let us depict the onion architecture.
The main premise is that it controls coupling. The fundamental rule is that all code can depend on layers more central, but code cannot depend on layers further out from the core. In other words, all coupling is toward the center. This architecture is unashamedly biased toward object-oriented programming, and it puts objects before all others.
Furthermore the Onion Architecture is based on the principles of Domain Driven Design. Applying those principles makes only sense if the application has a certain size and complexity. Be sure to reflect properly on that point before jumping blindly into the Onion Architecture. Let us see what Onions combined with Domain Driven Design produces.
In the very center, we see the Domain Model, which represents the state and behavior combination that models truth for the organization (everything unique to the business: Domain model, validation rules, business workflows). The number of layers in the application core will vary, but remember that the Domain Model is the very center, and since all coupling is toward the center, the Domain Model is only coupled to itself.
The first ring around the Domain Model is typically where we would find interfaces that provide object saving and retrieving behavior, called repository interfaces. The object saving behavior is not in the application core, however, because it typically involves a database. Only the interface is in the application core.
Out on the edges we see UI, Infrastructure, and Tests. The outer rings are reserved for things that change often. This approach to application architecture ensures that the application core doesn't have to change as: the UI changes, data access changes, web service and messaging infrastructure changes, I/O techniques change.
The Onion Architecture relies heavily on the Dependency Inversion principle. The application core needs implementation of core interfaces, and if those implementing classes reside at the edges of the application, we need some mechanism for injecting that code at runtime so the application can do something useful. So tools like Guice, Ninject etc. are very helpful for those kinds of architectures but not a necessity.
The application is built around an independent object model. The whole application core is independent because it cannot reference any external libraries and therefore has no technology specific code. The inner rings define interfaces. These interfaces should be focusing on the business meaning of that interface and not on the technical aspects. So the shape of the interface is directly related to the scenario it is used in the business logic. The core takes ownership of these interfaces. Outer rings implement interfaces, meaning all technology related code remains in the outer rings. The outermost ring can reference external libraries to provide implementations because it contains only technology specific code. This allows pushing the complexity of the infrastructure (which has nothing to do with the business logic) as far outwards as possible and therefore, the direction of coupling is toward the center.
That approach makes us independent of several infrastructure and crosscutting concerns:
- Database: The business rules do not depend upon the database (so storage can be swapped out)
- UI: The UI can change without changing the rest of the system
- Frameworks: The architecture does not depend on the existence of some library. This allows you to use frameworks as tools rather than having to cram your system into their limited constraints
- External agency: Business rules do not know anything about the outside world.
Which leads us to the ultimate benefit of this architecture. The application core is 100% testable.
Onion.Factory sample
To illustrate the onion architecture, we will analyze an Onion.Factory code sample that is available on github [14]
The Onion.Factory sample is a very simple Domain Driven Design application which follows the onion architecture pattern. The idea is that the domain model behind it represents a factory which produces onions (what a coincidence). In order to be able to create onion, a factory responsible for creating onions must be created first. When we analyze the sample application with Structure101 Studio we see that the application is well structured. It contains no namespace or class tangles and no fat namespaces or classes.
All classes which have dependencies to concrete things like the database, the file system, the view and more reside in the outer rings. All dependencies are floating towards the core. No dependency points from the core into the outer rings.
In order to see how the application structures itself internally we need to drill into the core. The core is shown isolated without the outer rings.
The core itself is pure domain driven design and contains only what is near and dear to the heart of the business: the business domain and rules! When we reflect again about the layered architecture we remember that we needed to map from data transfer object to data transfer object over all layers. How does the presenter and its dependencies look like with the onion architecture?
The OnionProductionPresenter uses the IOnionProductionApplicationService. This is exactly the same dependency we’ve previously seen in the layered architecture. The real difference becomes apparent in the area where the presenter directly uses the domain model. The presenter uses and display information available on the domain model and can reuse business rules implemented in the domain model behind aggregates, entities and value objects. We don’t need to reinvent the wheel!
Layers vs. Onions
If we apply the principles of the Onion Architecture to the layered architecture, we need to turn the layer diagram upside down.
The key difference is that the Data Access, the presentation and the cross-cutting layer along with anything I/O related is at the top of the diagram and not at the bottom. Another key difference is that the layers above can use any layer beneath them, not just the layer immediately beneath. At least this approach could be achieved by using relaxed layering.
If we put the traditional layered architecture in concentric circles we clearly see the application is built around data access and other infrastructure. Because the application has this coupling, when data access, web services, etc. change, the business logic layer will have to change. The world view difference is how to handle infrastructure. Traditional layered architecture couples directly to it. Onion Architecture pushes it off to the side and defines abstractions (interfaces) to depend on. Then the infrastructure code also depends on these abstractions (interfaces). Depending on abstractions is an old principle, but the Onion Architecture puts that concepts right up front.
Refactoring from Layers towards Onions
Now that we know the difference between layers and onions, let us try to refactor the Layer.Factory sample towards an onion architecture. The first refactoring approach we’ll take will try to leave as many of the original namespaces intact, remember they are used in the sample to simulate layers. After applying the following steps
- Introduce Core namespace
- Move Process and Domain namespace into Core
- Move FactoryRepository and LayerRepository into Data namespace
The code structures itself like the following:
We can see in that simple example that refactoring from a layered architecture to an onion architecture is not enough in order to get a true onion architecture. We actually need to redesign the software. The newly introduced Core still has a dependency outgoing. In order to get rid of this dependency we would need to introduce a small abstraction (interface) defined by the needs of the Core. The introduced interface needs then to be implemented by the outer rings (in the sample above by the SystemClock. Furthermore we need to get rid of the unnecessary abstractions introduced by the layered architecture in example by removing the unwanted data transfer objects.
Summary
Let us summarize what we learned. As easy as it may sound in the beginning, strictly following the layered architecture approach can lead to a big dependency tangle. Using the onion architecture approach makes you think about getting the dependencies right from the start. Combined with the introduction of necessary abstractions you achieve an independent application core which is fully testable. This allows you to maintain, protect and evolve what matters the most to your business: your domain logic! Chop onions instead of layers and turn your tears of sorrow into tears of delight.
References
- Alistair Cockburn Hexagonal Architecture
http://alistair.cockburn.us/Hexagonal+architecture - The Clean Architecture Uncle Bob
http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html - Screaming architecture Uncle Bob
http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html - Growing Object Oriented Software Guided by Tests, Steve Freeman & Nat Pryce, Addison-Wesley - Designing for Maintainability Page 47-49
- Ports and Adapters With No Domain Model http://www.natpryce.com/articles/000786.html
- Improve Your Software Architecture with Ports and Adapters
http://spin.atomicobject.com/2013/02/23/ports-adapters-software-architecture/ - Implementing Domain Driven Design by Vaughn Vernon published by Addison-Wesley Professional - Chapter 4 Hexagonal or Ports and Adapters
- The Onion Architecture : part 1 to part 4
http://jeffreypalermo.com/blog/the-onion-architecture-part-1/ - Creating N-Tier Applications in C# by Pluralsight
http://pluralsight.com/training/courses/TableOfContents?courseName=n-tier-apps-part1
http://pluralsight.com/training/courses/TableOfContents?courseName=n-tier-apps-part2 - The Onion Architecture by Matt Hidinger http://matthidinger.com
- Software development fundamentals part 2 Layered architecture by Hendry Luk
http://hendryluk.wordpress.com/2009/08/17/software-development-fundamentals-part-2-layered-architecture/ - Pattern-oriented Software Architecture – A System of Patterns Volume 1 by Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad and Michael Stal published by Wiley Series
- The Layer.Factory example https://github.com/danielmarbach/Layer.Factory
- The Onion.Factory example https://github.com/danielmarbach/Onion.Factory
No comments:
Post a Comment