Onion Architecture in .NET Core

Posted by

I recently built a small ASP.NET Core API that shows my preferred approach to testing cloud apps. I designed the application using the Onion Architecture, which I’d been wanting to get more familiar with in the context of a real-world application. This article chronicles the journey of building that application.

The business domain of the app gravitates around the pricing and purchase of items available at the supermarket. The business rules are based on the popular CodeKata of the same name. To keep things simple, I only implemented the pricing rules for items with a cost that varies based on quantity or weight.

The code samples are available in their entirety on GitHub.

What is Onion Architecture?

The name Onion Architecture was originally coined by Jeff Palermo, but has since gone under lots of other pseudonyms. Ports and Adapters, Hexagonal Architecture and Clean Architecture are all different names for effectively 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 contains the Application Interfaces. The contracts defined here orchestrate the use of the Domain Services and are the entry point for all business logic. Generally speaking, the Application Interfaces orchestrate an operation on the domain entities and services. The ICheckoutService in the example above does just that.

This layer is also where you would define any interfaces that aren’t directly related to the domain but will be needed by higher layers, such as an abstraction to send emails.

Application interface methods will typically be called from a controller in the WebApi Infrastructure layer to perform an action on its behalf.

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.

There isn’t a huge difference between Onion Architecture and n-tier. But I like the way Onion Architecture puts business logic at the centre of the application. It makes it easier for a developer to pick up what an application is doing. And isn’t writing quickly understandable code what it’s all about?

2 comments

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 )

Twitter picture

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

Facebook photo

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

Connecting to %s