Hexagonal Architecture in .NET (C#) API Development

Introduction

In the world of software development, architects and engineers constantly strive to create systems that are not only efficient but also maintainable and adaptable. One architectural pattern that has gained popularity in recent years for achieving these goals is the Hexagonal Architecture, also known as Ports and Adapters. In this article, we will explore the principles of Hexagonal Architecture and demonstrate how to apply them to .NET (C#) API development with practical examples.

Understanding Hexagonal Architecture

Hexagonal Architecture, introduced by Alistair Cockburn, is an architectural pattern that promotes the separation of concerns within a software system. It helps to create highly modular and testable applications by organizing code into layers or hexagons, each with a distinct responsibility. The primary idea is to isolate the core business logic (the inside) from the external dependencies (the outside) through well-defined interfaces or ports.

Here are the key components of Hexagonal Architecture.

  1. Core: This is the innermost layer, which contains the application's business logic, domain entities, and use cases. It should be free from any external dependencies and frameworks.
  2. Ports: Ports are the interfaces or contracts defined in the Core layer that specify the interactions required by the application. These can be considered as entry and exit points for data and functionality.
  3. Adapters: Adapters are the implementations of the ports. They bridge the gap between the Core and external systems, such as databases, web services, or user interfaces. Adapters are responsible for translating external requests into actions that the Core can understand and vice versa.

Setting Up a .NET API Project

Let's dive into practical implementation by creating a .NET API project using Hexagonal Architecture principles.

Step 1. Create a New Solution

Open Visual Studio or your preferred IDE and create a new solution for your project. In this example, we'll name it "HexagonalApiDemo."

Step 2. Define the Core

Inside your solution, create a new project for the Core layer. This project should contain your business logic, domain entities, and use cases. For instance, let's create a simple e-commerce application with a "Product" entity and a "ProductService" class.

// Core/Entities/Product.cs
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// Core/UseCases/ProductService.cs
public class ProductService
{
    public IEnumerable<Product> GetProducts()
    {
        // Logic to fetch products from the database or any other source
        // Return a list of products
    }
}

Step 3. Define Ports

In the Core project, define interfaces or ports that represent the interactions required by your application. These can include repositories, services, or any external dependencies.

// Core/Ports/IProductRepository.cs
public interface IProductRepository
{
    IEnumerable<Product> GetAll();
}

// Core/Ports/IEmailService.cs
public interface IEmailService
{
    void SendOrderConfirmationEmail(Order order);
}

Step 4. Implement Adapters

Now, create separate projects for the Adapters layer. In these projects, you will implement the interfaces defined in the Core. You can have different adapters for various external systems, such as a database adapter, a web API adapter, etc.

// Adapters/Database/ProductRepository.cs
public class ProductRepository : IProductRepository
{
    public IEnumerable<Product> GetAll()
    {
        // Implement database retrieval logic
    }
}

// Adapters/Email/EmailService.cs
public class EmailService : IEmailService
{
    public void SendOrderConfirmationEmail(Order order)
    {
        // Implement email sending logic
    }
}

Step 5. Wiring it Together

Finally, in your API project, wire everything together. Create controllers or entry points that use the Core's use cases and inject the appropriate adapters.

// API/Controllers/ProductController.cs
[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
    private readonly ProductService _productService;
    
    public ProductController(ProductService productService)
    {
        _productService = productService;
    }
    
    [HttpGet]
    public ActionResult<IEnumerable<Product>> GetProducts()
    {
        var products = _productService.GetProducts();
        return Ok(products);
    }
}

Ensure that your API project references both the Core and Adapters projects.

Conclusion

Hexagonal Architecture offers a robust way to structure your .NET API projects, making them more maintainable, testable, and adaptable to changes. By separating the Core from external dependencies using ports and adapters, you can achieve a clean and flexible architecture that supports future growth and evolution.

In this article, we've provided a high-level overview and practical example of Hexagonal Architecture in .NET (C#) API development. Remember that this is just the beginning, and you can further extend your project by adding more use cases, adapters, and features to meet your application's requirements while maintaining a clean and organized codebase.