87 lines
3.7 KiB
Markdown
87 lines
3.7 KiB
Markdown
# 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.
|