Community程式設計與開發github.com

Piyush-Nagar/coding-agents-skills

我們的後端服務遵循**基於功能的套件(Package by Feature)**,並組織成四個同級模組。此文件是決定新程式碼放置位置的唯一真理來源。

coding-agents-skills 是什麼?

此技能定義了一個嚴格的後端服務模組架構,強制採用「基於功能的套件(Package by Feature)」組織方式。所有程式碼都歸入四個同級模組:capabilities(業務邏輯)、db(持久化)、client(對外整合)和 starter(組合根目錄)。每個模組都有明確的依賴規則(starter → capabilities → {db, client})和嚴格的介面與實作分離(例如 *Repo / *RepoImpl、*Client / *ClientImpl),以確保可測試性、可切換性和關注點分離。此技能適用於 Java/Spring Boot、Kotlin、Go、Python 和 Node.js 等後端語言,主要說明代理在處理現有程式碼庫或建立新服務時,如何判斷每個檔案、類別或函式應放置的正確位置。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Piyush-Nagar/coding-agents-skills/tree/HEAD/backend/core

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

套件結構

我們的後端服務遵循基於功能的套件(Package by Feature),並組織成四個同級模組。此文件是決定新程式碼放置位置的唯一真理來源。

語言說明。 以下範例以 Java/Spring Boot 撰寫,因為這是我們主要的後端技術棧,但此結構本身與語言無關,且在我們使用的每個後端語言(Kotlin、Go、Python、Node 等)中皆為強制規定。請轉換檔案副檔名和框架註解;保留資料夾佈局、命名後綴(*Db、*Repo、*RepoImpl、*Dao、*Controller、*ControllerImpl)以及介面/實作的分離方式不變。

核心原則

依據功能分組,而非依據技術層次。 對「訂單」功能的修改應觸及彼此相鄰的檔案,而非散落在 controllers/、services/、repositories/ 套件中的檔案。技術層次在模組邊界(db、client、capabilities、starter)進行,而基於功能的套件則在各模組內部實作。

四個模組

每個服務都由以下四個同級模組組成:

<service>/
├── capabilities/   # 功能/業務邏輯 — 核心
├── db/             # 持久化:實體、儲存庫、遷移
├── client/        # 與外部系統的對外整合
└── starter/        # Spring Boot 啟動器 — 整合所有元件並執行

capabilities/ — 業務邏輯

每個功能都有自己的套件,且每個功能內部有一個 API 端點的子套件(getById、create、updateById、getByQuery……)。每個子功能擁有三個同級資料夾:

  • service/ — 該 API 的單一服務類別
  • dto/ — request/ 和 response/(視情況而定)
  • mapper/ — 僅針對此子功能,將 *Db 實體 ↔ DTO 進行轉換(雙向)

該功能的控制器位於功能套件的根目錄,如同儲存庫一樣分為介面和實作:

  • capabilities/<feature>/<Feature>Controller.java — 介面;宣告路由、HTTP 動詞、請求/回應型別、API 文件註解
  • capabilities/<feature>/<Feature>ControllerImpl.java — 實作;帶有框架註解(@RestController),並將每個路由委派給對應的子功能服務

該功能所有子功能共享。請參閱控制器。

  • 依賴於:db(僅透過 *Repo 介面 — 請參閱 DB 使用規則)、client
  • 絕不依賴於:starter
  • 不包含 main 類別,沒有 @SpringBootApplication
  • 擁有:HTTP 控制器、API 請求/回應 DTO、映射器、服務

db/ — 持久化

每個資料表一個資料夾。每個資料表資料夾恰好包含三個子資料夾:

db/<tableName>/
├── model/
│   ├── <EntityName>Db.java          # 主要 JPA 實體
│   └── <Other>Db.java               # 子模型(內嵌型別、子實體、投影)
├── dao/
│   └── <EntityName>Dao.java         # 低層級資料存取(Criteria/JPQL/native queries)
└── repository/
    ├── <EntityName>Repo.java        # PUBLIC 介面 — capabilities 唯一能看到的型別
    └── impl/
        └── <EntityName>RepoImpl.java # 將 Spring Data + Dao 組合在介面後方

命名慣例

層級後綴範例
JPA 實體DbTestcaseDb
儲存庫介面RepoTestcaseRepo
儲存庫實作RepoImplTestcaseRepoImpl
資料存取輔助DaoTestcaseDao

DB 使用規則

Capabilities 只能依賴 *Repo 介面。 *RepoImpl 和 *Dao 是內部元件 — 絕不能從 db/<tableName>/ 套件外部注入或參照。這能保持持久化層可切換,並使功能測試專注於合約。

  • 依賴於:此服務中無
  • 公開:*Repo 介面和 *Db 模型型別
  • 絕不包含:業務邏輯、轉換為 API DTO

client/ — 對外整合

包含呼叫此服務所依賴的其他服務的程式碼。主要為我們平台中每個依賴的微服務建立一個子資料夾(paymentService、userService、executionService……)。第三方 SDK 包裝器或訊息生產者也可以放在此處,但主要模式(也是此模組存在的原因)是依賴的微服務用戶端。

client/<microserviceName>/
├── <Microservice>Client.java       # 介面 — capabilities 依賴的物件
├── impl/
│   └── <Microservice>ClientImpl.java   # 實作:HTTP/gRPC/SDK 呼叫、重試、逾時
├── dto/                            # 此微服務 API 的請求/回應格式
│   ├── request/
│   └── response/
└── mapper/                         # 可選:在他們的格式和我們的格式之間轉換

規則

  • 每個依賴的微服務一個子資料夾,以服務名稱命名。即使它們「相關」,也不要將多個微服務塞入一個套件。

  • Capabilities 只能依賴 *Client 介面。 *ClientImpl 是內部元件 — 規則與 *Repo / *RepoImpl 相同。

  • 用戶端介面以其自身的 DTO 形式公開微服務的 API;如有需要,capabilities 可以進一步調整這些 DTO。

  • 跨橫切關注點(重試、斷路器、請求/回應記錄、驗證標頭)屬於 impl/ 內部 — capabilities 絕不應看到它們。

  • 依賴於:此服務中無

  • 公開:*Client 介面及其請求/回應 DTO

  • 絕不包含:業務邏輯、功能協調

starter/ — 組合根目錄

唯一會執行的模組。包含 @SpringBootApplication、全域 REST 異常處理器、安全設定、過濾器和 application.yml。沒有控制器 — 那些存在於 capabilities/ 中。

  • 依賴於:capabilities(以及傳遞依賴 db、client)
  • 擁有:可部署的構件、跨橫切 Web 設定(安全性、CORS、錯誤處理)
  • 絕不擁有:功能控制器或功能 DTO

依賴規則

starter → capabilities → { db, client }

箭頭方向單一。db 和 client 從不匯入 capabilities 或 starter。capabilities 從不匯入 starter。

控制器

每個功能有一個控制器(而非每個子功能),且該控制器存在於 capabilities/ 內的功能套件根目錄。它分為兩個檔案,類似 *Repo / *RepoImpl 的分離:

  • capabilities/<feature>/<Feature>Controller.java — 介面。宣告路由、HTTP 動詞、路徑/查詢參數、請求/回應 DTO 型別和 API 文件註解(OpenAPI/Swagger)。閱讀此介面即可看到完整的 HTTP 合約。
  • capabilities/<feature>/<Feature>ControllerImpl.java — 實作。帶有框架註解(Spring 中的 @RestController),是子功能服務的注入目標,並包含每個路由的一個簡短方法,該方法呼叫對應的 service/ 並回傳結果。除了委派之外沒有邏輯。

為什麼要分離?介面是 API 合約 — 穩定、有文件記錄、可獨立測試。實作是接線 — 它可以在不改變合約的情況下變更。原因與 *Repo 和 *RepoImpl 相同。

控制器將每個 HTTP 路由委派給 capabilities/<feature>/<sub-feature>/service/ 中對應的子功能服務。請求/回應 DTO 由子功能擁有(capabilities/<feature>/<sub-feature>/dto/);控制器僅是薄薄的 HTTP 轉接器。

為什麼放在 capabilities/ 而不是 starter/ 中?當 API 變更時,控制器、其服務和其 DTO 會一起變更 — 將它們保留在同一個模組中可維護「一個功能,一個位置」的基於功能套件承諾。starter/ 保留給跨橫切接線(安全性、錯誤處理器、應用程式進入點),而非每個功能的 Web 程式碼。

實作範例:一個 testcase 功能

以下樹狀結構使用 .java 副檔名進行說明。請替換您語言的檔案副檔名和框架慣用語 — Kotlin .kt、Go 套件、Python 模組等 — 但保持資料夾佈局和命名後綴完全相同。

testcase-service/
├── capabilities/
│   └── com/example/testcase/capabilities/
│       └── testcase/                          # 主功能
│           ├── TestcaseController.java        # 介面 — HTTP 合約、路由宣告、API 文件
│           ├── TestcaseControllerImpl.java    # 實作 — @RestController,委派給子功能服務
│           ├── getById/                       # 子功能 = 一個 API
│           │   ├── service/
│           │   │   └── GetTestcaseByIdService.java
│           │   ├── dto/
│           │   │   └── response/
│           │   │       └── GetTestcaseByIdResponse.java
│           │   └── mapper/
│           │       └── GetTestcaseByIdMapper.java     # TestcaseDb → response
│           ├── create/
│           │   ├── service/
│           │   │   └── CreateTestcaseService.java
│           │   ├── dto/
│           │   │   ├── request/
│           │   │   │   └── CreateTestcaseRequest.java
│           │   │   └── response/
│           │   │       └── CreateTestcaseResponse.java
│           │   └── mapper/
│           │       └── CreateTestcaseMapper.java      # request → TestcaseDb, TestcaseDb → response
│           ├── updateById/
│           │   ├── service/
│           │   │   └── UpdateTestcaseByIdService.java
│           │   ├── dto/
│           │   │   ├── request/
│           │   │   │   └── UpdateTestcaseByIdRequest.java
│           │   │   └── response/
│           │   │       └── UpdateTestcaseByIdResponse.java
│           │   └── mapper/
│           │       └── UpdateTestcaseByIdMapper.java
│           └── getByQuery/
│               ├── service/
│               │   └── GetTestcaseByQueryService.java
│               ├── dto/
│               │   ├── request/
│               │   │   └── GetTestcaseByQueryRequest.java
│               │   └── response/
│               │       └── GetTestcaseByQueryResponse.java
│               └── mapper/
│                   └── GetTestcaseByQueryMapper.java
│
├── db/                                        # 每個資料表一個資料夾
│   └── com/example/testcase/db/
│       └── testcase/                          # 資料表名稱
│           ├── model/
│           │   ├── TestcaseDb.java            # JPA 實體 — 主要
│           │   └── TestcaseStepDb.java        # 子模型(子/內嵌)
│           ├── dao/
│           │   └── TestcaseDao.java           # 查詢/條件輔助,package-private 使用
│           └── repository/
│               ├── TestcaseRepo.java          # 介面 — capabilities 依賴於此
│               └── impl/
│                   └── TestcaseRepoImpl.java  # 組合 Spring Data + TestcaseDao
│
├── client/                                   # 每個依賴的微服務一個資料夾
│   └── com/example/testcase/client/
│       ├── executionService/                 # 我們呼叫的依賴微服務
│       │   ├── ExecutionServiceClient.java        # 介面
│       │   ├── impl/
│       │   │   └── ExecutionServiceClientImpl.java  # HTTP/gRPC 呼叫、重試、逾時
│       │   ├── dto/
│       │   │   ├── request/
│       │   │   │   └── RunTestcaseRequest.java
│       │   │   └── response/
│       │   │       └── RunTestcaseResponse.java
│       │   └── mapper/
│       │       └── ExecutionServiceMapper.java
│       └── userService/                      # 另一個依賴的微服務
│           ├── UserServiceClient.java
│           └── impl/
│               └── UserServiceClientImpl.java
│
└── starter/                                  # 僅跨橫切接線 — 沒有控制器
    └── com/example/testcase/
        ├── TestcaseServiceApplication.java
        ├── config/
        │   ├── SecurityConfig.java
        │   └── WebConfig.java
        ├── error/
        │   └── GlobalExceptionHandler.java   # @ControllerAdvice — 跨橫切,非每個功能
        └── resources/application.yml

經驗法則

  • 為現有功能新增 API? 在 capabilities/<feature>/ 內建立一個新的子功能套件,包含其自己的 service/、dto/{request,response}/ 和 mapper/,然後將路由宣告新增到 <Feature>Controller.java(介面)並將委派方法新增到 <Feature>ControllerImpl.java。不要建立第二個控制器。
  • 新增全新的功能? 在 capabilities/ 中建立功能套件(控制器在根目錄,每個 API 的子功能),並在 db/<tableName>/ 中建立對應的資料表資料夾,包含 model/、dao/、repository/{impl/}。僅在呼叫外部系統時才新增用戶端。不要觸碰 starter/。
  • 始終注入 *Repo,絕不注入 *RepoImpl 或 *Dao。 Capabilities 僅依賴介面。*RepoImpl 和 *Dao 是 db/<tableName>/ 的內部元件 — 它們的存在是為了讓您可以切換查詢策略或後端儲存,而無需變更 capability 程式碼。
  • 始終注入 *Client,絕不注入 *ClientImpl。 對依賴微服務的呼叫也適用相同規則。Capabilities 依賴介面;傳輸層(HTTP、gRPC、SDK)位於 impl/ 後方。
  • client/ 內每個依賴的微服務一個資料夾。 以微服務名稱命名資料夾。不要將「所有內部服務」分組到一個套件中。
  • 子功能粒度止於 capabilities/。 db/ 按資料表組織;client/ 按外部系統組織。將任一者按 API 拆分會分散內聚的程式碼。
  • 請求/回應 DTO 及其映射器是每個子功能專屬的,絕不共享。 如果 create 和 updateById 今天看起來相似,請保留兩個 DTO 和兩個映射器。共享會將兩個會獨立改變的 API 耦合在一起。
  • 映射器直接進行實體 ↔ DTO 轉換。 不需要中間領域模型層。<SubFeature>Mapper 將 *Db 轉換為該子功能的請求/回應 DTO。
  • 三個子功能有相似的程式碼? 在您在四個或更多位置看到相同模式之前,請抵制提取共享服務或映射器的衝動。
  • 跨功能重用? 將共享工具放在 capabilities/ 內的 common/ 套件中。

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

相關技能