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>
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
BaseControllerinterface and return typedMono<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/R2dbcEntityTemplateand 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 bydc3-common-constant. - Keep framework- or capability-specific public helpers in the narrowest owning module, such as gRPC conversion in
dc3-common-api, WebFlux helpers indc3-common-web, and broker adapters in thedc3-mq-*family. - Keep constants and nested enums used by only one module, protocol, configuration object, or implementation beside that
owner. Reserve top-level
*Constantclasses and top-level public enums fordc3-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-constantand migrate callers together. - Preserve the dependency floor:
dc3-common-constantmust not depend on other DC3 modules, anddc3-common-publicmust 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 NULLshortcuts 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, ordc3-common-facade-local-manager. dc3-common-facade-localis 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:
- Update the
.protofile. - Compile the affected API module to regenerate sources.
- Update server implementations and client builders/stubs together.
- Preserve backward compatibility where practical.
- 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, andremarkin English. - Treat driver
codevalues 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, andlist(Q). - Reserve
select*for raw store persistence operations. - Do not introduce
find*,query*, orfetch*as primary CRUD verbs. - HTTP paths are lowercase snake_case and mirror Java names.
- Use
getStatusByPage(Q)for status maps anddispatchRead/dispatchWritefor 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, orStringflags when a domain enum exists or should exist. *FlagEnumis for boolean-like toggles,*StatusEnumfor state machines, and*TypeEnumfor classifications.- Enum constants use descriptive
UPPER_SNAKE_CASE; enumcodevalues 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
GroupedOpenApibean, gateway aggregation route, and Swagger UI entry. - Shared WebFlux/springdoc configuration belongs in
dc3-common-weband must be registered throughAutoConfiguration.importswhen 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
@ConfigurationPropertiesover scattered@Valuefields. - YAML deployment values use
${ENV:default}placeholders. - WebFlux base paths use
spring.webflux.base-path, notserver.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.