Onion Architecture in .NET Core

Posted by

I recently built a small .NET Core API that shows how a modern cloud application can be thoroughly tested. Although the project was for demo purposes only, I took the decision to model it like a real application so that it was more relatable to real-world projects.

It’s for that reason that I decided to implement the Onion Architecture, which I first became familiar with a few years ago. I’ve used it in a few projects since and found that it’s a great way to clearly separate the business rules of an application from its implementation details.

The code samples in this article are all available in their entirety on GitHub.

What is Onion Architecture?

Onion architecture goes under lots of pseudonyms. Ports and Adapters, Hexagonal Architecture and Clean Architecture are all effectively different names for the same thing. They represent a way to structure the code that clearly separates the domain of the problem from the underlying technologies that implement the solution.

I prefer the name onion architecture because of its similarity to the cross-section of a real onion. This metaphorical onion has multiple layers that can be pealed away to get to the core domain of the application. There can be as many layers as needed, but there are generally up to three:

Onion ArchitectureOnion Architecture Solution Explorer

I’ve included the Solution Explorer view to see the relationship between the logical and physical layers side-by-side. I’ll explain each of the layers by working my way from the inside out.

Domain Layer

At the core of the onion, you’ll find the Domain Layer, which is composed of Entities and Interfaces. Together they represent the core business logic of the application.

The Supermarket.Core project has an Entities folder, which is the physical manifestation of the Domain Entities. Any and all domain entities should be in this folder.

The Contracts folder represents the Domain Interfaces. These interfaces act directly or indirectly on Domain Entities to perform business logic. The Contracts folder also includes the interfaces for the Infrastructure Layer. This lets the Domain Entities and Interfaces exploit the underlying Infrastructure of the application without knowing their implementation.

Let’s look at a sample domain entity. The CartItem class in the Supermarket.Core project is such a class. It happens to be an abstract class that has two implementations, QuantityBasedItem and WeightBasedItem, which are also considered Domain Entities.

    public class QuantityBasedItem : CartItem
    {
        public int Quantity { get; set; }

        public QuantityBasedItem(string upc, string name, int quantity)
        {
            Upc = upc;
            Name = name;
            Quantity = quantity;
        }

        public override double Cost()
        {
            // For simplicity, all items are the same cost.
            return 3d;
        }

        public override double Discount()
        {
            // Same here, applying a global 7% discount
            var discountPercent = 0.07d;
            return Cost() * discountPercent;
        }
    }

In the above class, the cost and discount are calculated within the entity itself. Onion architecture takes inspiration from Domain-Driven design, which means that a domain entity should be rich in business logic to make the domain as clear as can be.

In some cases though, it might make more sense to use a Domain Interface to encapsulate business logic outside of the entity. The hard-coded discountPercent above could instead be calculated by a Domain Interface called IDiscountService, which takes the entity as a parameter and does a calculation based on its properties, returning a discount percentage.

public override double Discount()
{
   return Cost() * _discountService.Calculate(this);
}

The Domain Interfaces are implemented in the Supermarket.Core project. Since a Domain Interface acts on an Entity, it should be considered pure business logic.

A key tenet of Onion Architecture is that dependencies flow inwards or laterally toward the core. Concretely this means a project that is in an outer layer can refer to a project in an inner layer or the same layer. A project should never refer to a project that is farther from the core than itself.

Consequently, the domain layer doesn’t have any dependencies on NuGet packages because it is used purely for business logic. It’s okay to use some NuGet packages in the core but it should be kept to the strict minimum.

Application Interfaces Layer

The second layer of the onion is Application Interfaces. They orchestrate the use of the Domain Entities and Domain Services and are the entry point for all business logic. An application interface is generally needed when multiple actions with many conditionals need to be applied upon the domain entities and interfaces.

This layer isn’t necessary in simple cases such as the Supermarket API. For that reason, I don’t focus on it.

Infrastructure Layer

The final layer is the Infrastructure layer. Here you’ll find everything that exposes the application to the outside world as well all the dependencies that it relies upon, such as the WebAPI and projects that interact with a database, external API or other service.

The Infrastructure Layer is represented by three separate projects: Supermarket.WebAPI, Supermarket.Infrastructure.Http.Clients and Supermarket.Infrastructure.Data. Each of these projects hosts the implementations of the interfaces defined in Supermarket.Core. As always, the dependencies flow towards the centre or laterally. The lateral references enable the WebAPI layer to bootstrap all the dependencies used in the application.

Let’s look at what an Infrastructure dependency looks like.

public interface IGeoLocationClient
{
    Task GetDetails(string postalCode);
}

The contract for IGeoLocation doesn’t mention any details of the underlying infrastructure. The actual implementation, within Supermarket.Infrastructure.Http.Clients, uses the Google Maps API but you wouldn’t know it from looking at the interface. This is done on purpose since the Domain Entities and Interfaces don’t need to know anything about the implementation details. The details of the Maps API call are hidden by making Google’s API contract internal to Supermarket.Infrastructure.Http.Clients.

Supermarket.Http.Utilities is named differently from the other projects. That’s because it’s not part of the onion per se. Its classes aren’t domain specific and are generic enough that they can be used in many different contexts. In a real world application you’d probably want the project to be part of a NuGet package that it can be used where needed.

And that’s how you peel an onion

By now you’ll have noticed that my onion is slightly different from other explanations that can be found online. Some of this is my own interpretation of the rules, and some of it is my pragmatic way of implementing it.

I’ve come to prefer Onion Architecture over n-tier mainly because of how it puts business rules and logic at the centre of an application. It makes it easier for a developer to understand what the application is doing as well as making for a more maintainable and scalable software architecture.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s