A complete design guide from domain model to REST API: CQRS read-write separation, four-layer data object transformation chain (PO→DO→DTO→VO), unified response format, BFF multi-client adaptation, version management, and security design.
Workflow
- Identify Command vs Query — Divide domain behavior into commands (write) and queries (read), determining Method and endpoints
- Design data object transformation chain — Establish PO→DO→DTO→VO four-layer transformation, each layer with independent responsibilities
- Design REST endpoints — Command verb suffix, Query resource naming
- Define unified response format — Result<T> wrapper + business error code system
- Apply BFF — One BFF per frontend, data aggregation + format adaptation + protocol conversion
- Choose versioning strategy — Recommended URL Path: /api/v1/orders, CDN friendly
- Apply security controls — AuthN + AuthZ + three-layer validation + differentiated rate limiting
When to Use
| ✅ ALWAYS use when | ❌ Skip when |
|---|---|
| API design, REST API, interface design | Internal tools with no external consumers |
| DTO/VO design, data object transformation | GraphQL/gRPC projects |
| BFF / Backend for Frontend | When there is no domain model → domain-designer |
| OpenAPI / Swagger / API documentation | Simple CRUD without DDD |
| API version management / security design | Pure gRPC microservices (use protobuf IDL) |
| Need to expose DDD aggregates as REST API | Rapid prototyping without concern for API specifications |
Boundary
✅ Clearly applicable
- Need to expose DDD domain model as REST API — CQRS read-write separation, full implementation of data object transformation chain
- CQRS command/query separation design — independent Command DTO and Query DTO, each evolving separately
- Unified API design for multiple clients (Web/iOS/MiniApp) — BFF pattern adapted per platform
- Unified response format and error code system design — Result<T> + business error code standardization
- OpenAPI/Swagger specification output — code generation strategy keeps interface and implementation in sync
⚠ Needs careful evaluation
- Team unfamiliar with DDD/CQRS → learn basic concepts first
- Monolithic application with no scaling needs → evaluate ROI, may be over-engineering
- Existing API has no consumer compatibility requirements → version management can be simplified
❌ Not applicable
- GraphQL/gRPC projects → use corresponding IDL and toolchain
- Simple CRUD without DDD → first use a general REST framework or domain-designer
- Rapid prototyping/demo stage → first use a simplified API, introduce specifications later
- Pure gRPC microservices → use protobuf IDL + gRPC interceptors
- Internal tools with no external consumers → simplify API design
CQRS API Design
Commands (write) are verb-driven, Queries (read) are resource-driven:
| Dimension | Command | Query |
|---|---|---|
| HTTP Method | POST/PUT/DELETE | GET |
| URL verb | Required (confirm, cancel) | Not required |
| Request body | Command object | Query parameters only |
| DTO separation | Independent Command DTO | Independent Query DTO |
| Idempotency | Must be implemented | Naturally idempotent |
| Caching | Never cached | ETag, max-age |
| Response | Created resource summary | Data DTO / List |
Principle: Command DTO and Query DTO are always defined separately. Sub-resource nesting is at most 2 levels. See references/patterns/cqrs-api-design.md
Data object conversion chain (PO → DO → DTO → VO)
| Object | Layer | Responsibility | Visibility |
|---|---|---|---|
| PO | Infrastructure | ORM mapping, corresponds to database structure | Internal |
| DO | Domain | Rich domain model, contains business behavior | Internal |
| DTO | Interface/App | Cross-layer and cross-service data transfer | Semi-internal |
| VO | Interface | Page-specific display data | External |
Read direction: PO→DO→DTO→VO; write direction: VO→DTO→Command→DO→PO. One DO can be converted into multiple DTOs by scenario (detail DTO, summary DTO, etc.). Controller does not directly return domain objects. See references/examples-ref/data-object-transformation.md
API Design Specification
| Rule | Example |
|---|---|
| Plural nouns | /orders ✓ |
| Kebab-case | /order-history ✓ |
| Maximum 2 levels of nesting | /orders/{id}/items |
| Write verb suffix | /orders/{id}/confirm |
| Query parameters | ?status=PAID&page=1 |
| No URL verbs | ❌ GET /getOrders → GET /orders |
HTTP Status: 201 Created (create), 200 OK (query/update), 204 No Content (delete), 400 (validation/business), 404 (not found), 409 (concurrency conflict), 429 (rate limiting), 500 (internal error). See references/security/api-naming-conventions.md
Unified response format
Success:{ "code": 0, "message": "success", "data": T } — 201/200/204
Error:{ "code": 40001, "message": "...", "detail": "...", "requestId": "req-xxx" } — 400/404/409/429/500
Response wrapper Result<T> contains code + message + data + requestId. Error responses never return stack traces. See references/examples-ref/unified-response-format.md
BFF(Backend for Frontend)
One BFF per frontend (Web/iOS/MiniApp), responsibilities:
- Data aggregation: combine data from multiple services into a page VO (1 frontend call replaces N calls)
- Format adaptation: full fields for Web / streamlined fields for mobile
- Protocol conversion: internal gRPC → external REST/JSON
- Response shaping: remove internal fields, add UI metadata
Difference from API Gateway: BFF does view aggregation (page level), Gateway does routing + rate limiting (service level). BFF does not directly access the database and does not contain business logic. See references/patterns/BFF-design-pattern.md
API Version Management
| Strategy | Example | Recommendation level |
|---|---|---|
| URL Path ★ | /api/v1/orders → /api/v2/orders |
★★★★★ |
| Request Header | Accept: vnd.company.v2+json |
★★★☆☆ |
| Query Param | /api/orders?version=2 |
★★☆☆☆ |
Recommended URL Path: intuitive, CDN-friendly, Swagger-compatible. Migration process: v1 → v1+v2 → v2 only → v1 sunset (410 Gone). See references/migration/api-versioning-strategies.md
API Security Design
Four-layer security model:
- Authentication: JWT Bearer Token / OAuth2 / API Key (service-to-service)
- Authorization: by bounded context + resource ownership + role
- Input validation: Controller format → Application business → Domain invariants
- Rate limiting: Command 50/s, Query 200/s, Auth 10/s. See references/security/api-security-design.md
Gotchas — Common Pitfalls
DTO exposing enum→string code | Command/Query DTO mixed use→separate | null safety→handle Optional | VO passing through DB fields→view customization | missing idempotency→Idempotency-Key | error passing through stack trace→requestId | deep nesting→≤2 levels | domain object serialization→via DTO/VO
Rules
- Command/Query DTO separation — Write operations and read operations use independent DTOs; reusing the same structure is forbidden
- Controller protocol conversion — The Controller layer only performs HTTP protocol adaptation, and contains no business logic or domain calls
- Unified error code prefix — Business error 5-digit code: first digit category (4=client/5=server) + next two digits HTTP + last three digits specific error
- BFF responsibility boundaries — The BFF only performs data aggregation and format adaptation, and does not directly access the database or contain business rules
- Response wrapping — All API responses use Result<T> wrapping, with only 204 No Content and file downloads as exceptions
FAQ
| Question | Answer |
|---|---|
| Can DO and DTO fields be reused if they are the same? | No. DO contains behavior, DTO is pure data, and their evolution directions differ. |
| Do all APIs need a unified response format? | Yes, only file downloads and 204 can be exceptions. |
| How should error codes be designed? | 5 digits: first digit category + next two digits HTTP + last three digits specific error. |
| When is a BFF needed? | When there are multiple frontend platforms or the frontend needs to combine data from multiple services. |
| How many levels deep can sub-resources be nested at most? | At most 2 levels; more than 2 levels indicates a problem with the aggregation boundary. |
| Can business logic be placed in the Controller? | No, it only performs protocol conversion. |
Keywords
CQRS API REST endpoint design PO DO DTO VO data object transformation unified response format BFF Backend for Frontend OpenAPI Swagger API versioning API security command query separation Result<T> response wrapper input validation rate limiting idempotency pagination design
References
- references/patterns/cqrs-api-design.md — CQRS API design
- references/examples-ref/data-object-transformation.md — PO↔DO↔DTO↔VO conversion
- references/patterns/BFF-design-pattern.md — BFF design pattern
- references/security/api-security-design.md — API security
- references/migration/api-versioning-strategies.md — Version management
- references/security/api-naming-conventions.md — Naming conventions
- references/examples-ref/unified-response-format.md — Unified response
- references/security/openapi-specification.md — OpenAPI 3.0 specification
- references/patterns/data-access-api.md — Data access layer API design
- references/patterns/idempotency-design.md — Idempotency design
- references/patterns/pagination-filtering-design.md — Pagination filtering
- references/architecture/partme-16-service-data-view.md — Collaboration relationships
- references/architecture/clean-ddd-hexagonal-hexagonal.md — Hexagonal architecture
- references/examples-ref/api-error-handling.md — Error handling
- references/security/api-rate-limiting.md — Rate limiting design
- references/examples-ref/event-driven-api.md — Event-driven API
Examples
- examples/order-api-design.md — Order service case
- examples/user-api-design.md — User service case: registration/login/profile + security design
- examples/BFF-aggregation-example.md — BFF aggregation case: multi-service data aggregation for the order details page
- examples/api-version-migration.md — API version migration case: full v1 → v2 process
- examples/payment-api-design.md — Payment service API case: refund idempotency, state machine, asynchronous notification
- examples/openapi-codegen-example.md — OpenAPI code generation case: Spec-first strategy
- examples/product-api-design.md — Product service API case
- examples/inventory-api-design.md — Inventory service API case
- examples/notification-api-design.md — Notification service API case
- examples/search-api-design.md — Search service API case
🧭 DDD Skills Journey
📍 Current:
ddd-api-designer— Step 4: API design and data conversion
Step 1 (awesome) → Step 2 (selector) → Step 3 (架构落地) → Step 4 (领域+CQRS+API) → Step 5 (审查) → Step 6 (辅助) → Step 7 (文档)
↑
⭐ ddd-api-designer: 领域模型 → REST API
← Previous stop: hand off to ddd-domain-designer skill — design the API after having the domain model first (Install: npx skills add full-stack-skills/ddd-skills --skill ddd-domain-designer).
→ Next stop: hand off to ddd-code-reviewer skill — review API design compliance (Install: npx skills add full-stack-skills/ddd-skills --skill ddd-code-reviewer).
🔗 Related: hand off to ddd-cqrs-architecture skill — CQRS in depth (Install: npx skills add full-stack-skills/ddd-skills --skill ddd-cqrs-architecture) | ddd-architecture-doc skill — OpenAPI documentation output (Install: npx skills add full-stack-skills/ddd-skills --skill ddd-architecture-doc).
Core principle: Command and Query are designed separately. Keep in mind the four-layer conversion chain PO→DO→DTO→VO: DTO is decoupled from domain objects, and VO is decoupled from the database structure.