Files
NowChessSystems/docs/adr/ADR-002-api-shared-models.md
T

87 lines
3.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-002: Shared-Models Library (`modules/api`)
## Status
Accepted
## Context
NowChessSystems is a microservice platform. As soon as two or more services need to
exchange data — whether through REST, messaging, or internal function calls — they must
agree on common data types. Without a shared home for those types, the same case class
(e.g. `Square`, `Move`, `GameState`) is duplicated in every module, diverges over time,
and causes silent serialisation mismatches at runtime.
The `core` module currently owns the chess engine logic. Future modules (matchmaking,
game history, user management, notation export, etc.) will all need to refer to the
same chess domain vocabulary. A cross-cutting place to hold that vocabulary is therefore
required before any second service is built.
## Decision
We introduce `modules/api` as a **shared-models library**: a plain Scala 3 library
(no Quarkus, no Jakarta, no persistence) that contains only:
- Pure Scala 3 data types: `case class`, `sealed trait`, and `enum` definitions
- Value objects that model the chess domain (pieces, colors, squares, moves, game state)
- Cross-service API envelope types (`ApiResponse[A]`, `ApiError`, `Pagination`)
- Minimal player/user identity stubs (IDs and display names only)
Every service module that needs these types declares:
```kotlin
implementation(project(":modules:api"))
```
in its own `build.gradle.kts`. The `modules/api` module itself carries no runtime
dependencies beyond the Scala 3 standard library.
### Package layout
```
de.nowchess.api
├── board Color, PieceType, Piece, File, Rank, Square
├── game CastlingRights, GameState, GameResult, GameStatus
├── move MoveType, Move, PromotionPiece
├── player PlayerId, PlayerInfo
└── response ApiResponse, ApiError, Pagination
```
## What belongs in `modules/api`
| Belongs | Does NOT belong |
|---|---|
| `case class`, `sealed trait`, `enum` for chess domain | Quarkus `@ApplicationScoped` beans |
| API envelope types (`ApiResponse`, `ApiError`) | Jakarta Persistence entities (`@Entity`) |
| Player identity stubs (ID + display name) | REST resource classes |
| FEN/board-state representation types | Business logic, engine algorithms |
| Pure type aliases and value objects | Database queries or repositories |
The rule of thumb: if a type carries a framework annotation or requires I/O to produce,
it does not belong in `modules/api`.
## How other modules depend on it
1. `modules/api` is a regular Gradle subproject already declared in `settings.gradle.kts`.
2. Consuming modules add `implementation(project(":modules:api"))` — nothing else.
3. Because `modules/api` has no Quarkus BOM, consuming modules must not re-export Quarkus
transitive dependencies through it.
4. If a future module needs JSON serialisation, it adds its own JSON library (e.g.
`circe`, `jsoniter-scala`) as a dependency and derives codecs for the shared types
there — codec derivation stays out of `modules/api`.
## Consequences
### Positive
- Single source of truth for all chess domain vocabulary.
- Adding a new microservice requires only one `implementation(project(":modules:api"))`
line — no copy-paste of types.
- The library is fast to compile (no framework processing) and cheap to test in isolation.
- Enforces a strict boundary: if a type needs a framework annotation it is forced into the
correct service module.
### Negative / Risks
- Any breaking change to a shared type (rename, field removal) is a cross-cutting change
that touches every consuming module simultaneously.
- Developers must resist the temptation to add convenience methods or logic to these
types; discipline is required to keep the library pure.