Files
Chengdong Zhang e10cae5789 docs: API architecture ADRs, tech spec, and marketing capability tracker
Persist the modular-monolith decision (ADR 0001 handler/service/repo,
ADR 0002 keep REST) with the companion tech spec and the api-architecture
OpenSpec capability. Replace the finished mock-migration tracker with
docs/TBD-marketing.md listing the backend-less marketing domains still on
fixtures (coupons, favorites, account stats, seckill, collective, integral,
reviews). README and AGENTS.md point at the new docs.
2026-09-18 16:00:31 +08:00

2.9 KiB

api-architecture Specification

Purpose

Internal layout of vmall-api: a modular monolith (handler → service → repository) over a stable HTTP REST contract. Client-visible behavior of each domain remains in the other OpenSpec capabilities.

Requirements

Requirement: Bounded-context modules

Domain code SHALL live under apps/api/src/modules/<context>/ (identity, catalog, cart, order, fulfillment, billing, address, shop, content, currency, health). Shared HTTP helpers SHALL live under apps/api/src/http/. New features MUST NOT add sqlx to a deleted-style routes/ tree.

Scenario: new use case

  • WHEN a developer adds a write use case (for example checkout or set-default-address)
  • THEN the SQL and transaction live in a service and/or repository, and the Axum handler only authenticates, deserializes, and maps ApiResult to status + JSON

Scenario: simple list

  • WHEN the endpoint is a single SELECT with no extra rules
  • THEN the handler MAY call the repository directly without a dedicated service function

Requirement: Layer contracts

Services SHALL return ApiResult<Dto> and MUST NOT depend on axum::Json or StatusCode. Repositories SHALL accept &PgPool, &mut PgConnection, or &mut Transaction so one service can compose several writes. The crate MUST NOT introduce a generic persistence trait unless a second backend exists.

Scenario: checkout transaction

  • WHEN checkout locks SKUs, inserts orders, decrements stock, and clears the cart
  • THEN Postgres writes share one transaction in order::service, and Redis cart clear happens after commit

Requirement: Stable REST envelope

The public API SHALL remain resource-oriented REST under /api. Errors SHALL use {"error":{"code","message"}}. Unique and check constraint violations SHALL map to HTTP 409 unless the handler substitutes a domain message via unique_conflict.

Scenario: duplicate email

  • WHEN registration hits a unique email constraint
  • THEN the client receives 409 with code CONFLICT and a domain message, not a 500

Requirement: Shared use cases across surfaces

Customer, shop, and platform-admin reads of the same aggregate SHALL call one service with an explicit scope (for example OrderScope::{User, Shop, Admin}) instead of copying SQL per router.

Scenario: order list

  • WHEN GET /api/orders, GET /api/shop/orders, and GET /api/admin/orders run
  • THEN they share order::service::list and differ only by auth and scope

Requirement: Tests

HTTP integration tests under apps/api/tests/ SHALL remain the contract suite. State-machine use cases (checkout stock, illegal pay/cancel) SHALL also be covered at the service layer without requiring GraphQL.

Scenario: service checkout

  • WHEN a cart qty exceeds SKU stock
  • THEN order::checkout returns ApiError::Conflict and no order row is committed