ASP.NET MVC: Make Your Application an Onion!
When building your application from scratch, how do you arrange your libraries in your solution? Today, we'll talk about how to break your project into reusable components to make it extendable for future projects using an onion architecture.
Recently, I had a reader ask me about how do I layout my projects when building websites. Do you combine all of your code into one assembly or do you make everything so granular that it's easy to include in other projects?
Do you create the proverbial "big ball of mud" (don't laugh...I've seen professional consultants do this) or do you make your system easily approachable and easy to read...WITHOUT documentation?
This post will not only show you how your finished solution should look, but I will take you step-by-step through the refactoring of an existing application into reusable components.
A brief history
Back in 1997-ish, I was writing software for an insurance company and thought that there had to be more to it than just banging at the keyboard making applications. Outside the company, I decided to further my knowledge of coding and do some research on how to build applications properly.
I finally came across a paper from Scott Ambler of Ambysoft titled "The Design Of A Robust Persistence Layer for Relational Databases" (PDF). I read it numerous times and realized that this was the way real applications were built. After reading additional material, I started to realize that if you logically partition your application into modules, you start making reusable components for future applications instead of re-inventing the wheel.
ALWAYS Leverage Your Existing Code!
As always, I will be using our standard FaqController project that we used a while back located here. It has been updated since because of more recent articles. As you can see, the application isn't broken into any parts.
This is considered to be our "Big Ball Of Mud." Even if we tried, we couldn't use our models in any other application. They aren't in their own assembly. It isn't a layer in the onion architecture. Hell, it's not even an onion.
Let's refactor the application and discuss the layers as we go.
First, we need the core of our "onion." This is the Business Layer and contains all of the business logic for our entities. This is the layer that tells us what we can and can't do with the application.
Let's create a new Class Project and call this project BusinessLayer.
Your business layer will contain the logic as to whether fields are required, is there a range of numbers that a property can accept, whether shipping is allowed based on an address, etc. If you are using it in your application, there will be a class for it.
We will move our GeneratedClasses and Classes and/or Models folder into this BusinessLayer project. The GeneratedClasses folder is from our Entity Framework generating our classes for us from this post. But if you notice in our project, we just have a Models folder so we can copy that over as well, BUT KEEP THE ViewModels in the ThinController project under the Models directory.
Why? Because ViewModels are attached to the UI. They are meant to be data containers for your MVC Views. Keep them in the UI Layer.
Also, any additional classes for the app are placed into the Classes or Models folder.
ReSharper Tip: Right-click on your models folder in the BusinessLayer project and select "Refactor..." then "Adjust Namespaces" to modify your namespaces to your new folder.
So now we have this solution layout.
The next class project we need is the Data Layer project. This is the layer that allows database access and builds the objects from your BusinessLayer. These types of classes will contain:
- Repositories (and corresponding Interfaces)
- Unit Of Work (to combine the repositories together)
Now we move the repositories into the DataLayer project. Here is what we have now.
Any questions? Good! NEXT SECTION! ;-)
In this next section (The Services Layer), I've heard a number of developers argue about creating/removing a Services Layer that sits on top of the Data Layer. This is more of a preference in my opinion. The Services layer abstracts the Data Layer even further making it even easier to retrieve data using one call and letting the Data Layer perform the heavy lifting.
Some developers use this layer as either a:
- Validation - Use this to validate user input and either send the data to the data layer for saving or return it to the UI
- Caching - Since this is a higher level abstraction of our data layer, it only makes sense to add a caching layer on top similar to what I described here.
- Aggregation - Make this a simple abstraction with higher-level calls to the data layer.
As you can see, when you get higher up the chain (or getting further outside the onion), you have additional options for your layers. You could even have all three layer options as separate class libraries in your solution.
Some developers WANT this layer, some want it REMOVED because they feel they don't need the extra layer of complexity.
Do you absolutely NEED this layer? I know this will sound like a cop out, but "It depends..." How far should you take your application? I have seen developers perform HUGE abstractions of an application to where there are 40 (yes, 40) class projects to make the application run. That can get a little crazy and intimidating for newbies to the company.
So I leave this layer as an exercise to the reader. If you wish to add a "Service" layer, by all means. Just make sure you know how many layers you are creating and which layer calls which layer.
Web Services (optional)
If you haven't figured this out yet, the Web Services layer is optional and something you can provide from your application to your API users.
At most companies I've consulted at, they either created their own or bought a Middleware/Web Services host that can be extended through WCF or extend the Middleware API to their users.
Again, I hate to cop out, but my answer to this is...it depends. It depends on whether you absolutely need this layer. As mentioned by Marc Andreessen, software is eating the world and web APIs are assisting in that feast. If your website doesn't have an API, most developers think you are behind the times and do not have a vision for your product to become scalable.
For this particular example, the Web Services Layer would become another Class Library Project in your Solution that would access either the Services Layer (preferred) or Data Layer.
And finally, our UI Layer. This layer existed when we started. The UI Layer is your ASP.NET MVC application and references the Services layer which, in turn, uses the Data Layer, which in turn, uses the Business Entities.
If you decided to add the Web Service Layer, I think we can all agree that it would be in it's own "UI Layer" that would access the Service Layer or Data Layer and not even call the UI Layer.
When we first started, we had a big ball of mud for an application. Now we have a modular application that you can use the entities assembly in another application and not worry about the inner layer of your onion being exposed.
Some developers experience circular references in their code. If you've done everything as I've described above in your own application, the nature of this architecture would eliminate the possibility of ANY circular references. You are essentially writing code that protects you from yourself.
Each layer that you create is built on top of an existing layer that doesn't rely on any other layer but it's own or the layers contained below it...hence the Onion Architecture.
Did this make sense? Do you partition your application differently? Post your comments below.