In my last few posts I have hammered a lot on simplicity in software. In my first post (Simplicity in software) I explained what the difference is between easy and simple code (or hard and complex code). On the basis that introducing frameworks and libraries increases complexity, the following posts then touched on a few common types of frameworks that I feel are often overused:
- Aspect oriented programming versus composition in Simplify code by using composition
- Hand-written SQL or Micro-ORM’s versus ORM’s in How to ditch your ORM
- DI containers versus Pure DI in How to use Pure DI
- Partial application versus over-usage of interfaces in Using partial application for dependency injection
In this post I will reiterate these patterns by combining them all into a sample application. The application can be found on GitHub.
Disclaimer: the sample application serves to demonstrate patterns and practices. It is not intended to be fully tested nor complete. I do not pretend for this architecture to be the golden standard for development. I’m merely using it to show examples and alternative solutions to common and recurring problems I have faced. If you have any ideas or suggestions, feel free to comment, fork, throw a pull-request or engage with me on twitter (@kennethtruyers). The discovery and learning part is what interests me.
The sample app
The application is a rest API built with ASP.NET Web API (primarily because it saved me from writing a UI) on top of a SQL database. It allows you to create a user, update its profile and keep track of a list of friends. They key patterns that I want to illustrate are these:
- The separation of the read and write model.
- The use of an event-bus, query bus and command bus to interact between the endpoints, domain and the database.
- The use of partial application to do dependency injection.
- The use of composition to avoid attribute based programming.
This is a short overview of the app structure:
- API: Contains the controllers. The controllers are very lightweight. They are just declarations of endpoints and dispatch either a query or a command
- App_start: Contains the regular start up code for a web api project and the the code that bootstraps the dependency resolution mechanism
- Application: The application is the part that coordinates all the input we receive to either the database or the domain.
- Domain: This contain all our custom logic. Since it’s a sample app, there’s not a lot there, but in a large application, this would be the beef of the app. It raises events about changes.
- Infrastructure: Contains PetaPoco as a Micro-ORM to talk to the database and a logging component to demonstrate composition
- ReadModel: Flat DTO’s that serve as output for query operations
- SimpleCode.Tests: Contains tests for the domain logic inside the User class
I’m not going to go over a the code, since most of the patterns are discussed in the previous posts and if you want you can have look at the sample application. I do want to highlight the main traits this app has.
Separation of read and write model
On the write-side, because our domain is free from read concerns, it allows our model to be very expressive. That means we don’t need public properties, but can expose only behavior. On the reading side, we can read directly from the database and project the data immediately into the read-model. This means that we don’t need any mapping between viewmodels and domain entities. A tool like AutoMapper is simply not necessary with this structure.
Event, Query and Command bus
The different buses are what composes the application while at the same decoupling components from each other. It allows for a clean separation between domain, database and the public endpoints. An extra benefit is that our application becomes scalable. The sample app consists of in-memory buses, but you could easily create a bus that communicates over a remote channel, thereby splitting up the read, write and endpoints into different physical tiers.
Partial Application and dependency injection
Because of the usage of partial application and pure DI as a mechanism for composing the application, we don’t need a DI-container. The entire application is configured inside the BootsTrap-method (WebApiConfig-class). A view on this method will tell you exactly how the application is composed. It frees us from learning the details about a specific container, increases readability and reduces complexity. The bootstrap-method can grow quickly once you start adding code to this project. But that’s OK, because that code has value, it tells you how the app works. It’s also simple code, because it’s plain object composition.
Another part of the equation is that we didn’t need to interfere with the way Controllers are constructed. Because the buses have public static methods, we can use them from anywhere. It allows our controllers to be mere declarations of endpoints (that are not at all served by unit tests) and place the dependency graphs one level lower. (see testability for more details about the ‘static’-ness of the buses).
Because we are using partial application, we can wrap different types of functions around other functions. This allows us to create cross-cutting concerns as separate elements and then apply them in the composition root (Bootstrap-method). An example of this can be seen where we wrap a logging-function around a handler function in the composition-root. This frees us of the burden of an AOP-framework and scattered attributes through our code base.
A concern that some people may have is that the Command, Query and Event bus are static classes with static methods and that they are not replaceable in tests. But that’s not an issue, we can actually just use them in our tests as well. If you look at the user test, you’ll see that we fire a few commands at the handler and then check whether we receive the correct events on the bus. Because commands and events are really domain concepts and should be tied closely to the problem domain, it makes for a nice way of testing. It let’s you write tests that say: If I create a user (command) then a user was created (event), which correlate closely with the domain language. We’re issuing commands and then testing whether that command had the correct reaction of the system (events).
The combination of these patterns allows us to create an architecture without depending on external tools. Because external tools introduce complexity, such an architecture yields simpler code. I want to stress again that when I say simple, I do not mean easy. The code this app showcases is not really easy (easy as in, a junior could write this first day out of school). This code is not easy, but it is simple in the fact that there is no magic involved by frameworks (generating code at runtime, dynamically resolving factories, reflecting over properties to map from one type to another, …). If a bug would show up in this code, it will definitely be a visible one and troubleshooting will be rather simple.
This code is also not an endpoint, there are still things that I don’t like about it as well:
- The bootstrap method is not testable, that means that you could encounter an error at runtime where a handler is not resolved. For commands and events, this could be done through a unit test, by reflecting over all classes that implement ICommand or IEvent and trying to resolve a handler (although it’s rather ugly). For the query handlers it’s more difficult as you could theoretically dispatch any combination of a requested result by any type of query. I’m not sure how this could be made testable without an insane amount of reflection code (any ideas here are more than welcome).
- There’s still quite a bit of ugly syntax overhead. If you look at the command handlers, they’re signature is public static readonly Action
NameOfHandler = command =>. That’s a lot of words to say you’re declaring an Action , but I guess that is just how C# syntax works. F# would probably offer a huge improvement in this area.
As I said, I look at this code more as an exploration of interesting patterns in order to reduce complexity. If you have any feedback, suggestions or improvements, feel free to contact me. Happy to discuss, defend my opinions and/or admit I’m wrong.