Buy Me a Coffee

淺談 Clean Architecture with ASP.NET Core 8

Clean Architecture 是一種軟體架構設計模式,旨在將應用程式的關注點明確地分離,使程式碼更具彈性、可測試性和可維護性。在本文中,我們將探討如何在 ASP.NET Core 8 中實作 Clean Architecture,並討論其優點和實踐方式。

Youtube影片來源

什麼是 Clean Architecture?

Clean Architecture 也被稱為 Onion Architecture、Hexagonal Architecture 或 Ports and Adapters。它的核心思想是將應用程式的核心業務邏輯與外部界面(如 UI、資料庫等)分離,使業務邏輯不受外部界面的影響。這種分離可以透過依賴反轉原則(Dependency Inversion Principle)來實現,將外部界面作為實作(Implementation),而業務邏輯作為抽象(Abstraction)。

在 Clean Architecture 中,應用程式被分為以下幾個層次:

層次描述
Domain Model包含應用程式的核心業務邏輯、實體(Entities)、值物件(Value Objects)和領域服務(Domain Services)。這是最內層,不依賴任何外部介面。
Application Layer定義應用程式的使用案例(Use Cases),包括命令(Commands)、查詢(Queries)和 DTO(Data Transfer Objects)。這層可以依賴 Domain Model,但不依賴外部介面。
Infrastructure Layer實作外部介面,如資料存取(Repository)、API 用戶端、檔案系統存取等。這層可以依賴 Application Layer 和 Domain Model。
Presentation Layer處理使用者介面(UI),如 Web 應用程式、CLI 等。這是最外層,可以依賴所有內層。

透過這種分層架構,Clean Architecture 使得應用程式的核心業務邏輯與外部界面完全解耦,提高了可測試性和可維護性。此外,它也符合開放/封閉原則(Open/Closed Principle),因為新功能的添加只需要在相應的層次中實作,而不需要修改其他層次的程式碼。

在 ASP.NET Core 8 中實作 Clean Architecture

ASP.NET Core 8 提供了一個方便的範本,可以快速建立符合 Clean Architecture 的解決方案。你只需要在終端機執行以下命令:

dotnet new install Microsoft.DotNet.Web.ProjectTemplates.8.0::8.0.0-preview.2.23128.3
dotnet new console -n cleanarch

這將建立一個包含以下專案的解決方案:

  • Domain Model:包含實體、值物件、領域服務等核心業務邏輯。
  • Application:定義使用案例,包括命令、查詢和 DTO。
  • Infrastructure:實作資料存取、外部服務等。
  • WebUI:ASP.NET Core Web 應用程式。
  • Tests:單元測試和整合測試專案。

在 Domain Model 專案中,你可以定義實體、值物件和領域服務。例如,一個簡單的產品實體可能如下所示:

public class Product : Entity
{
    public string Name { get; private set; }
    public decimal Price { get; private set; }

    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }
}

在 Application 專案中,你可以定義使用案例,例如創建新產品的命令和查詢:

// Command
public record CreateProductCommand(string Name, decimal Price) : IRequest<Guid>;

public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Guid>
{
    private readonly IRepository<Product> _productRepository;

    public CreateProductCommandHandler(IRepository<Product> productRepository)
    {
        _productRepository = productRepository;
    }

    public async Task<Guid> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        var product = new Product(request.Name, request.Price);
        _productRepository.Add(product);
        await _productRepository.SaveChangesAsync(cancellationToken);
        return product.Id;
    }
}

// Query
public record GetProductQuery(Guid Id) : IRequest<ProductDto>;

public class GetProductQueryHandler : IRequestHandler<GetProductQuery, ProductDto>
{
    private readonly IRepository<Product> _productRepository;

    public GetProductQueryHandler(IRepository<Product> productRepository)
    {
        _productRepository = productRepository;
    }

    public async Task<ProductDto> Handle(GetProductQuery request, CancellationToken cancellationToken)
    {
        var product = await _productRepository.GetByIdAsync(request.Id, cancellationToken);
        return new ProductDto(product.Id, product.Name, product.Price);
    }
}

在 Infrastructure 專案中,你可以實作資料存取層,例如使用 Entity Framework Core:

public class ProductRepository : IRepository<Product>
{
    private readonly ApplicationDbContext _dbContext;

    public ProductRepository(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void Add(Product product)
    {
        _dbContext.Products.Add(product);
    }

    public async Task<Product> GetByIdAsync(Guid id, CancellationToken cancellationToken)
    {
        return await _dbContext.Products.FindAsync(id, cancellationToken);
    }

    // 其他存放庫方法...
}

最後,在 WebUI 專案中,你可以實作 ASP.NET Core Web 應用程式,包括控制器、視圖等。例如,一個產品控制器可能如下所示:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ProductsController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<ActionResult<Guid>> Create(CreateProductCommand command)
    {
        return await _mediator.Send(command);
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<ProductDto>> Get(Guid id)
    {
        return await _mediator.Send(new GetProductQuery(id));
    }
}

透過這種架構,你可以清晰地分離應用程式的關注點,提高可測試性和可維護性。核心業務邏輯被封裝在 Domain Model 中,與外部介面完全解耦。而當需要新增功能時,只需要在相應的層次中實作,而不需要修改其他層次的程式碼,符合開放/封閉原則。

優點與實踐

實作 Clean Architecture 帶來了以下幾個主要優點:

  1. 可測試性提高:由於核心業務邏輯與外部介面分離,可以更輕鬆地對業務邏輯進行單元測試,而無需啟動整個應用程式或模擬外部依賴項。

  2. 更好的可維護性:通過將關注點明確分離,程式碼變得更加模組化和可理解。當需要修改或添加新功能時,只需要在相應的層次中進行更改,而不會影響其他層次的代碼。

  3. 更高的靈活性:由於外部介面是通過抽象介面與核心業務邏輯交互,因此可以輕鬆地更換或添加新的外部介面實現,而不需要修改核心業務邏輯。

  4. 更好的可伸縮性:由於關注點的明確分離,可以更輕鬆地在不同的團隊或個人之間分配工作,從而提高開發效率。

在實踐 Clean Architecture 時,需要注意以下幾點:

  1. 嚴格遵循依賴規則:外層不能直接依賴內層,只能通過接口進行交互。違反此原則將破壞整個架構的可測試性和可維護性。

  2. 適當使用設計模式:如 CQRS(Command Query Responsibility Segregation)、Domain Events、Specification 等設計模式,可以進一步提高架構的靈活性和可維護性。

  3. 保持業務邏輯的純淨性:Domain Model 中的業務邏輯應該與任何外部依賴項完全隔離,以保持其純淨性和可測試性。

  4. 適當抽象和封裝:在每個層次中,適當地抽象和封裝代碼,以減少重複代碼和提高可維護性。

  5. 充分利用依賴注入:在整個應用程式中廣泛使用依賴注入,以降低耦合度並提高可測試性。

總的來說,Clean Architecture 為構建可靠、可維護和可擴展的應用程式提供了一種有力的架構模式。通過適當的實踐,可以顯著提高應用程式的質量和開發效率。