7.1 KiB
To Be Played (TBP) - Core Service
This is the Core Service for the "To Be Played" project. It acts as the central modulith, handling all business logic in vertical micro-service ready slices.
🚀 Tech Stack
- Language: Java 25
- Framework: Spring Boot 4.1.x
- Security: Spring Security & JSON Web Tokens (JWT)
- Database: PostgreSQL 17
- Migrations: Flyway
- Observability: Spring Boot Actuator & Micrometer (Prometheus)
- Build Tool: Maven Wrapper (
mvnw) - CI/CD: Gitea Actions (Forgejo)
🏗️ Architecture (Clean Architecture)
The project strictly follows a Domain-Driven / Clean Architecture (Ports & Adapters) approach to ensure high maintainability and testability. The codebase is divided into three main layers to prevent framework lock-in and keep business logic isolated.
Layer Breakdown
-
API / Web Layer (
api)- Contains REST endpoints, DTOs (
Request/Responserecords), and Global Exception Handlers. - Uses
ApiMapperto map between DTOs and Domain Models. - Rule: This layer delegates to the Service layer and never accesses persistence directly.
- Contains REST endpoints, DTOs (
-
Business / Core Layer (
model,service,exception)- The heart of the application. Contains pure Java classes (Models) and Business Logic (Services).
- Throws specific business exceptions (e.g.,
InvalidCredentialsException). - Rule: This layer has no dependencies on the API or Persistence layer. It defines interfaces that the persistence layer must implement.
-
Persistence Layer (
persistence)- Handles database operations. Contains JPA Entities (
UserEntity), Spring Data Repositories, and theEntityMapper. - Implements the interfaces defined in the Domain layer.
- Rule: Entities never leak into the API layer.
- Handles database operations. Contains JPA Entities (
Architectural boundaries are enforced via ArchUnit (src/test/java/core_service/architecture/ArchitectureTest.java), which runs as part of the standard ./mvnw test quality gate. Abstract test bases live under core_service.shared.test (AbstractUnitTest, AbstractIntegrationTest, AbstractPersistenceTest, AbstractWebMvcTest, AbstractArchitectureTest).
Core-specific structure note. Unlike
auth-service, Core keeps JPA@Entityclasses in..domain..(rather than a separate..persistence.entities..package) and repository interfaces in..repo... The ArchUnit rules are adapted to this layout. See "Roadmap / Open Topics" below for the planned tightening.
⚙️ Local Development
Running the application locally is fully automated via Spring Boot's Docker Compose Support.
Prerequisites
- Java 25 installed
- Docker & Docker Compose running
How to start
- Simply run the application from your IDE (e.g., IntelliJ) via the
CoreServiceApplication.javamain class. - Spring Boot will automatically detect the
compose.yamland spin up a local PostgreSQL container (tbp_db_local) on port5432. - Flyway will automatically apply all database migrations located in
src/main/resources/db/migration. - The application will be available at
http://localhost:8090.
To stop the environment and clean the database volume (if needed):
docker compose down -v
🔐 API Versioning
All API endpoints utilize Query Parameter Versioning to keep URLs clean and manageable. Example: GET /api/users/me?v=1
🐳 CI/CD Pipeline
The project uses Gitea Actions for Continuous Integration and Deployment.
Quality Gate: Runs ./mvnw clean verify to ensure tests and builds pass.
Docker Build: Packages the application using a multi-stage Dockerfile.
Registry: Pushes the lightweight Alpine-based image to the private Gitea container registry (git.andreas-dahm.eu/lobo_corp/tbp-core-service:latest).
📈 Observability
Metrics and health checks are exposed out-of-the-box via Spring Boot Actuator:
Health: http://localhost:8090/actuator/health
Prometheus Metrics: http://localhost:8090/actuator/prometheus
🗺️ Roadmap / Open Topics
Tracking known work that is not yet implemented. Each item is independently actionable.
Architecture / ArchUnit follow-ups
Currently active rules cover: cycles, controller→repository direction, repository naming, controller/service annotations, shared/configuration isolation, and a relaxed slice rule. The following rules are intentionally not yet enforced because they would fail today; addressing them is a separate refactor.
- Strict module independence.
librarycurrently imports fromsteam_adapter(e.g.SteamApiService, response DTOs). The ArchUnit rule exempts that edge viaignoreDependency(library.., steam_adapter..). Long-term: either movesteam_adapterout ofmodules(intocore_service.adapters.steamorcore_service.shared.steam) to make its role as a shared outbound port explicit, then enable strictnotDependOnEachOther()without exemptions. - "DTOs only in
..api.." rule. Steam adapter response records currently live in..api.dto..and are consumed bylibraryservices. Move them tocore_service.modules.steam_adapter.model(or.port) so they read as domain types, not API DTOs, then re-enable the strict rule. - "Entities only in
..persistence.." rule. Skipped because Core mixes entities with the domain package. Decide whether to (a) accept that pattern and keep the rule off, or (b) split entities into..repo.entity..and re-enable the rule for consistency withauth-service.
Security
- Drop the shared
JWT_SECRET. Today both Auth and Core hold the same HMAC secret, which means a Core compromise lets an attacker forge tokens for Auth. Once Auth ships EdDSA signing (see Auth README roadmap), Core should hold only the public key (JWT_PUBLIC_KEY) and verify withJwts.parser().verifyWith(publicKey).build(). TouchesJwtConfig(renamesecret→publicKey),JwtService(replaceKeys.hmacShaKeyForwithKeyFactory.getInstance("Ed25519")), and the deployment env wiring. - Consume a JWKS endpoint once Auth exposes one (
GET https://auth.tbp.andreas-dahm.eu/.well-known/jwks.json). Cache keys at startup, refresh onkidmiss. Avoids redeploying Core when keys rotate. - Add a
JwtServiceTest. No verification tests exist for Core's JWT parser today; adding one is also a natural place to add the EdDSA key fixture.
Testing
- First Steam adapter integration test.
SteamApiServiceTestis a unit test; an integration test with WireMock (or Spring'sMockRestServiceServer) would lock in the contract with the external Steam API and the Resilience4j retry/rate-limit configuration. - Persistence-layer tests.
library/repo/*Repositoryhas noAbstractPersistenceTest-based test today. Good first targets:UserGameRepository,UserSteamSyncRepository. - Controller WebMvc tests.
LibraryController,PublicStatsController,SteamControllerhave noAbstractWebMvcTest-based tests yet. The Auth project's*ControllerTestclasses are the pattern to follow.
CI
- Wire the ArchUnit + scaffolding into the existing Forgejo Actions quality gate.
./mvnw verifyalready runs them; confirm CI fails (not just warns) on architecture violations so drift is caught before merge.