Simplifying querying in a typical ASP.NET n-layer architecture
I've been working on a pretty big project for a couple of months now, and I'm at a point where the current software architecture doesn't scale well enough to fit my needs. With scaling I don't mean performance, but rather the complexity of the source code (which in turn impacts maintainability). Using a very basic form of CQRS I've simplified the query part, which reduces the need for DTO classes and brings querying data to the front of the application. In this post I will explain the steps I've taken and discuss the results.
A 'typical' ASP.NET n-layer architecture
'Typical', yea, so what does that mean? Most solutions I come across have their application split up into distinct layers of related functionality, stacked vertically on top of each other. The functionality within each layer is related by a common role or responsibility. This provides a strong seperation of concerns.
Most of the time a strict interaction is enforced between the layers. This means a layer may only use the abstraction of the layer beneath it.
Note that there is nothing wrong with this architecture. It's easy to understand and feels very natural. Everything has a place; it's organized. And as far software architecture goes: if it works for you, it works. Who am I to judge ;-) But this architecture does come with its cons.
What's the problem?
Imagine the application growing to, let's say, 60 entities. Alongside it now also supports 200 separate tasks. From querying products under a specific category to registering a new customer. All these tasks need to be expressed in code through every layer. That means creating intermediate DTO's, near-empty handlers, mappings, DI-container registrations and interface definitions. In short:
Layers add additional boilerplate code to do minimal tasks.
Now I'm not saying layers are totally unnecessary, but for the majority of the tasks they probably are. A simple task; retrieve the first and last name of a client with id 391. This can be expressed in very little code. Yet it takes 5 seperate files to adjust, add and maintain to get to this little piece of code. That seems a bit overkill, doesn't it?
Beside the obvious downsides of being more difficult to maintain, the context also gets lost when working through many files and verbose code. Horizontal reuse of components seems like a good idea, but introduces even more context loss. And don't even get me started with attached or detached entities if you use an ORM like EF.
Separating queries from commands
First of all a small disclaimer: there's nothing new here or stuff I thought up all by myself. You can find tons of info about CQRS or something alike on the internet. I merely provide my experience and how I applied part of this theory on a real life project.
Let's assume roughly half of the operations of an ASP.NET application are tasks where you want something to happen (like kicking a workflow one state further). The other half is querying data for your front-end. 9 out of 10 of those query tasks are simple queries, with probably one or two where-filter, pagination and an order-by. I don't want queries to be buried deep within the business layer. They don't need validation, consistency checks, transactions or custom error handling. I prefer having queries very close to the entry-point of the application, because this is where the context is most clear to me.
CQRS dictates splitting up the conceptual model of your application into two separate models: the Command- and Query Part. As for the rest it does not tell us how. People may even use separate applications and databases for each stack. I'm not going that extreme, I'm just borrowing some concepts :-) Here's my attempt:
This architecture is a bit different from the one you saw before, but copes with the same problems. I've split up the business layer into two distinct parts: one for querying, one for commands. The presentation layer is still one ASP.NET application, but now uses the QueryModel for actions which require querying. The DAL stays exactly the same, except it now provides an implementation for the interfaces of the QueryModel. Luckily this can be extracted real easy from what's already in place.
Inside the QueryModel
Most CQRS implementations use a separate data model for the querying part. I wasn't looking for something groundbreaking, just something that would simplify querying data. So what I did was simply providing generic read-only repositories to the presentation layer. Time for some code!
Next the implementation in the DAL using EF's DbContext (mines called DataContext):
IQueryable Awesomeness
As you can see in the code examples above the return type is mostly an IQueryable<TEntity>. This IQueryable interface is freakin' awesome, and my advice is to use and embrace it as your query currency. You can of course write your own query infrastructure, but IQueryable is widely supported by allot of Microsoft products like EF.
By deferring execution we are able to provide the query criteria way up front in the application. With projection it's also possible to select only what's necessary for the ViewModel/DTO. Generating and executing the query is done when the result needs to be materialized.
Simple and efficient. But we can make it more elegant. Complex queries can still bloat the controller, which will decrease overview and maintainability. We can create a separate ClientRepository in the QueryModel, extending the existing IReadOnlyRepository<Client>. Here we can provide methods executing more complex queries. These still can return IQueryable of course.
Another possibility is using IQueryableExtensions. By extending IQueryable<TEntity> we can add bits of isolated query-logic, which are highly reusable and testable. A great side effect is that the query using these extensions can be defined in a more natural language representing the business.
Now we can improve the Find method on the ClientController from before into:
Verdict
A couple of weeks ago I started out refactoring allot of querying business components into the new QueryModel. While doing this I've deleted allot more code than I've wrote, which is because of all the boilerplate code I got rid of.
It feels like the application gained allot more flexibility, especially for those tiny 'we-need-an-extra-field-here'-requests. Finding out what a controller query method does is now simply reading every query part, which is defined in a more natural language instead of (nested(ex|pre&&ions)).
I am aware that this is not CQRS at it purest. Given a pretty large application which already 'just works', mine didn't need a separate model. It did need simplification of the business layer. By splitting up querying from commanding I've reduced complexity in the old business layer by 50%. A new query part got added with roughly 50% of that other complexity, but if everything can use everything (NxN), N+N is still less complex :-)