The Purpose of Architecture
I believe that the value of explicitly identifying and conforming to an architectural pattern in your applications is two-fold.
- It defines the number of boundaries between layers and how they communicate.
- Future maintainers can leverage what they’ve learned about one vertical slice to understand the rest of the application.
In most cases I don’t think developers bother to identify their application architecture, and consequently their application doesn’t have one. Or rather, it has many. Each new operation is put together in a slightly different way from all the others. Nothing learned about one area of the system can be used to understand other areas of the system. Nothing in the existing implementations direct you on how to add or modify new or existing features.
What a mess!
Onion Architecture
Full disclosure: I don’t know many architectural patterns. However, I’ve spent the last 5 years of my career groping toward an architecture that was hazy in my mind. The first explicit description I encountered of this pattern that I encountered was in a lecture I attended by Uncle Bob at SCNA 2011. Later I found some other resources.
In The Onion Architecture Jefferey Palermo describes a method of structuring an application such that the work of the application is separated from the infrastructure and loose coupling provides flexibility.
There are two important rules about this architecture. I personally thinking of them as the Dependency Direction Principle and the Dependency Depth Principle.
Palermo writes:
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 principle is about the direction of dependencies. A given application may have a greater or fewer number of layers in its “onion,” but the outer layers should depend on the layers underneath, and the inner layers should have no dependencies on the outer layers.
In Clean Architecture, Uncle Bob calls this The Dependency Rule
The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle. That includes, functions, classes. variables, or any other named software entity.
By the same token, data formats used in an outer circle should not be used by an inner circle, especially if those formats are generate by a framework in an outer circle. We don’t want anything in an outer circle to impact the inner circles.
Neither of the above-linked posts calls out what I think is another important rule about dependencies. Here, I’m speaking for myself.
The Dependency Depth Principle: Application layers must depend only on the contracts exposed by the layer immediately beneath them.
By controlling dependency depth, the seams between your application layers become well-defined and easily testable. More importantly, changes to the mechanics or api of one layer do not necessitate changes to the surrounding layers.
An Example
Let’s say I have a domain service that performs searching for musical performances. This service accepts a request like this:
1: public class QueryPerformanceRequest
2: {
3: public int[] PerformanceIds { get; set; }
4: public string[] Artists { get; set; }
5: public string[] Venues { get; set; }
6: public bool? Upcoming { get; set; }
7:
8: public int? Page { get; set; }
9: public int? PageSize { get; set; }
10: }
and returns this:
1: public class QueryResponse<T>
2: {
3: public T[] Results { get; set; }
4:
5: public int Page { get; set; }
6:
7: public int PageSize { get; set; }
8:
9: public int TotalRecords { get; set; }
10: }
QueryPerformanceRequest is a parameter object with which the client can indicate which data is being searched for by filling out properties. Some underlying layer is responsible for executing the query against the data store.
My web application application has this controller action:
1: public ActionResult Search(string term)
2: {
3: using (var command = _performanceCommands.Query())
4: {
5: var request = _builder
6: .Create<QueryPerformanceRequest>()
7: .From(term)
8: ;
9: var results = command.Execute(request);
10:
11: var model = _builder
12: .Create<PerformanceSearchResultsViewModel>()
13: .From(results);
14:
15: return View(model);
16: }
17: }
What’s happening?
Line 3: Constructs the domain service used to perform the search.
Line 5: I like to use a fluent api for object conversion. I’ll speak more about this later. In this case, we’re just converting the incoming string into the QueryPerformanceRequest object. The converter manages the seam between the Application Services layer (MVC Controller) and the Domain Services layer (the command).
Line 9: This is the line that performs the query.
Line 11: Now that we have results from the query, let’s get them ready for presentation by converting them into a ViewModel.
Line 15: Returns the page with the required data.
Things to notice:
The domain services layer provides a contract in the form of a command to execute the query, a parameter object to describe the query, and the query results. It’s probably that somewhere inside the command database connections are being created, domain models are being searched for, and the results are being transformed back into the response. The controller layer does not directly depend on any of that logic.
Further, the controller has it’s own api independent from that of the domain services layer. Pass me a string and I’ll hand you a ViewModel. None of the details of the domain services layer leak into the View.
Each layer depends only on the layers underneath, and only on the layer directly underneath. No abstraction from an underlying layer crosses more than one seam.
If I change my controller api, the query contracts do not have to change. If I add behavior to my query contracts, the controller gets that behavior without modifications to the existing code. If I change the implementation of the query command, the controller does not have to change. Each layer is protected from changes in the other layers by layer-specific contracts and explicit management of the seams between layers.
About this series
I’m going to be writing a series of posts in which I build a simple application from the ground up. I want to express the Onion Architecture as well as my favorite implementation patterns. I think it will be a good exercise for me to explicitly identify the reasons for many of the things I do in my daily work.
I will host the application on github so you’ll be able to review the full source code as I work.
In my next post in this series, I’ll explain the demo project and start describing the innermost layer of the onion: The domain model.
One thought on “Onion Architecture: An Opinionated Approach, Part 1”
-
Pingback: Onion Architecture: An Opinionated Approach Part 2, Anemic Data Models « IExtendable