Files
pnokerandClaude Code 11a8a8f775 docs(agents): split task-area conventions into dc3/agents guides
AGENTS.md stays lean with always-relevant rules and a task routing
table; the deep per-area conventions move to
dc3/agents/{backend,frontend,cli,testing}.md to be read on demand.
CONTRIBUTING.md documents the changelog-only commit rule and the
generated-CHANGE.md editing policy.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-07 19:33:30 +08:00

8.7 KiB

Backend conventions

Read this when changing Java modules, REST/gRPC contracts, or drivers. Entry point and cross-cutting rules: AGENTS.md.

Layering

Business modules follow this flow:

Controller (WebFlux) -> Service (BO) -> Reactive repository (R2DBC)
  • Controllers implement the BaseController interface and return typed Mono<T>/Flux<T> payloads.
  • Keep the request path non-blocking; use Reactor-native R2DBC publishers end to end.
  • Services extend BaseService<B, Q>, work in business objects, and own business rules.
  • Reactive repositories use DatabaseClient/R2dbcEntityTemplate and explicit tenant predicates.
  • Do not expose persistence objects from controllers or facade contracts.

Common types:

Type Module Role
BaseService<B,Q> dc3-common-public base CRUD service contract
BaseController dc3-common-web reactive controller helpers and user/tenant context
RFC 9457 problem details dc3-common-web standard HTTP error body (application/problem+json)
BaseBO, BaseVO, BaseDTO dc3-common-model shared business, web, and transfer fields
BaseBuilder dc3-common-model MapStruct conversion base
TenantOwned dc3-common-public marker for tenant-scoped entities

Shared code placement

Place code according to ownership and dependency direction, not Java visibility. A public method is not automatically shared infrastructure.

  • Put framework-neutral contracts, request/pagination/tree entities, tenant markers, and broadly reusable utilities in dc3-common-public.
  • Put platform-wide constants and top-level shared domain/wire/persistence enums in dc3-common-constant.
  • Put BO/VO/DTO bases, builders, validation groups, extensions, and shared transport models in dc3-common-model; those models may reference enums owned by dc3-common-constant.
  • Keep framework- or capability-specific public helpers in the narrowest owning module, such as gRPC conversion in dc3-common-api, WebFlux helpers in dc3-common-web, and broker adapters in the dc3-mq-* family.
  • Keep constants and nested enums used by only one module, protocol, configuration object, or implementation beside that owner. Reserve top-level *Constant classes and top-level public enums for dc3-common-constant; use a concern-specific local name such as *Limits/*Defaults, a nested enum, or a private field until the concept becomes a stable cross-module contract.
  • Do not duplicate cross-module wire names, header names, routing identifiers, cache-key fragments, or persistence codes. Define one canonical symbol in dc3-common-constant and migrate callers together.
  • Preserve the dependency floor: dc3-common-constant must not depend on other DC3 modules, and dc3-common-public must not depend on capability modules.

Tenant safety

Tenant isolation is a hard requirement.

  • Preserve tenant scope in every new query and mutation.
  • Carry tenant IDs through gRPC requests whenever the contract supports them.
  • Include tenant context in cache keys for tenant-owned data.
  • Validate ownership before returning or mutating data across service boundaries.
  • Do not add tenantId IS NULL shortcuts unless the data model explicitly defines global records.
  • Treat missing tenant validation as a correctness and security defect, not a convenience trade-off.

Facade boundaries

Business code must use facade interfaces for cross-service calls.

  • Contracts belong in dc3-common-facade-api.
  • Transport-backed implementations belong in dc3-common-facade-grpc.
  • In-process implementations belong in the matching module: dc3-common-facade-local-auth, dc3-common-facade-local-data, or dc3-common-facade-local-manager.
  • dc3-common-facade-local is a dependency aggregator and does not contain implementation sources.
  • Keep controllers and services independent from transport details unless they are explicit transport adapters.

gRPC contracts

Proto files live under dc3-api/*/src/main/protobuf.

When changing a contract:

  1. Update the .proto file.
  2. Compile the affected API module to regenerate sources.
  3. Update server implementations and client builders/stubs together.
  4. Preserve backward compatibility where practical.
  5. Verify tenant propagation and standard gRPC status errors.

Servers are Spring beans extending generated *ImplBase classes. Reuse shared stub configuration; do not construct ad hoc channels in business code.

Driver SDK

Drivers implement protocol behaviour through the SPI types in dc3-common-driver; shared runtime services handle registration, scheduling, and value dispatch.

Primary extension points are DriverProtocol, DriverLifecycle, DriverMetadataListener, DriverHealth, DeviceHealth, and DriverCommand. Prefer existing SDK plumbing over driver-specific infrastructure.

Driver application.yml metadata is user-facing:

  • Keep name, attribute-name, and remark in English.
  • Treat driver code values as routing-stable identifiers. Changing one requires a metadata and RabbitMQ migration plan.

CRUD verbs

CRUD-shaped names reflect result cardinality across Service, Controller, Facade, gRPC server, and proto RPCs:

Action Java HTTP gRPC
create one add(BO) /add n/a
delete by ID delete(Long) /delete n/a
update one update(BO) /update n/a
return one getXxx(...) /get_xxx GetXxx
return many listXxx(...) /list_xxx ListXxx
  • Base CRUD comes from BaseService<B,Q>: add, delete, update, getById, and list(Q).
  • Reserve select* for raw store persistence operations.
  • Do not introduce find*, query*, or fetch* as primary CRUD verbs.
  • HTTP paths are lowercase snake_case and mirror Java names.
  • Use getStatusByPage(Q) for status maps and dispatchRead/dispatchWrite for command dispatch, following existing contracts.

Models and enums

  • DOs model database storage; BOs model business semantics; VOs/DTOs model web or transport input/output.
  • Persistent write paths accept BOs in services. Controllers and transport adapters convert VO/DTO input to BO.
  • Read-only projections may return VOs directly when a duplicate BO would add no business meaning.
  • Use MapStruct builders for VO/BO/DO conversion, including enum/index conversion.
  • Do not leak database-coded Byte, Integer, or String flags when a domain enum exists or should exist.
  • *FlagEnum is for boolean-like toggles, *StatusEnum for state machines, and *TypeEnum for classifications.
  • Enum constants use descriptive UPPER_SNAKE_CASE; enum code values use lowercase tokens.
  • Do not introduce magic flag constants such as private static final Byte DEFAULT = 1.
  • Do not expose secrets in VOs. Exclude apiKey, password, secret, token, and credential fields from serialization and Lombok @ToString.

Web API and OpenAPI

  • Controllers return typed payloads and never expose DOs; errors use RFC 9457 problem details.
  • Apply grouped validation consistently and keep validation/exception messages in English.
  • Document REST endpoints with springdoc annotations; do not maintain a parallel handwritten OpenAPI spec.
  • Each business controller package needs the appropriate GroupedOpenApi bean, gateway aggregation route, and Swagger UI entry.
  • Shared WebFlux/springdoc configuration belongs in dc3-common-web and must be registered through AutoConfiguration.imports when component scanning will not discover it.
  • Docs are enabled in development-style profiles and disabled in production. Export a running stack with make openapi.

Configuration and logging

  • Custom configuration-property prefixes use dc3.*.
  • Prefer validated, typed @ConfigurationProperties over scattered @Value fields.
  • YAML deployment values use ${ENV:default} placeholders.
  • WebFlux base paths use spring.webflux.base-path, not server.servlet.context-path.
  • Use English, stable event names and parameterized SLF4J messages.
  • Prefer structured fields such as tenantId={}, userId={}, deviceId={}.
  • Never log tokens, passwords, credentials, full request bodies, or raw private payloads at info level.
  • Pass caught exceptions to warn/error logs unless stack-trace suppression is intentional.