FSTS 後端編碼規範

OMS 專案後端(.NET 8 / ASP.NET Core 8)的 Clean Architecture + CQRS + MediatR 編碼規範(v1.0,2026-02-26)。回到 FSTS 專案總覽、搭配前端:FSTS 前端編碼規範

一句話

OMS 後端採 Clean Architecture 四層(Domain / Application / Infrastructure / Api)+ CQRS + MediatR Pipeline,依「外層可參考內層、內層不可參考外層」的相依方向編排,所有共用邏輯透過「決策樹」判定落點。1

架構總覽(四層)

層次專案相依方向核心職責
DomainOms.Domain無外部相依實體、列舉、例外、事件
ApplicationOms.Application參考 DomainCQRS、驗證、介面定義
InfrastructureOms.Infrastructure參考 ApplicationEF Core、Repository、外部服務
ApiOms.Api參考全部Controller、Middleware、DI

相依方向:Api → Infrastructure → Application → Domain1

2. Domain Layer — Oms.Domain

最內層,完全不依賴任何外部套件,僅包含純粹的領域模型與不變量。2

主要目錄:2

  • Entities/ — 資料實體(對應 DB 表)+ 商業不變量(如 User.cs, Order.cs
  • Enums/ — 領域列舉(如 OrderStatus
  • Exceptions/ — 領域特定例外
  • Events/ — 領域事件
  • Services/純計算 Domain Service(static class,如 PricingCalculator

3. Application Layer — Oms.Application

核心商業邏輯層,採 CQRS + MediatR:每個 Use Case 對應一個 Command 或 Query,Handler 編排流程。3

3.1 目錄結構

  • Features/{Feature}/Commands/ — 寫入操作(Command + Validator + Handler 合併於同一檔案
  • Features/{Feature}/Queries/ — 讀取操作(Query + Validator + Handler 合併於同一檔案)
  • Features/{Feature}/DTOs/ — 資料傳輸物件
  • Features/{Feature}/EventHandlers/ — 域事件處理器
  • Features/{Feature}/Specifications/ — 查詢規格(封裝複雜查詢條件)
  • Common/Interfaces/ — Repository、外部服務的抽象介面
  • Common/Attributes/ — 自訂 Attribute(如敏感資料標記 SensitiveDataAttribute
  • Common/Behaviors/ — MediatR Pipeline 行為(橫切關注)
  • Common/Models/ — 共用回應模型(PagedResponse<T>, Result<T>3

3.2 Handler 的角色 — 編排者模式

Handler 在 CQRS 架構中扮演「編排者」角色,不直接實作共用邏輯,而是透過依賴注入調用其他服務。職責流程:3

  1. 接收 Command/Query 請求
  2. 調用 Domain Services 執行純計算
  3. 調用 Repository 存取資料
  4. 調用 Application Interfaces 使用外部服務(如 IJwtTokenGeneratorIEmailService
  5. 組裝回應 DTO 並回傳

3.3 Domain Service vs Application Service

屬性Domain ServiceApplication Service(介面)
位置Domain/Services/Application/Common/Interfaces/ 定義介面,Infrastructure/ 實作
是否需要介面否(直接 static class是(反轉相依)
是否需要 DI否(直接呼叫 static)是(構造器注入)
是否涉及 I/O否(純計算)是(DB / API / 檔案)
可測試性直接呼叫即可透過 Mock 介面測試

判斷原則:如果邏輯不需要 await、不碰資料庫、不呼叫外部服務 → Domain Service;反之 → Application Interface。3

4. Infrastructure Layer — Oms.Infrastructure

負責所有外部資源存取與 Application 層介面的實作。4

  • Persistence/ — EF Core AppDbContext
  • Persistence/Configurations/ — Fluent API 實體設定(如 UserConfiguration.cs
  • Persistence/Repositories/IRepository<T> 實作
  • Persistence/Interceptors/ — SaveChanges 攔截器(如 AuditableEntityInterceptor
  • Services/ — Application 介面的具體實作(如 JwtTokenGenerator, BcryptPasswordHasher, MediatRDomainEventDispatcher
  • DependencyInjection/ — Infrastructure DI 註冊(InfrastructureServiceRegistration.cs4

5. Api Layer — Oms.Api

進入點層,Controller 僅負責轉發請求至 MediatR,不包含商業邏輯5

  • Controllers/ — HTTP 端點,僅做 _mediator.Send()
  • Middleware/ — 全域例外處理(GlobalExceptionMiddleware
  • Filters/ — Action Filter
  • Services/ — Api 層專用服務(如 CurrentUserServiceHttpContext 取資料)5

三種 Service 的差異5

目錄性質是否需要介面範例
DomainDomain/Services/純計算、static、零相依不需要PricingCalculator
ApplicationCommon/Interfaces/介面定義(涉 I/O 的共用邏輯)是(實作在 Infra)IStockService
ApiApi/Services/框架膠水,橋接 ASP.NET 與 Application 介面不需要(它本身就是實作)CurrentUserService

Controller 三件事原則

Controller 內不應出現 if/else 商業判斷,僅做三件事:5

  1. 組裝 Command / Query(加入 server-side 資訊,如 IP、UserAgent)
  2. 透過 MediatR 轉發(_mediator.Send(command)
  3. 回傳 HTTP 狀態碼 + 結果

6. 共用邏輯決策樹

當一段商業邏輯被多個 Handler 使用時,依以下流程決定放置位置:6

#判斷問題YesNo
Q1只被一個 Handler 使用?直接寫在該 Handler 內繼續 Q2
Q2涉及 I/O(DB / API / 檔案 / 網路)?繼續 Q3Domain Service(純 static class
Q3是資料存取操作?Repository 擴充方法繼續 Q4
Q4涉及第三方服務(Email / SMS / 外部 API)?Application Interface + Infrastructure 實作繼續 Q5
Q5涉及多個 Repository 的組合操作?Application Service(組合服務)重新檢視是否真的是共用邏輯

四種放置位置速查

  • A. Domain ServiceDomain/Services/ + public static class,無需 DI,如 PricingCalculator.CalculateTotal(items, discount)6
  • B. Repository 擴充:介面在 Application/Common/Interfaces/IOrderRepository.cs,實作在 Infrastructure/Persistence/Repositories/OrderRepository.cs(如 GetByIdWithItemsAsync, GetByStatusAsync)。6
  • C. Application Interface + Infra 實作:跨外部服務(Email、呼叫外部 API),介面在 Application/Common/Interfaces/IEmailService.cs,實作在 Infrastructure/Services/SmtpEmailService.cs,DI 用 services.AddScoped<IEmailService, SmtpEmailService>()6
  • D. Application Service(組合服務):橫跨多個 Repository 的編排(如 IStockService 注入 IProductRepository + IInventoryRepository),介面在 Application、實作在 Infrastructure。6

7. Pipeline Behaviors

MediatR Pipeline Behaviors 處理橫切關注點:7

Behavior用途
ValidationBehavior自動執行 FluentValidation,失敗拋 ValidationException
LoggingBehavior記錄請求名稱 + 執行時間,自動遮蔽 [SensitiveData] 標記欄位
TransactionBehavior(預留)自動包裝 UnitOfWork 交易(尚未實作)

8. 請求參數驗證(FluentValidation)

8.1 驗證流程

搭配 MediatR Pipeline 實現自動驗證,不需在 Handler 或 Controller 手動呼叫:8

HTTP Request → Controller(不驗證,只轉發)
  → _mediator.Send(command)
  → ValidationBehavior → 通過 → Handler
                       → 失敗 → ValidationException → GlobalExceptionMiddleware → HTTP 400 + ProblemDetails

8.2 三類驗證的職責分工

驗證類型負責層失敗 HTTP 狀態碼範例
輸入格式Validator (Application)400 Bad RequestEmail 格式不正確、密碼太短
業務規則Handler / Domain Entity422 Unprocessable帳號已存在、訂單狀態不可轉換
找不到資源Handler404 Not Found訂單不存在

關鍵:Validator 驗證「輸入格式」(email 格式、必填、長度限制);Handler / Domain 驗證「業務規則」(帳號是否存在、庫存是否足夠)。8

8.3 檔案放置

Validator 與對應的 Command/Query 放在同一個檔案中CreateOrder.cs 含 Command + Validator + Handler);檔案過大才拆成 CreateOrder.Command.cs / .Validator.cs / .Handler.cs9

8.4 驗證規則速查

類型方法適用場景
必填NotEmpty()字串、集合不可為空
字串長度MinimumLength(n) / MaximumLength(n)密碼下限、名稱上限
精確長度Length(min, max)手機號碼、統一編號
EmailEmailAddress()Email 欄位
正則Matches("pattern")密碼複雜度、自定格式
數值 > NGreaterThan(n)ID、數量必須為正整數
數值 >= NGreaterThanOrEqualTo(n)金額不可為負
數值範圍InclusiveBetween(min, max)分頁 PageSize 限制
欄位比對Equal(x => x.OtherField)確認密碼
自定義Must((cmd, val) => ...)任何自定邏輯
列舉值IsInEnum()確保值在 enum 範圍內
巢狀物件SetValidator(new ChildValidator())驗證子物件
集合每項RuleForEach(x => x.Items).ChildRules(...)驗證集合內每個元素

8.5 驗證失敗回應格式(RFC 7807 ProblemDetails)

{
  "status": 400,
  "title": "Validation Error",
  "detail": "One or more validation errors occurred.",
  "errors": {
    "CustomerId": ["CustomerId must be a positive integer."],
    "CustomerName": ["CustomerName is required."],
    "Items": ["Order must contain at least one item."]
  },
  "traceId": "00-abc123..."
}

DI 註冊:services.AddValidatorsFromAssembly(assembly) 掃描所有 Validator,services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)) 註冊 Pipeline。10

9. 開發最佳實踐

9.1 C# 命名規範(.editorconfig 編譯時強制)

.editorconfig + EnforceCodeStyleInBuild + TreatWarningsAsErrorsdotnet build 時強制執行,違反即編譯失敗11

對象規則範例嚴重性
介面I 開頭 + PascalCaseIUserRepositoryerror
泛型型別參數T 開頭 + PascalCaseTResponseerror
型別(class, struct, enum)PascalCaseOrderStatus, Usererror
公開成員(property, method, event)PascalCaseTotalAmount, GetByIdAsync()error
Private 欄位_camelCase_userRepositoryerror
參數camelCasecancellationTokenerror
常數PascalCase(非 UPPER_SNAKEMaxRetryCountwarning→error
靜態唯讀欄位PascalCaseDefaultTimeoutwarning→error
Async 方法Async 結尾SaveChangesAsync()warning→error
區域變數camelCasevar user = ...warning→error

設定檔:.editorconfig(命名規則)+ Directory.Build.propsEnforceCodeStyleInBuild, TreatWarningsAsErrors)。11

9.2 C# 程式碼風格

規則設定值說明
Namespace 宣告file_scoped (error)namespace X; 而非 namespace X { }
using 位置outside_namespace (error)using 放在 namespace 外
using 排序System 優先using System.* 排最前
var 使用型別明顯時用 varvar user = new User()
大括號Allman style(全部換行)if (...)\n{
Expression body單行時建議使用public int Id => _id;

嚴重性等級:error(編譯失敗)> warning(搭配 TreatWarningsAsErrors 也失敗)> suggestion(僅 IDE 提示)> none(關閉)。12

常用指令12

  • dotnet format --verify-no-changes — 檢查格式問題(不修改)
  • dotnet format — 自動修正格式
  • dotnet format style — 只修正命名規則
  • dotnet format whitespace — 只修正空白/縮排

9.3 CQRS 架構命名慣例

類型規則範例
Command{Action}{Entity}CommandCreateOrderCommand
QueryGet{Entity}[By{Filter}]QueryGetOrderByIdQuery
Handler{Command/Query}HandlerCreateOrderCommandHandler
Validator{Command}ValidatorCreateOrderCommandValidator
DTO{Entity}ResponseOrderResponse, AuthResponse
InterfaceI{Service/Repository}IOrderRepository, IJwtTokenGenerator

9.4 跨層相依規則

  • Domain 不參考任何其他專案(零依賴)
  • Application 僅參考 Domain(定義介面,不實作 I/O)
  • Infrastructure 參考 Application(實作介面)
  • Api 參考全部(組裝 DI、只透過 MediatR 傳遞請求)
  • Controller 不直接注入 Repository 或 DbContext13

9.5 新功能開發 Checklist(10 步)

  1. Domain — 新增/修改 Entity(如有需要)
  2. Application — 建立 Command/Query + Handler
  3. Application — 建立 Validator(FluentValidation)
  4. Application — 建立 DTO(回應格式)
  5. Infrastructure — 如需新 Repository 方法,擴充介面 + 實作
  6. Infrastructure — 如需新外部服務,建立 Interface + 實作
  7. Infrastructure — DI 註冊新服務
  8. Api — 新增 Controller endpoint(僅 MediatR.Send
  9. Database — 更新 schema.sql
  10. 測試 — Unit Test Handler + Integration Test API14

11. 完整範例:查詢訂單列表

GET /api/v1/orders?status=Pending&page=1&pageSize=10 涵蓋:參數檢核(分頁、篩選條件驗證)、DB 查詢(分頁 + 條件篩選 + Include 關聯資料)。完整從建檔到完成流程詳見 raw §11。15

既有 Pipeline 在新功能中的複用:新增此 API 不需修改 DI 註冊(MediatR 自動掃描)、Pipeline(全域生效)、Middleware(全域生效)。15

12. 後端測試規範

12.1 測試金字塔

層級位置用途
Unittests/Oms.UnitTests/Domain 邏輯、Handler、Validator
Integrationtests/Oms.IntegrationTests/Repository 查詢、EF Core 設定
APItests/Oms.Api.Tests/Controller 端到端

12.2 測試專案獨立

.NET 生態系慣例將測試放在獨立 csproj(透過 ProjectReference 引用 src/)。原因:測試專案不應打包進發布產物,xUnit 與 NSubstitute 只存在於測試依賴。16

12.3 何時必須寫測試(PR 合併前強制)

  • Domain Entity 的任何變更(Order 狀態機、User 欄位等)
  • 新增或修改 Command/Query Handler
  • 新增或修改 FluentValidation Validator
  • Bug 修復:先寫失敗的測試,再修正程式碼17

12.4 命名慣例

  • 檔案:{ClassName}Tests.cs
  • 方法:{Method}_Should{ExpectedResult}_When{Condition}
  • DisplayName 用中文:[Fact(DisplayName = "登入:密碼錯誤應拋出 DomainException")]17

12.5 覆蓋率目標

範圍目標
Domain Entities90%+
Command/Query Handlers80%+
FluentValidation Validators80%+
Infrastructure Services70%+

12.6 測試工具

工具版本用途
xUnit2.9.2測試框架
NSubstitute5.1.0Mocking 框架
FluentAssertions6.12.1斷言語法
EF Core InMemory8.0.11整合測試記憶體 DB
WebApplicationFactory8.0.11API 端到端測試

執行指令18

  • dotnet test tests/Oms.UnitTests/ — 全部單元測試
  • dotnet test tests/Oms.UnitTests/ --filter "FullyQualifiedName~OrderTests" — 特定測試類別
  • dotnet test --collect:"XPlat Code Coverage" — 含覆蓋率報告
  • dotnet test — 全部測試

13. CI/CD 管線與 Git Hooks

13.1 GitHub Actions CI

觸發情境:Push 到 maindevelop;對 main / develop 發起 PR。19

管線內容:19

  1. 後端:dotnet restoredotnet builddotnet test(三個測試專案)
  2. 前端:npm citype-checklinttest:coverage

設定檔:.github/workflows/ci.yml

13.2 Git Hooks

啟用:git config core.hooksPath .githooks20

Hook觸發時機檢查內容
pre-commitgit commit後端編譯 + 前端 type-check + lint
pre-pushgit push後端單元測試 + 前端測試

13.3 PR Checklist

每次提交 PR 前確認:21

  • 所有現有測試通過(dotnet test + npm run test
  • 新增/修改的商業邏輯有對應測試
  • 測試涵蓋成功路徑和錯誤路徑
  • 沒有被跳過的測試(除非有文件說明原因)
  • Domain Entity 變更有對應 Domain 測試
  • Handler 變更有對應 Handler 測試
  • 前端新增的 utility / hook 有對應測試

相關頁面

補充資訊

來源補充:OMS Coding Rule & CI/CD 簡報(2026-03,OMS Team)

OMS Team 於 2026-03 對後端規範做了一份簡報版摘要,強化了以下工程決策背景(與本頁主要規範一致,僅補充理由與社群參考):

  • Clean Architecture 的選型理由:Robert C. Martin(Uncle Bob,SOLID / Agile Manifesto / 《Clean Code》作者)於 2012 提出;.NET 社群採用度高 — Jason Taylor 的 CleanArchitecture 範本(★ 17,000+)、Ardalis 的 Clean Architecture(★ 16,000+)、微軟 eShopOnWeb 官方推薦;OMS 專案以 Jason Taylor 範本為基礎搭建22
  • RFC 7807 ProblemDetails:錯誤格式選用 IETF 制定的 RFC 7807(2016,2022 更新為 RFC 9457);ASP.NET Core 自 .NET 7 起設為預設;Spring Boot / NestJS 皆有對應支援,選用此規範與業界主流一致;透過 GlobalExceptionHandler 統一攔截例外轉成 ProblemDetails 格式回傳。23
  • 「查詢訂單列表」四層協作範例OrdersController.GetOrders → _mediator.Send(query) → GetOrdersQueryHandler → IOrderRepository → Order Entity,示範「Controller 越薄越好 / Handler 只依賴介面 / Repository 以 EF Core 實作」的同心圓依賴方向。24
  • Services/ 目錄在四層的角色差異(避免放錯):Domain → 純 static 計算;Application → 介面定義;Infrastructure → 介面實作;Api → HttpContext 膠水。25

上述簡報同時涵蓋 CI/CD 章節,獨立成頁:CD Pipeline


參考資料

Footnotes

  1. Backend-CodingRule §1 架構總覽 2

  2. Backend-CodingRule §2 Domain Layer 2

  3. Backend-CodingRule §3 Application Layer 2 3 4

  4. Backend-CodingRule §4 Infrastructure Layer 2

  5. Backend-CodingRule §5 Api Layer 2 3 4

  6. Backend-CodingRule §6 共用邏輯決策樹 2 3 4 5

  7. Backend-CodingRule §7 Pipeline Behaviors

  8. Backend-CodingRule §8.1 驗證流程 2

  9. Backend-CodingRule §8.2 檔案放置位置

  10. §8.5 完整範例 + 運作原理

  11. [[_raw/projects/fsts/docs/coding-rules/Backend-CodingRule|Backend-CodingRule §9.1 C# 命名規範]] 2

  12. [[_raw/projects/fsts/docs/coding-rules/Backend-CodingRule|Backend-CodingRule §9.3 如何修改 C# 編碼規則]] 2

  13. §9.5 CQRS 命名 + 跨層相依

  14. Backend-CodingRule §9.6 新功能開發 Checklist

  15. Backend-CodingRule §11 完整開發範例 — 查詢訂單列表 2

  16. Backend-CodingRule §12.2 測試專案結構

  17. §12.4 何時必須寫測試 + 命名慣例 2

  18. Backend-CodingRule §12.5 執行測試指令

  19. Backend-CodingRule §13.1 GitHub Actions CI 2

  20. Backend-CodingRule §13.2 Git Hooks

  21. Backend-CodingRule §13.3 PR Checklist

  22. OMS-CodingRule-CICD §Slide 3 什麼是 Clean Architecture

  23. OMS-CodingRule-CICD §Slide 7 CQRS · 錯誤處理 · 測試

  24. OMS-CodingRule-CICD §Slide 5 查詢訂單列表 四層範例

  25. OMS-CodingRule-CICD §Slide 6 四層目錄結構