Skip to content

Implement Architecture Decision Records (ADRs) #299

@nanotaboada

Description

@nanotaboada

Problem

This project makes several opinionated architectural and technology choices (Spring Boot over Quarkus/Micronaut, Spring Data JPA + SQLite, Spring Cache, Lombok, layered architecture, stadium-themed versioning) without documenting the why behind these decisions.

Pain points:

  • New contributors/learners must reverse-engineer the rationale behind technology choices
  • Architectural context is lost — no record of alternatives considered or trade-offs made
  • Risk of uninformed changes — future modifications may violate original design principles without understanding the constraints that shaped them
  • Missed learning opportunity — as a PoC demonstrating modern Java patterns, the reasoning behind choices is as valuable as the implementation itself
  • Difficult to justify forks/adaptations — others reusing this as a template can't make informed decisions about what to keep vs. change

This is especially problematic for a learning-focused PoC where understanding the "why" is critical for educational value.

Proposed Solution

Implement Architecture Decision Records (ADRs) using the Michael Nygard template to systematically document all architecturally significant decisions.

Benefits:

  • Better onboarding — new contributors understand context immediately
  • Enhanced learning value — learners see not just what was built, but why
  • Informed evolution — future changes can reference past decisions and their constraints
  • Knowledge preservation — rationale survives team/contributor changes
  • Best practice demonstration — shows proper architecture documentation for production systems
  • Easier forking — others can adapt this PoC with full context of original decisions

Suggested Approach

1. Directory Structure

docs/
  adr/
    README.md                                          # Index and overview
    0001-adopt-spring-boot.md
    0002-spring-data-jpa-sqlite.md
    0003-spring-cache-memory.md
    0004-layered-architecture.md
    0005-lombok-boilerplate-reduction.md
    0006-springdoc-openapi.md
    0007-docker-single-container.md
    0008-stadium-themed-versioning.md
    0009-uuid-surrogate-squad-number-natural-key.md
    0010-full-replace-put-no-patch.md
    0011-mixed-test-strategy.md
    0012-no-flyway-manual-ddl-dml-scripts.md
    template.md                                        # ADR template for future decisions

2. ADR Template

Use the Michael Nygard format:

# ADR-NNNN: {Title}

Date: YYYY-MM-DD

## Status

{Proposed | Accepted | Deprecated | Superseded by ADR-XXXX}

## Context

{Describe the forces at play: technical, business, team constraints.
What problem needs solving? What alternatives exist?}

## Decision

{State the decision in full sentences with active voice: "We will..."}

## Consequences

{Describe the resulting context after applying this decision:
- Positive outcomes
- Negative trade-offs
- Neutral implications
All affect the project going forward.}

3. Initial ADRs to Create (Retroactive)

ADRs are numbered in the order decisions were made (or, for retroactive records, the order that best reflects foundational-to-incidental progression). Each record is fully self-contained — no ADR depends on another.

ADRs 0001–0008 document existing, already-implemented decisions (status: Accepted):

  1. ADR-0001: Adopt Spring Boot as REST API Framework

    • Context: Need a production-grade Java framework for a CRUD REST API
    • Alternatives: Quarkus, Micronaut, Jakarta EE + Jersey, plain Spring MVC
    • Decision: Spring Boot 4.0.0
    • Why: Largest ecosystem, auto-configuration, embedded server, wide industry adoption, cross-language comparison favours the dominant framework per ecosystem
  2. ADR-0002: Use Spring Data JPA with SQLite

    • Context: Need persistence with ORM support and multi-database compatibility
    • Alternatives: Plain JDBC, MyBatis, jOOQ, PostgreSQL as primary
    • Decision: Spring Data JPA + Hibernate + SQLite (file-based runtime, in-memory for tests)
    • Why: Demonstrates JPA patterns, SQLite requires no external service, in-memory variant gives fast isolated tests, community Hibernate dialect bridges SQLite gap
  3. ADR-0003: Implement In-Memory Caching with Spring Cache

    • Context: Need caching for GET endpoints without external infrastructure
    • Alternatives: Redis, Caffeine, Hazelcast, no caching
    • Decision: Spring @Cacheable with simple in-memory provider (no expiry)
    • Why: Zero external dependencies, built into Spring Boot, sufficient for PoC, demonstrates the pattern
  4. ADR-0004: Adopt Layered Architecture (Controller → Service → Repository → JPA)

    • Context: Need clear separation of concerns across HTTP, business, and data layers
    • Alternatives: Hexagonal/ports-and-adapters, CQRS, flat structure
    • Decision: Strict 3-layer: controllers delegate to services, services own business logic and transactions, repositories handle data access
    • Why: Industry-standard Spring idiom, highly testable per layer, appropriate complexity for PoC, matches patterns learners will encounter in real projects
  5. ADR-0005: Use Lombok to Reduce Boilerplate

    • Context: Java verbosity in entity/DTO classes with getters, setters, constructors, and builders
    • Alternatives: Java Records, manual boilerplate, MapStruct only
    • Decision: Lombok (@Data, @Builder, @RequiredArgsConstructor, @AllArgsConstructor)
    • Why: Dramatically reduces noise, constructor injection via @RequiredArgsConstructor enforces good DI habits, widely adopted in enterprise Spring projects
  6. ADR-0006: Use SpringDoc OpenAPI 3 for API Documentation

    • Context: Need interactive, standards-compliant API documentation
    • Alternatives: springfox (unmaintained), hand-written OpenAPI YAML, no docs
    • Decision: SpringDoc OpenAPI 3 with Swagger UI
    • Why: Actively maintained, Spring Boot 3+ compatible, auto-generates from annotations, interactive UI at /swagger-ui.html
  7. ADR-0007: Single-Container Docker Deployment

    • Context: Need containerisation strategy for the API
    • Alternatives: Docker Compose multi-service, Kubernetes, no containers
    • Decision: Single container with a bind-mounted volume for the SQLite file
    • Why: Simplicity, appropriate for PoC, demonstrates Docker patterns without orchestration overhead
  8. ADR-0008: Use Stadium-Themed Semantic Versioning

    • Context: Need a memorable release naming convention consistent with the cross-language comparison series
    • Alternatives: Standard semver only, animal names, city names
    • Decision: Famous football stadiums A–Z
    • Why: Fun, memorable, culturally universal for a football-themed domain, alphabetical progression makes ordering obvious

ADRs 0009–0012 document forward-looking or in-flight decisions. They start as Proposed and are updated to Accepted once the corresponding issue is implemented:

  1. ADR-0009: Demote UUID to Surrogate Key and Promote Squad Number to Natural Key (tracks Migrate Player PK from Long to UUID #268)

    • Context: The Player entity uses a sequential Long PK — predictable, not globally unique, and semantically wrong. Squad numbers are the stable, domain-meaningful identifier for players within a team. UUID provides global uniqueness without the drawbacks of sequential IDs, but should not be the mutation key because it is opaque to API consumers.
    • Alternatives: Keep Long PK, use UUID as PK (exposed in all endpoints), composite key
    • Decision: UUID becomes a non-PK surrogate (unique, non-updatable, generated at application level via @PrePersist). squadNumber is promoted to @Id. PUT /players/{squadNumber} and DELETE /players/{squadNumber} use squad number as path variable. GET /players/{id} (UUID) is retained for admin/internal lookup only.
    • Why: Squad numbers are natural keys — unique per team, stable, and meaningful to consumers. UUID provides global uniqueness for distributed scenarios without exposing sequential counters. Keeping UUID accessible via a dedicated admin endpoint preserves internal traceability without burdening the public API.
  2. ADR-0010: Full-Replace PUT, No PATCH (status: Accepted)

    • Context: HTTP defines PUT (full replacement) and PATCH (partial update). Both are common in REST APIs. The choice affects the DTO design (all fields required vs optional), the service layer logic (overwrite all vs merge), and the client contract.
    • Alternatives: PATCH only, both PUT and PATCH, PUT with optional fields
    • Decision: PUT performs a full replacement — the request body represents the complete new state of the resource. No PATCH endpoint is provided.
    • Why: Simpler to implement correctly; no merge logic, no ambiguity about absent fields, no need to choose a patch format (JSON Merge Patch vs JSON Patch). For a PoC with a small, flat domain model, requiring the full resource on update is not a meaningful burden for clients. Consistent with the same decision made across the Python and Go sibling projects.
  3. ADR-0011: Mixed Test Strategy (MockMvc Unit Tests + In-Memory SQLite Integration Tests) (status: Accepted)

    • Context: Testing strategies range from fully mocked unit tests (fast, isolated, risk of mock divergence) to fully integrated tests against a real database (high confidence, slower). Two distinct scenario categories exist: happy paths and expected error branches (400, 404, 409) that can be exercised with a real database, and infrastructure-level error branches (500s from service or repository failures) that require controlled failure injection.
    • Alternatives: Unit tests with mocks only, integration tests only, TestContainers with PostgreSQL
    • Decision: Use MockMvc + Mockito for controller and service unit tests — including 500-level error branches unreachable with a healthy database. Use in-memory SQLite (auto-cleared after each test) for integration tests covering the full request/response cycle. The boundary is explicit: if a scenario can be triggered with a real database, it uses the real database.
    • Why: In-memory SQLite gives near-zero setup cost with real constraint enforcement. Mockito covers error branches that cannot be triggered otherwise. Consistent with Go's mixed strategy. Avoids the false confidence documented in the TypeScript sibling project, where mocked Sequelize tests passed while real queries failed.
  4. ADR-0012: No Flyway — Manual DDL/DML Scripts (tracks Implement Flyway for database migrations #130)

    • Context: Spring Boot projects typically use Flyway (Apache 2.0) or Liquibase for versioned schema migrations. The alternatives considered: Flyway (versioned, reversible migrations, strong Spring Boot integration), Liquibase (XML/YAML/SQL changelogs, broader database support), manual ddl.sql / dml.sql scripts executed at startup or test setup with no migration history.
    • Alternatives: Flyway, Liquibase, Spring Boot spring.sql.init with manual scripts (current)
    • Decision: Use manual ddl.sql (schema) and dml.sql (seed data) scripts for now. Migration to Flyway is tracked in issue Implement Flyway for database migrations #130 and will be revisited alongside the PostgreSQL support work in issue Add PostgreSQL support with unified migration-based initialization #286.
    • Why: For a PoC with a stable schema and a single committed SQLite file, Flyway adds configuration overhead without meaningful benefit today. When PostgreSQL support is introduced (Add PostgreSQL support with unified migration-based initialization #286), versioned migrations become necessary — at that point Flyway is the natural choice given its first-class Spring Boot auto-configuration and Apache 2.0 license.

4. Integration with Existing Docs

  • Update README.md — Add "Architecture Decisions" section linking to docs/adr/
  • Update .github/copilot-instructions.md — Reference ADRs in an "Additional Resources" section
  • Add guideline — New architecturally significant decisions must include an ADR (note in contributing guidance)

5. File Modifications Required

Create docs/adr/README.md

# Architecture Decision Records (ADR)

This directory contains records of architecturally significant decisions made in this project.

## Index

| ADR | Title | Status |
|-----|-------|--------|
| [ADR-0001](0001-adopt-spring-boot.md) | Adopt Spring Boot as REST API Framework | Accepted |
| [ADR-0002](0002-spring-data-jpa-sqlite.md) | Use Spring Data JPA with SQLite | Accepted |
| [ADR-0003](0003-spring-cache-memory.md) | Implement In-Memory Caching with Spring Cache | Accepted |
| [ADR-0004](0004-layered-architecture.md) | Adopt Layered Architecture | Accepted |
| [ADR-0005](0005-lombok-boilerplate-reduction.md) | Use Lombok to Reduce Boilerplate | Accepted |
| [ADR-0006](0006-springdoc-openapi.md) | Use SpringDoc OpenAPI 3 for API Documentation | Accepted |
| [ADR-0007](0007-docker-single-container.md) | Single-Container Docker Deployment | Accepted |
| [ADR-0008](0008-stadium-themed-versioning.md) | Use Stadium-Themed Versioning | Accepted |
| [ADR-0009](0009-uuid-surrogate-squad-number-natural-key.md) | Demote UUID to Surrogate Key and Promote Squad Number to Natural Key | Proposed |
| [ADR-0010](0010-full-replace-put-no-patch.md) | Full-Replace PUT, No PATCH | Accepted |
| [ADR-0011](0011-mixed-test-strategy.md) | Mixed Test Strategy | Accepted |
| [ADR-0012](0012-no-flyway-manual-ddl-dml-scripts.md) | No Flyway — Manual DDL/DML Scripts | Proposed |

## Resources

- [Michael Nygard's ADR article](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions)
- [ADR GitHub organization](https://adr.github.io/)
- [Microsoft Azure Well-Architected: ADRs](https://learn.microsoft.com/en-us/azure/well-architected/architect-role/architecture-decision-record)

Acceptance Criteria

  • Directory structure created: docs/adr/ with README.md and template.md
  • ADRs 0001–0008, 0010–0011 written with status Accepted using Michael Nygard template format
  • ADR-0009 written with status Proposed, referencing issue Migrate Player PK from Long to UUID #268; updated to Accepted once Migrate Player PK from Long to UUID #268 is implemented
  • ADR-0012 written with status Proposed, referencing issue Implement Flyway for database migrations #130; updated to Accepted once Implement Flyway for database migrations #130 is implemented
  • README.md updated with "Architecture Decisions" section linking to docs/adr/
  • .github/copilot-instructions.md updated with reference to ADRs in an "Additional Resources" section
  • ADRs are discoverable: reachable via README, copilot instructions, or file explorer
  • ADRs are educational: each ADR clearly explains alternatives considered and trade-offs made
  • Template provided: docs/adr/template.md exists for future ADRs
  • CHANGELOG.md updated: entry in [Unreleased] section under "Added"

References

ADR Resources

Tooling (Optional)

  • adr-tools — Command-line tools for managing ADRs
  • log4brains — Modern ADR toolchain with architecture knowledge base

Related Issues


Note: This feature aligns with the project's philosophy of being a learning-focused PoC by making architectural reasoning explicit and accessible to learners and contributors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or requestjavaPull requests that update Java code

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions