Files
clean-architecture-backend-…/docs/adr/ADR-GRPC-001-platform-family-and-registry-shape.md
T

56 lines
3.0 KiB
Markdown

# ADR-GRPC-001: The gRPC platform ships as a registered family, not as one adapter leaf
- Status: accepted
- Date: 2026-08-30
- Scope: `:grpc:*`, `:grpc-advanced:*`, `src/config/architecture/modules.json`
## Context
The two source plans describe a type-safe gRPC execution platform with its own API, SPI, adapters and
composition root: fifteen Stable modules under `modules/grpc` and sixteen Advanced ones under
`modules/grpc-advanced`, on Gradle Kotlin DSL, in package `io.backend.skeleton.grpc`, against
Spring Boot 4.1.
None of that layout exists here. This repository uses Groovy DSL, a fail-closed module registry that
owns the leaf list, package root `dev.caskeleton`, and Spring Boot 4.0.8. The plans anticipate this:
their last Global Constraint says that when the repository structure differs, file paths are remapped
and the public contracts, invariants and test meanings are not changed.
Two shapes were available. Fold the platform into the existing `:adapter:inbound:grpc` leaf as
packages — which is what the JPA, GraphQL, WebSocket and HTTP platforms did here — or register it as
a family the way `messaging:*` is registered.
## Decision
Register it as a family: twelve Stable leaves under `src/grpc/` and six Advanced ones under
`src/grpc-advanced/`.
The deciding property is that this is not a layer of this application. Root `CLAUDE.md` already
describes `messaging:*` as "a vendored messaging platform: a product with its own API, SPI, adapters
and composition boundary, not a layer of this application", and the gRPC platform is the same shape
for the same reason — the application is meant to reach it the way it reaches a library, through an
application-owned port. The four platforms that became packages are all layers of this application;
this one is not.
The split between `src/grpc/` and `src/grpc-advanced/` is not organisational. The Stable plan
requires that the Stable starter's build fail if it reaches an Advanced module, and separate Gradle
path prefixes make that a `verifyCleanArchitectureDependencies` failure rather than a review note:
`grpc-spring-boot-starter`'s registry entry names no advanced id, and it cannot acquire one silently.
## Consequences
**The registry grew from 44 leaves to 62.** That is a large registry change, made deliberately and in
one place. Every new leaf is `runtime_memberships: []`, so nothing ships until a second, explicit
decision moves it.
**The advanced boundary is checked twice.** Once by the registry at build time, and once by
`GrpcStableBuildInvariant` at runtime, because a fat jar or a shaded artifact is assembled by
something the registry never sees.
**Four testkit modules became four test lanes.** The plan's split exists so in-process results cannot
be mistaken for network results; this repository expresses that with `ca.strict-test-lane`, whose
lanes fail when they discover nothing and never serve an up-to-date result. `GrpcEvidenceGrade` keeps
the same rule inside the code, so a report cannot cite a contract run as transport evidence.
**Codegen is not wired.** See ADR-GRPC-002.