☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
設計模式 進階

🏗️ Clean Architecture 與專案架構

📌 為什麼需要好的架構?

好的架構就像蓋房子的設計藍圖,沒有藍圖就動工,蓋出來的房子可能牆歪、水管漏、電線亂接。軟體也一樣!

好的架構讓你的程式碼:容易理解、容易測試、容易擴充、容易維護。


🔷 傳統三層式架構(Layered Architecture)

💡 比喻

就像一棟三層樓的辦公大樓:

  • 1 樓(Presentation):接待大廳,負責接待訪客(使用者)
  • 2 樓(Business Logic):辦公區,負責處理業務
  • 3 樓(Data Access):檔案室,負責存取資料

📝 結構

┌─────────────────────────┐
│    Presentation Layer    │  ← Controller、View(面對使用者)
├─────────────────────────┤
│   Business Logic Layer   │  ← Service、商業規則(核心邏輯)
├─────────────────────────┤
│    Data Access Layer     │  ← Repository、EF Core(存取資料庫)
└─────────────────────────┘

📝 C# 範例

// === Data Access Layer(資料存取層)===
// 負責跟資料庫溝通
public class ProductRepository
{
    private readonly AppDbContext _context; // 資料庫上下文

    public ProductRepository(AppDbContext context)
    {
        _context = context; // 注入資料庫上下文
    }

    // 根據 ID 取得產品
    public async Task<Product?> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id); // 從資料庫查詢
    }

    // 取得所有產品
    public async Task<List<Product>> GetAllAsync()
    {
        return await _context.Products.ToListAsync(); // 回傳所有產品
    }
}

// === Business Logic Layer(商業邏輯層)===
// 負責處理商業規則
public class ProductService
{
    private readonly ProductRepository _repository; // 依賴資料存取層

    public ProductService(ProductRepository repository)
    {
        _repository = repository; // 注入 Repository
    }

    // 取得產品,加上商業規則(例如計算折扣)
    public async Task<ProductDto?> GetProductAsync(int id)
    {
        var product = await _repository.GetByIdAsync(id); // 從 Repository 取資料
        if (product == null) return null;                  // 找不到就回傳 null

        return new ProductDto // 轉換成 DTO 回傳
        {
            Id = product.Id,             // 產品 ID
            Name = product.Name,         // 產品名稱
            Price = product.Price,       // 原價
            FinalPrice = product.Price * 0.9m // 商業邏輯:9 折優惠
        };
    }
}

// === Presentation Layer(呈現層)===
// 負責接收請求和回傳結果
// [ApiController]
// public class ProductController : ControllerBase
// {
//     private readonly ProductService _service; // 依賴商業邏輯層
//
//     public ProductController(ProductService service)
//     {
//         _service = service; // 注入 Service
//     }
//
//     [HttpGet("{id}")]
//     public async Task<IActionResult> Get(int id)
//     {
//         var product = await _service.GetProductAsync(id); // 呼叫 Service
//         if (product == null) return NotFound();            // 找不到回傳 404
//         return Ok(product);                                // 回傳產品資料
//     }
// }

⚠️ 三層式的問題

三層式架構的依賴方向是:Presentation → Business → Data Access。這表示商業邏輯依賴資料存取層。如果要換資料庫,商業邏輯也要跟著改!


🔷 Clean Architecture(整潔架構 / 洋蔥模型)

💡 比喻

想像一顆洋蔥:最核心的那一層是最重要的商業邏輯,外面一層一層包裹著基礎設施。核心不依賴外層,外層依賴核心!

📝 四層結構

            ┌──────────────────────────────┐
            │       Presentation           │  ← API Controller、Blazor 頁面
            │   (最外層:面對使用者)        │
            ├──────────────────────────────┤
            │       Infrastructure         │  ← EF Core、外部 API、檔案系統
            │   (外層:技術實作細節)        │
            ├──────────────────────────────┤
            │       Application            │  ← Use Case、DTO、介面定義
            │   (中層:應用程式邏輯)        │
            ├──────────────────────────────┤
            │         Domain               │  ← Entity、Value Object、商業規則
            │   (核心:最重要的商業邏輯)    │
            └──────────────────────────────┘

🔑 核心原則:依賴方向由外向內

Presentation → Application → Domain ← Infrastructure
                    ↑                        │
                    └────────────────────────┘
    Infrastructure 實作 Application 定義的介面

📝 各層的 C# 範例

// ============================================================
// 🟡 Domain Layer(領域層)— 最核心,不依賴任何其他層
// ============================================================

// 領域實體:訂單
public class Order
{
    public int Id { get; private set; }                     // 訂單 ID
    public string CustomerName { get; private set; } = ""; // 客戶名稱
    public List<OrderItem> Items { get; private set; } = []; // 訂單項目清單
    public DateTime CreatedAt { get; private set; }         // 建立時間
    public OrderStatus Status { get; private set; }         // 訂單狀態

    // 建構子:建立訂單時必須有客戶名稱
    public Order(string customerName)
    {
        CustomerName = customerName;   // 設定客戶名稱
        CreatedAt = DateTime.UtcNow;   // 記錄建立時間
        Status = OrderStatus.Pending;  // 預設狀態為「待處理」
    }

    // 商業邏輯:新增訂單項目
    public void AddItem(string productName, decimal price, int quantity)
    {
        if (quantity <= 0) // 數量必須大於 0
            throw new ArgumentException("數量必須大於零"); // 違反規則就丟例外

        Items.Add(new OrderItem(productName, price, quantity)); // 加入項目
    }

    // 商業邏輯:計算訂單總金額
    public decimal GetTotal()
    {
        return Items.Sum(item => item.Price * item.Quantity); // 加總所有項目
    }

    // 商業邏輯:確認訂單
    public void Confirm()
    {
        if (Items.Count == 0) // 沒有項目不能確認
            throw new InvalidOperationException("空訂單無法確認"); // 丟例外
        Status = OrderStatus.Confirmed; // 改為已確認
    }
}

// 值物件:訂單項目
public class OrderItem
{
    public string ProductName { get; } // 產品名稱
    public decimal Price { get; }      // 單價
    public int Quantity { get; }       // 數量

    public OrderItem(string productName, decimal price, int quantity)
    {
        ProductName = productName; // 設定產品名稱
        Price = price;             // 設定單價
        Quantity = quantity;       // 設定數量
    }
}

// 列舉:訂單狀態
public enum OrderStatus
{
    Pending,    // 待處理
    Confirmed,  // 已確認
    Shipped,    // 已出貨
    Delivered,  // 已送達
    Cancelled   // 已取消
}

// ============================================================
// 🟢 Application Layer(應用層)— 定義介面和使用案例
// ============================================================

// 定義倉儲介面(在 Application 層定義,在 Infrastructure 層實作)
public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(int id);      // 根據 ID 查詢訂單
    Task<List<Order>> GetAllAsync();        // 查詢所有訂單
    Task AddAsync(Order order);            // 新增訂單
    Task SaveChangesAsync();               // 儲存變更
}

// 定義通知服務介面
public interface INotificationService
{
    Task SendOrderConfirmationAsync(       // 發送訂單確認通知
        string customerName, int orderId);
}

// DTO(資料傳輸物件):用來回傳給外層
public class OrderDto
{
    public int Id { get; set; }            // 訂單 ID
    public string CustomerName { get; set; } = ""; // 客戶名稱
    public decimal Total { get; set; }     // 訂單總金額
    public string Status { get; set; } = ""; // 訂單狀態
}

// Use Case(使用案例):建立訂單的流程
public class CreateOrderUseCase
{
    private readonly IOrderRepository _repository;        // 倉儲介面
    private readonly INotificationService _notification;  // 通知服務介面

    // 注入介面,不是具體實作!
    public CreateOrderUseCase(
        IOrderRepository repository,
        INotificationService notification)
    {
        _repository = repository;       // 注入倉儲
        _notification = notification;   // 注入通知服務
    }

    // 執行建立訂單的流程
    public async Task<OrderDto> ExecuteAsync(
        string customerName, List<(string Name, decimal Price, int Qty)> items)
    {
        var order = new Order(customerName); // 建立新訂單

        foreach (var item in items) // 逐一加入訂單項目
        {
            order.AddItem(item.Name, item.Price, item.Qty); // 加入項目
        }

        order.Confirm(); // 確認訂單(商業規則檢查)

        await _repository.AddAsync(order);       // 儲存訂單
        await _repository.SaveChangesAsync();    // 寫入資料庫

        await _notification.SendOrderConfirmationAsync( // 發送通知
            customerName, order.Id);

        return new OrderDto // 回傳 DTO
        {
            Id = order.Id,                       // 訂單 ID
            CustomerName = order.CustomerName,   // 客戶名稱
            Total = order.GetTotal(),            // 訂單總金額
            Status = order.Status.ToString()     // 訂單狀態
        };
    }
}

🔷 CQRS 概念(Command Query Responsibility Segregation)

💡 比喻

就像銀行的存款窗口查詢窗口分開:存款(寫入)走一個流程,查餘額(讀取)走另一個流程。讀寫分離,各自優化!

📝 C# 概念範例

// === Command(命令):負責寫入操作 ===

// 建立訂單的命令
public class CreateOrderCommand
{
    public string CustomerName { get; set; } = ""; // 客戶名稱
    public List<OrderItemDto> Items { get; set; } = []; // 訂單項目
}

// 命令處理器:處理建立訂單的邏輯
public class CreateOrderHandler
{
    private readonly IOrderRepository _repository; // 寫入用的倉儲

    public CreateOrderHandler(IOrderRepository repository)
    {
        _repository = repository; // 注入倉儲
    }

    public async Task<int> HandleAsync(CreateOrderCommand command)
    {
        var order = new Order(command.CustomerName); // 建立訂單
        // ... 加入項目並儲存 ...
        await _repository.AddAsync(order);          // 寫入資料庫
        await _repository.SaveChangesAsync();       // 儲存變更
        return order.Id;                             // 回傳訂單 ID
    }
}

// === Query(查詢):負責讀取操作 ===

// 查詢訂單的請求
public class GetOrderQuery
{
    public int OrderId { get; set; } // 要查詢的訂單 ID
}

// 查詢處理器:處理查詢訂單的邏輯
public class GetOrderHandler
{
    private readonly IOrderRepository _repository; // 讀取用的倉儲

    public GetOrderHandler(IOrderRepository repository)
    {
        _repository = repository; // 注入倉儲
    }

    public async Task<OrderDto?> HandleAsync(GetOrderQuery query)
    {
        var order = await _repository.GetByIdAsync(query.OrderId); // 查詢訂單
        if (order == null) return null; // 找不到回傳 null

        return new OrderDto // 轉成 DTO 回傳
        {
            Id = order.Id,                       // 訂單 ID
            CustomerName = order.CustomerName,   // 客戶名稱
            Total = order.GetTotal(),            // 總金額
            Status = order.Status.ToString()     // 狀態
        };
    }
}

🔷 Repository + Unit of Work

💡 比喻

Repository 像各科的老師,每個老師管一科(一個資料表)。Unit of Work 像校長,負責統一說「大家一起交成績單(SaveChanges)」,確保所有操作要嘛全部成功、要嘛全部失敗。

📝 C# 實作

// Unit of Work 介面:統一管理所有 Repository 的交易
public interface IUnitOfWork : IDisposable
{
    IOrderRepository Orders { get; }     // 訂單倉儲
    IProductRepository Products { get; } // 產品倉儲
    Task<int> SaveChangesAsync();        // 統一儲存所有變更
}

// 使用方式
public class OrderService
{
    private readonly IUnitOfWork _unitOfWork; // Unit of Work

    public OrderService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork; // 注入 Unit of Work
    }

    public async Task CreateOrderAsync(string customerName, int productId)
    {
        var product = await _unitOfWork.Products // 查詢產品
            .GetByIdAsync(productId);

        if (product == null) // 產品不存在
            throw new Exception("產品不存在"); // 丟例外

        var order = new Order(customerName); // 建立訂單
        order.AddItem(product.Name, product.Price, 1); // 加入產品

        await _unitOfWork.Orders.AddAsync(order); // 新增訂單

        // 統一儲存:訂單和產品的變更一起成功或一起失敗
        await _unitOfWork.SaveChangesAsync(); // 一次性寫入資料庫
    }
}

🔷 實際專案資料夾結構範例

MyProject/                              # 方案根目錄
├── MyProject.sln                       # 方案檔
├── src/                                # 原始碼目錄
│   ├── MyProject.Domain/              # 🟡 領域層
│   │   ├── Entities/                  #    實體類別
│   │   │   ├── Order.cs               #    訂單實體
│   │   │   └── Product.cs             #    產品實體
│   │   ├── ValueObjects/             #    值物件
│   │   │   └── Money.cs               #    金額值物件
│   │   ├── Enums/                    #    列舉
│   │   │   └── OrderStatus.cs         #    訂單狀態
│   │   └── Interfaces/               #    領域介面
│   │       └── IDomainEvent.cs        #    領域事件介面
│   ├── MyProject.Application/        # 🟢 應用層
│   │   ├── DTOs/                     #    資料傳輸物件
│   │   │   ├── OrderDto.cs            #    訂單 DTO
│   │   │   └── ProductDto.cs          #    產品 DTO
│   │   ├── Interfaces/               #    倉儲及服務介面
│   │   │   ├── IOrderRepository.cs    #    訂單倉儲介面
│   │   │   └── INotificationService.cs#    通知服務介面
│   │   ├── UseCases/                 #    使用案例
│   │   │   ├── CreateOrderUseCase.cs  #    建立訂單
│   │   │   └── GetOrderUseCase.cs     #    查詢訂單
│   │   └── Mappings/                 #    物件對應設定
│   │       └── OrderProfile.cs        #    訂單的 AutoMapper 設定
│   ├── MyProject.Infrastructure/     # 🔵 基礎設施層
│   │   ├── Data/                     #    資料庫相關
│   │   │   ├── AppDbContext.cs        #    EF Core DbContext
│   │   │   └── Migrations/           #    資料庫遷移
│   │   ├── Repositories/            #    倉儲實作
│   │   │   └── OrderRepository.cs     #    訂單倉儲實作
│   │   └── Services/                #    外部服務實作
│   │       └── EmailService.cs        #    Email 服務實作
│   └── MyProject.WebApi/            # 🔴 呈現層
│       ├── Controllers/             #    API 控制器
│       │   └── OrderController.cs     #    訂單 API
│       ├── Program.cs               #    應用程式進入點
│       └── appsettings.json         #    設定檔
└── tests/                            # 測試目錄
    ├── MyProject.UnitTests/          #    單元測試
    └── MyProject.IntegrationTests/   #    整合測試

🤔 我這樣寫為什麼會錯?

錯誤一:把商業邏輯放在 Controller 裡

// ❌ Controller 裡面寫了一堆商業邏輯
// [HttpPost]
// public async Task<IActionResult> CreateOrder(OrderRequest request)
// {
//     if (request.Items.Count == 0)        // 商業規則不該在這裡!
//         return BadRequest("訂單不能為空");
//     var total = request.Items.Sum(i => i.Price * i.Qty); // 計算邏輯也不該在這裡!
//     if (total > 10000)                   // 折扣邏輯更不該在這裡!
//         total *= 0.9m;
//     // ... 存資料庫 ...
// }
// ✅ Controller 只負責接收請求和回傳結果
// ✅ 商業邏輯放在 Domain 或 Application 層

錯誤二:Domain 層依賴 Infrastructure 層

// ❌ Domain 實體直接使用 EF Core — 依賴方向錯了!
using Microsoft.EntityFrameworkCore; // Domain 層不應該引用這個!

public class Order
{
    public void Save(AppDbContext context) // Domain 不應該知道 DbContext
    {
        context.Orders.Add(this);  // 這是 Infrastructure 的工作!
        context.SaveChanges();     // Domain 不該碰資料庫!
    }
}
// ✅ Domain 層完全不知道資料庫的存在
// ✅ 在 Application 層定義 IOrderRepository 介面
// ✅ 在 Infrastructure 層實作 IOrderRepository

錯誤三:所有程式碼都塞在同一個專案裡

// ❌ 一個專案包含所有東西
MyProject/
├── Controllers/     # 呈現邏輯
├── Models/          # 混合了 Entity 和 DTO
├── Services/        # 混合了商業邏輯和資料存取
└── Data/            # 資料庫相關
// 結果:改一個功能要翻遍整個專案,測試也很難寫

// ✅ 依照 Clean Architecture 分成多個專案
// 每層各自獨立,依賴方向清楚,容易維護和測試

📝 架構選擇指南

專案規模 推薦架構 理由
小型專案(PoC、工具) 單層或三層式 快速開發,不過度設計
中型專案(企業內部系統) 三層式 + Repository 結構清楚又不會太複雜
大型專案(商業產品) Clean Architecture 高度解耦,易於測試和維護
高流量系統 Clean Architecture + CQRS 讀寫分離,各自優化效能

💡 記住:沒有最好的架構,只有最適合的架構。不要為了一個小工具套用 Clean Architecture,也不要用三層式架構去做大型商業系統!

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊