Using Layering to Improve Your Project’s Underlying Design

A guide to structuring your project as several smaller, more modular parts

Mike Zrimsek
Sep 10, 2016 · 17 min read

Background

Recently I made a post detailing how to set up a .NET Core server with a database. Now I want to expand upon that post (as I will again later in other aspects) to talk about ways to improve that project, namely with the onion architecture philosophy.

The purpose of this post is to both walk through how I updated my existing project to be more compartmentalized, but also why I made the decisions I did. Hopefully by the end I will have convinced you that this project structure is demonstrably better.


The Problem

One of the goals when building a project that will be larger than just a handful of files is to try to make your classes as reusable as possible — you do this by writing things well, but also by making sure classes are as loosely coupled together as possible (think interfaces over concrete classes).

It turns out you can apply this same idea to how you build out your projects. Being able to swap out databases or UIs without having to do a complete project rewrite sounds awesome right? I thought so too, so I’ve been spending a lot of time lately working through how to implement this in the boilerplate project I’ve been working on (all in the name of having a good place to start in some projects I know I’m going to be starting here in a month’s time).


Splitting Up The Project

My code will be starting from where that post I mentioned at the start, so if you want to follow along a little better it might be worth doing a quick once over of that post. With that in mind, this is the project structure I’m starting with.

Notice that we have our repositories, controllers, database, and view models all clumped together. The goal for this post is to move to a structure where our view models aren’t dependent on our database models, our controllers aren’t dependent on concrete repositories, etc.

First we’re going to make two new projects called UI and Integration.EntityFramework and initialize a new project in each. The most tedious part of this process will be making sure all the new namespaces match up (though if you’re using an actual IDE like Visual Studio this should be much less of a problem), so make sure to spend some extra time fixing those file by file — I’m going to be omitting this part from the guide, hoping you’ll take care of this as we go. And as always, apologies in advance for any code formatting that gets butchered into weird multi-line messes.

Now that we have our new projects created, we should also add a global.json file that enumerates all the projects found in the root directory.

The UI project will hold things like our controllers, views, and view models. It will also become the entry point for the application, so all server configuration code will also be moving here.

The EntityFramework project will hold things like our repositories and database models.

While the Core project will lose the large majority of its contents, it will also gain some new things, namely all of our project’s interfaces and domain models. By the time we’re done, these new things will be the heart (or core) of our application with the new projects talking to each other as little as possible.

The Core Project

Since the Core project is the base of our application, it makes sense to start here. Add UserDomainModel.cs to the Models folder.

Whether all these fields are necessary is arguable, but having them and then not needing them is almost definitely going to eliminate more headaches than it is going to cause. Remember that the domain model will not user facing, so we still have the opportunity to restrict what data we reveal when converting to a view model later. Then make an Interfaces folder and add IUserRepository.cs to it — this will be the blueprint for our UserRepository.

Notice that the interface only operates on the UserDomainModel. All business logic operations should be done using this model — when saving to the database we will map from a domain model to a database model, and when retrieving data from the database we will map from a database model to a domain model. Likewise when displaying data we will map from a domain model to a view model.

The Entity Framework Project

We now have the base for our other projects. Let us proceed with filling out the EntityFramework project next. Our project is going a project.json that looks like the following.

Because this is the EntityFramework project it makes sense that the main dependencies are, well, Entity Framework. Take note of the frameworks section and where we list dependencies. The new entry here is where we’re telling this project to reference the Core project.

Create a new folder called Models and copy the UserDatabaseModel.cs and DatabaseContext.cs from Core/Models here. Before we start dealing with repositories, let’s get some infrastructure in place — we’re going to need some mappers to convert to and from the database and domain models.

Create a Mappers folder and add UserDomainModelMapper.cs to move from a database model to a domain model.

And then create UserDatabaseModelMapper.cs to move from a domain model to a database model.

These mappers might feel kind of redundant because the fields in both models happen to be the same in this case, but imagine a case where the database model has nullable fields. By mapping to a domain model be can use empty strings or zeros or whatever other convention you want to use in place of having to deal with null values (because null values are annoying and concrete types are almost always easier to work with).

Now that we have that out of the way we can copy the Repositories folder from Core and start fixing up the UserRepository. The main concern is making sure that the IUserRepository is properly implemented — our new mappers will be very helpful for this.

These updates aren’t that substantial. In the GetByEmail method, we still take in a string that we search our database with. The important difference is that we’re mapping from that database model to a domain model.

Likewise in the Save method, we’re now taking in a domain model and mapping it to a database model, then doing our logic with this new mapped model. The frustrating thing about how this method changes is that we must set each field on the tracked database object so Entity Framework knows it has been updated.

I may have mentioned this before, but previous versions of Entity Framework had an AddOrUpdate function that did exactly what the Save method is accomplishing, but it has yet to be implemented in Entity Framework Core (which I’ve learned is actually a complete rewrite for .NET Core) as of the time I am writing this article.

At this point the EntityFramework project is finished and we can move on to fixing up the UI project. Don’t try to build yet because our application is horribly crippled and will scream out in pain.

The UI Project

As with the EntityFramework project, let’s start with what the new project.json file should look like for this project.

Here notice we have dependencies on both the Core and EntityFramework projects. I know I said we want these projects to only touch the Core project, but I wanted to avoid solutions to this that seemed like hand-wavy magic until I more fully understood exactly what they were doing, especially for doing dependency injection (we’ll get to this toward the end of this section).

As you’ll see shortly, the UI project is really only reliant on the EntityFramework project in a few select places that deal almost exclusively with configuring our server. The effort to update these files in the event we decide Entity Framework is awful and we want to use some other technology is pretty low, so I’m not too worried about it currently.

Since this project is the new entry point for our server, let’s deal with all that nonsense first. Move Program.cs from Core. One caveat here is that Core still needs Program.cs as an entry point because it’s exposing its internals for our other projects to utilize.

To my understanding this has something to do with all projects being treated as console applications in .NET Core. I want to look into some workarounds or solutions to this because the Program.cs files feel pretty useless in the Core and EntityFramework projects.

For now we should have a UI Program.cs that looks like this.

And a Core Program.cs with almost nothing in it.

It’s annoying, but I’ve accepted it and moved on. If and when I figure out what the fix for this I will make sure to write about it.

Since appsettings.json is a really easy update let’s deal with that now. Move it from Core. Done — Startup.cs is next.

My issue with this file is how unwieldy it will get once the project becomes sufficiently complex. There are several ways to go about fixing this — I opted to build a helper class that will hopefully help make the code more readable now and going forward.

Create a Helpers folder and create StartupHelper.cs

My goal is that each time I want to add something to the services in the future, I’ll be able to make a small ‘AddWhateverToServicesmethod that will only have to be called in Startup.cs in a single place. Regardless of how that helper changes, the Startup.cs now becomes much more readable and transparent as to what its purpose actually is.

Notice that Startup no longer has any Entity Framework dependencies and only relies on our Core project! Now if we swap out Entity Framework, we will only have to touch a single file in this project — the StartupHelper.

Now that we have our server code moved and updated, we can take care of the rest of those errors you’re likely getting when trying to build the project — the view models and controllers.

We’re going to need a new Models folder for the UserViewModel that can currently be found in Core/Models/View.

And a new Mappers folder for the UserViewModelMapper located in Core/Mappers. We’ll be adding a few updates to map from the UserDomainModel instead of the UserDatabaseModel we were previously using.

Almost there! To complete the UI project we still have to deal with the controllers and their views. Move the HomeController from Core/Controllers to a new Controllers folder, and the corresponding views from Core/Views/Home to an identical structure in this project — just make sure to update Index.cshtml so it’s using the correct model.

Take note that we’re injecting an IUserRepository instead of using the DatabaseContext — we’re no longer coupled to the EntityFramework project and rely solely on Core for the definition of what our UserRepository will be able to do.

In the case where you’re injecting several different dependencies to a controller, you should consider adding a service later that encapsulates the repositories and the functionality you’re using them for. One or two dependencies is probably fine, but anything more than that is where I’d start considering this solution. By doing this you can simplify your controller again by having a single dependency and abstracting some of your logic to a different, possibly reusable location.

The last step before we can get the project and running again is to register our newly injected dependencies — we need to let the our application know what the heck to do with this IUserRepository when it’s injected into a controller. We’ll have to add a few small updates to StartupHelper.cs.

We’re telling the application to user a UserRepository anywhere we’ve injected an IUserRepository. AddScoped is telling the application to only create one of these UserRepositories when it receives a request and then to hold on to it until it’s all done with its operations — if another request is made later, a new UserRepository will be created.

To put this work in action we need to update Startup.cs.

At this point you should be able to launch the project and get literally the exact same output as you were getting before, except this time you have the satisfaction of knowing the code behind the page is markedly better in almost every way.


Recap

We made a lot of changes to all these projects, so I’m going to go through each of them to write out what I’ve ended up with in my final project. First let’s do the one file that does not live in any of the smaller projects.

global.json

And now on to each of the individual sub-projects.

Core Project

An image of what the project ended up looking like…

And the the code for each relevant file…

IUserRepository.cs

UserDomainModel.cs

Program.cs

project.json

Integration.EntityFramework Project

An image of what the project ended up looking like…

And the code for each relevant file…

UserDatabaseModelMapper.cs

UserDomainModelMapper.cs

DatabaseContext.cs

UserDatabaseModel.cs

UserRepository.cs

Program.cs

project.json

UI Project

An image of what the project ended up looking like…

And the code for each relevant file…

HomeController.cs

StartupHelper.cs

UserViewModelMapper.cs

UserViewModel.cs

Index.cshtml

appsettings.json

Program.cs

project.json

Startup.cs


Conclusion

If you get one thing from this venture in splitting up our project into multiple smaller projects, it should be that by splitting things up everything has a concrete place to go. Having interchangeable projects is cool, but for me the biggest benefit is now that I don’t have to think about where things go. Want to add a view model? UI project. Want to add a new database model? Database project.

Admittedly this approach adds a bit more overhead because you have to add some extra code for each model that you want to add, but I think it adds enough benefit in the long run that it is worth it. On top of that, this is an architecture that we could easily apply to almost any project that has reached a certain threshold of complexity, completely agnostic to implementation language, project goals, etc.

In general the amount of the project that has to be rewritten to swap in a new technology has gone down a great deal, and we’ll easily be able to build and expand on top of this architecture in the future — so building applications with this project structure will always deliver extra value if it is implemented properly.

All the code that was covered in this post can be found in a repository on my github page.

Mike Zrimsek

Written by

Full stack developer — I do code and other fun stuff.