Architecture
flowchart LR
Console[React Vite management console] --> ReactClient[React client library]
ReactClient --> Api[Spring Boot API /api/v1/badge]
JavaClient[Java client library] --> Api
DartClient[Dart client library] --> Api
Api --> Service[BadgeService]
Service --> Repository[BadgeRepository]
Repository --> Database[(PostgreSQL badges)]
Docs[MkDocs] -. documents .-> Api
Slides[Marp presentation] -. explains .-> Docs
The repository demonstrates one vertical CRUD feature. HTTP controllers validate transport requests and delegate business operations to BadgeService. The service owns timestamps and upsert behavior, while BadgeRepository isolates Spring Data JPA access. The Java, TypeScript, and Dart clients depend on the public HTTP contract, not on persistence classes.
Public Object and Persistence Entity
Badge is the public business object returned by controllers and represented by each client. It is an immutable Java record with id, code, label, description, createdDate, and lastModifiedDate. This is the model API consumers should understand.
BadgeEntity is the internal mutable JPA representation. It maps to the PostgreSQL table badges and exists to satisfy persistence concerns such as a protected no-argument constructor, generated identity, column constraints, and dirty tracking. Controllers never expose the JPA entity.
The service maps between these types. This boundary keeps the public contract stable when persistence details change and prevents JPA behavior from leaking into client code.
Invariants and Ownership
| Concern | Owner | Behavior |
|---|---|---|
| Persistent identity | Server and database | id is a generated Long. |
| Business identity | Service and database | code is required, unique, and immutable. |
| Mutable business data | Client request | label is required; description may be null. |
| Audit timestamps | Service | Both dates are Instant values assigned by the server. |
| Bulk reconciliation | Service | Seed and import upsert by code and ignore imported IDs and dates. |
Schema Evolution
Hibernate is configured with spring.jpa.hibernate.ddl-auto: update for this runnable example. It can add or adjust the badges schema, but it does not remove tables that are no longer mapped. In particular, an existing demo volume may retain the obsolete badge_records table. The application does not use that table.
This convenience setting is not a migration strategy for production. Use versioned database migrations in a real service. For the demo, reset the Compose volume when a clean schema is required, as described in the deployment guide.