Set up development infrastructure for TDD: JPA + Liquibase for database migrations, Testcontainers for integration tests against real PostgreSQL, profile-based configuration (prod/local), and README deployment documentation with docker-compose example. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
20 KiB
date, git_commit, branch, topic, tags, status
| date | git_commit | branch | topic | tags | status | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-03-04T19:37:59.203261+00:00 | cb0bcad145 |
master | T-4: Development Infrastructure Setup |
|
complete |
Research: T-4 Development Infrastructure Setup
Research Question
What is the current state of the codebase relative to T-4's acceptance criteria? What already exists, what is missing, and what are the technical considerations for each criterion?
Summary
T-4 is the final infrastructure task before user story implementation can begin. It bridges the gap between the existing project scaffolds (T-1, T-2, T-3, T-5 — all complete) and actual feature development with TDD. The task covers six areas: database migration framework, database connectivity, environment variable configuration, SPA router, backend test infrastructure, frontend test infrastructure, docker-compose documentation, and container verification with PostgreSQL.
The codebase already has partial coverage: Vue Router exists with placeholder routes, frontend test infrastructure (Vitest + @vue/test-utils) is operational, and backend test infrastructure (JUnit 5 + Spring Boot Test + MockMvc) is partially in place. What's missing: JPA/Liquibase, Testcontainers, environment variable wiring, and docker-compose documentation.
Detailed Findings
AC 1: Database Migration Framework (Liquibase)
Current state: Not present. No Liquibase or Liquibase dependency in pom.xml. No migration files anywhere in the project. The CLAUDE.md explicitly states "No JPA until T-4."
What's needed:
- Add
spring-boot-starter-data-jpadependency tobackend/pom.xml - Add
liquibase-coredependency (Spring Boot manages the version) - Create changelog directory at
backend/src/main/resources/db/changelog/ - Create master changelog:
db.changelog-master.xmlthat includes individual changesets - Create first empty/baseline changeset to prove the tooling works
- Spring Boot auto-configures Liquibase when it's on the classpath and a datasource is available — no explicit
@Beanconfig needed
Spring Boot + Liquibase conventions:
- Default changelog location:
classpath:db/changelog/db.changelog-master.xml - Format: XML (chosen for schema validation and explicitness)
- Changelogs are DB-agnostic — Liquibase generates dialect-specific SQL at runtime
- Spring Boot 3.5.x ships Liquibase via its dependency management
- Liquibase runs automatically on startup before JPA entity validation
Architectural note: The hexagonal architecture has an existing adapter.out.persistence package (currently empty, with package-info.java). JPA repositories and entity classes will go here. Domain model classes remain in domain.model without JPA annotations — the persistence adapter maps between them. The existing ArchUnit tests already enforce this boundary.
AC 2: Database Connectivity via Environment Variables
Current state: application.properties has no datasource configuration. Only spring.application.name=fete and actuator settings.
What's needed:
- Configure Spring datasource properties to read from environment variables via profile-based separation
Chosen approach: Profile-based separation with generic env vars.
The properties are split across three files with clear responsibilities:
application.properties — environment-independent, always active:
spring.application.name=fete
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.open-in-view=false
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=never
application-prod.properties — committed, production profile, activated in Docker via ENV SPRING_PROFILES_ACTIVE=prod:
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DATABASE_USERNAME}
spring.datasource.password=${DATABASE_PASSWORD}
application-local.properties — gitignored, developer creates from .example template:
spring.datasource.url=jdbc:postgresql://localhost:5432/fete
spring.datasource.username=fete
spring.datasource.password=fete
application-local.properties.example — committed as template, never directly used.
Dockerfile:
ENV SPRING_PROFILES_ACTIVE=prod
Key points:
- No datasource defaults in
application.properties— if neither profile nor env vars are set, the app fails to start (intentional: no silent fallback to a nonexistent DB) - Generic env var names (
DATABASE_URL,DATABASE_USERNAME,DATABASE_PASSWORD) — the container user never sees Spring property names ddl-auto=validateensures Hibernate validates entities against the Liquibase-managed schema but never modifies itopen-in-view=falseprevents the anti-pattern of lazy-loading in views (also avoids Spring Boot's startup warning)- PostgreSQL JDBC driver (
org.postgresql:postgresql) is needed — Spring Boot manages the version - Tests use
@ServiceConnection(Testcontainers) which auto-configures the datasource — no profile or env vars needed for tests
AC 3: All Runtime Configuration via Environment Variables
Current state: No environment-variable-driven configuration exists beyond Spring Boot defaults.
What's needed beyond database:
- Unsplash API key: optional, used by US-16
- Max active events: optional, used by US-13
Implementation pattern: @ConfigurationProperties(prefix = "fete") class (FeteProperties) in de.fete.config. Type-safe, validatable, testable.
These properties also go in application-prod.properties with generic env var mapping:
fete.unsplash.api-key=${UNSPLASH_API_KEY:}
fete.max-active-events=${MAX_ACTIVE_EVENTS:0}
Empty UNSPLASH_API_KEY = feature disabled. MAX_ACTIVE_EVENTS=0 = unlimited.
Note: These properties are only scaffolded in T-4 (the FeteProperties class with fields and defaults). The business logic using them comes with US-13/US-16.
AC 4: SPA Router Configuration
Current state: Vue Router IS configured and operational.
File: frontend/src/router/index.ts
- Uses
createWebHistory(HTML5 History API — clean URLs, no hash) - Two routes defined:
/(HomeView, eager) and/about(AboutView, lazy-loaded) - Router is registered in
main.tsviaapp.use(router)
Backend SPA support: Already implemented in WebConfig.java:
PathResourceResolverfalls back toindex.htmlfor any path not matching a static file- This enables client-side routing — the backend serves
index.htmlfor all non-API, non-static paths
Assessment: This AC is effectively already met. The router exists, uses history mode, and the backend supports it. What will change during user stories: routes will be added (e.g., /event/:token, /event/:token/edit), but the infrastructure is in place.
AC 5: Backend Test Infrastructure
Current state: Partially in place.
What exists:
- JUnit 5 (via
spring-boot-starter-test) — operational - Spring Boot Test with
@SpringBootTest— operational - MockMvc for REST endpoint testing — operational (
FeteApplicationTest.java,WebConfigTest.java) - ArchUnit for architecture validation — operational (
HexagonalArchitectureTest.java) - Surefire configured with fail-fast (
skipAfterFailureCount=1) - Test logging configured (
logback-test.xmlat WARN level)
What's missing:
- Testcontainers — not in
pom.xml, no test configuration for it - Integration test support with real PostgreSQL — currently no database tests exist (because no database exists yet)
What's needed:
- Add
org.testcontainers:postgresqldependency (test scope) - Add
org.testcontainers:junit-jupiterdependency (test scope) — JUnit 5 integration - Add
spring-boot-testcontainersdependency (test scope) — Spring Boot 3.1+ Testcontainers integration - Create a test configuration class or use
@ServiceConnectionannotation (Spring Boot 3.1+) for automatic datasource wiring in tests
Spring Boot 3.5 + Testcontainers pattern (TestApplication):
A TestFeteApplication.java in src/test/ registers Testcontainers beans. All @SpringBootTest tests automatically get a PostgreSQL instance — no per-test wiring needed. Existing tests (FeteApplicationTest, WebConfigTest) continue to work without modification.
// src/test/java/de/fete/TestFeteApplication.java
@TestConfiguration(proxyBeanMethods = false)
public class TestcontainersConfig {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:17-alpine");
}
}
With @ServiceConnection, Spring Boot auto-configures the datasource to point at the Testcontainers-managed PostgreSQL — no manual URL/username/password wiring needed. Testcontainers starts one shared container per test suite run, not per test class.
Important: Once JPA is on the classpath, every @SpringBootTest needs a datasource. The TestApplication pattern ensures this globally. Without it, all existing @SpringBootTest tests would break immediately.
Test categories after T-4:
- Unit tests: no Spring context, no database — fast, test domain logic
- Integration tests:
@SpringBootTest+ Testcontainers — test full stack including database - Architecture tests: ArchUnit — already in place
AC 6: Frontend Test Infrastructure
Current state: Already in place and operational.
What exists:
- Vitest configured (
vitest.config.ts): jsdom environment, bail=1, e2e excluded @vue/test-utilsv2.4.6 installed — Vue component mounting and assertions- TypeScript test config (
tsconfig.vitest.json) with jsdom types - Sample test exists:
components/__tests__/HelloWorld.spec.ts— mounts component, asserts text - Test command:
npm run test:unit(runs Vitest in watch mode) /npm run test:unit -- --run(single run)
Assessment: This AC is already met. The test infrastructure is functional with a passing sample test.
AC 7: Both Test Suites Executable
Current state: Both work.
- Backend:
cd backend && ./mvnw test— runs JUnit 5 tests (3 tests in 3 classes) - Frontend:
cd frontend && npm run test:unit -- --run— runs Vitest (1 test in 1 file) - CI pipeline (
ci.yaml) already runs both in parallel
Assessment: Already met. Will remain met after adding Testcontainers (new tests use the same ./mvnw test command).
AC 8: README Docker-Compose Documentation
Current state: No docker-compose file or documentation exists. The README covers development setup and code quality but has no deployment section.
What's needed:
- A
docker-compose.ymlexample (either in-repo or documented in README) - Must include: app service (the fete container) + postgres service
- Must document required environment variables:
DATABASE_URL,DATABASE_USERNAME,DATABASE_PASSWORD - Must document optional environment variables:
UNSPLASH_API_KEY,MAX_ACTIVE_EVENTS - Per CLAUDE.md: "A docker-compose example in the README is sufficient" — no separate file in repo
Example structure:
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_DB: fete
POSTGRES_USER: fete
POSTGRES_PASSWORD: changeme
volumes:
- fete-db:/var/lib/postgresql/data
app:
image: gitea.example.com/user/fete:latest
ports:
- "8080:8080"
environment:
DATABASE_URL: jdbc:postgresql://db:5432/fete
DATABASE_USERNAME: fete
DATABASE_PASSWORD: changeme
# MAX_ACTIVE_EVENTS: 100 # optional
# UNSPLASH_API_KEY: abc123 # optional
depends_on:
db:
condition: service_healthy
volumes:
fete-db:
AC 9: Container Health Check with PostgreSQL
Current state: The Dockerfile has a HEALTHCHECK directive that queries /actuator/health. Currently the app starts without a database and the health check passes.
After T-4: With JPA and Liquibase on the classpath, Spring Boot will:
- Fail to start if no database is reachable (datasource auto-configuration fails)
- Include database health in
/actuator/healthautomatically (viaDataSourceHealthIndicator) - Run Liquibase migrations on startup — if migrations fail, the app won't start
What's needed for verification:
- Start the app with docker-compose (app + postgres)
- Verify
/actuator/healthreturns{"status":"UP"}(which now includes DB connectivity) - Verify Liquibase ran the baseline migration (check
flyway_schema_historytable or app logs)
Code References
Existing Files (will be modified)
backend/pom.xml:1-170— Add JPA, Liquibase, PostgreSQL driver, Testcontainers dependenciesbackend/src/main/resources/application.properties:1-4— Add datasource, JPA, Liquibase, app-specific configREADME.md:1-134— Add deployment section with docker-compose example
Existing Files (relevant context, likely untouched)
backend/src/main/java/de/fete/config/WebConfig.java:1-40— SPA routing already configuredbackend/src/main/java/de/fete/FeteApplication.java— Entry point, no changes neededfrontend/src/router/index.ts:1-23— Router already configuredfrontend/vitest.config.ts:1-15— Test infra already configuredfrontend/package.json:1-52— Test dependencies already present.gitea/workflows/ci.yaml— CI pipeline, may need Testcontainers Docker access for backend tests
New Files (to be created)
backend/src/main/resources/db/changelog/db.changelog-master.xml— Liquibase master changelogbackend/src/main/resources/application-prod.properties— Production profile with env var placeholdersbackend/src/main/resources/application-local.properties.example— Template for local developmentbackend/src/test/java/de/fete/TestFeteApplication.java(or similar) — Testcontainers PostgreSQL bean via TestApplication patternde/fete/config/FeteProperties.java—@ConfigurationPropertiesclass for app-specific settings- README deployment section — docker-compose example inline (no standalone file)
backend/src/main/resources/application-prod.properties— Production profile with env var placeholdersbackend/src/main/resources/application-local.properties.example— Template for local development
Package Structure (existing, will gain content)
de.fete.adapter.out.persistence— JPA entities and Spring Data repositories (empty now)de.fete.domain.model— Domain entities (empty now, no JPA annotations here)de.fete.config— App configuration (WebConfig exists, may add@ConfigurationPropertiesclass)
Architecture Documentation
Hexagonal Architecture and JPA
The existing ArchUnit tests (HexagonalArchitectureTest.java) enforce:
- Domain layer must not depend on Spring, adapters, application, or config
- Ports (in/out) must be interfaces
- Web adapter and persistence adapter must not cross-depend
This means JPA integration must follow the pattern:
- Domain entities in
domain.model— plain Java, no JPA annotations - JPA entities in
adapter.out.persistence— annotated with@Entity,@Table, etc. - Mapping between domain and JPA entities in the persistence adapter
- Repository interfaces (Spring Data) in
adapter.out.persistence - Port interfaces in
domain.port.out— define what the domain needs from persistence - Service implementations in
application.service— use port interfaces, not repositories directly
This is a well-established hexagonal pattern. The ArchUnit tests will automatically validate any new code follows these boundaries.
Test Architecture After T-4
Test Type | Context | Database | Speed | Purpose
-------------------|---------------|-----------------|---------|---------------------------
Unit tests | None | None | Fast | Domain logic, services
Integration tests | SpringBoot | Testcontainers | Medium | Full stack, DB queries
Architecture tests | None (static) | None | Fast | Structural validation
All test types run via ./mvnw test. Testcontainers starts/stops PostgreSQL containers automatically — no external setup needed. The CI pipeline already has Docker available (runs on ubuntu-latest with Docker socket).
CI Pipeline Considerations
The current CI pipeline runs ./mvnw -B verify for backend tests. Testcontainers requires Docker socket access. On Gitea Actions with ubuntu-latest runners, Docker is typically available. If the runner uses a Docker-in-Docker setup, the Testcontainers DOCKER_HOST environment variable may need configuration — but this is a runtime concern, not a code concern.
Spring Boot Profiles
Currently no profiles are configured. For T-4, a test profile may be useful to separate test-specific configuration (e.g., Testcontainers datasource) from production defaults. Spring Boot's @ActiveProfiles("test") on test classes or application-test.properties can handle this. However, with @ServiceConnection, Testcontainers auto-configures the datasource without profile-specific properties.
Acceptance Criteria Status Matrix
| # | Criterion | Current Status | Work Required |
|---|---|---|---|
| 1 | Liquibase configured with first changelog | Not started | Add liquibase-core, create changelog dir and master XML |
| 2 | External PostgreSQL via env var | Not started | Add datasource properties with env var placeholders |
| 3 | All runtime config via env vars | Not started | Add datasource + app-specific properties |
| 4 | SPA router configured | Done | Vue Router with history mode already works |
| 5 | Backend test infra (Testcontainers) | Partial | JUnit 5 + MockMvc exist; add Testcontainers |
| 6 | Frontend test infra | Done | Vitest + @vue/test-utils operational |
| 7 | Both test suites executable | Done | Both ./mvnw test and npm run test:unit work |
| 8 | README docker-compose documentation | Not started | Add deployment section with example |
| 9 | Container health with PostgreSQL | Not started | Verify after JPA/Liquibase are added |
Resolved Decisions
-
Liquibase for database migrations, XML format. DB-agnostic changelogs — Liquibase generates dialect-specific SQL at runtime. XML chosen over YAML for schema validation and explicitness. The project intentionally avoids PostgreSQL-specific features in migrations to keep the database layer portable.
-
Profile-based properties separation with generic environment variable names. Three files:
application.properties(environment-independent, always active),application-prod.properties(committed, maps${DATABASE_URL}etc. to Spring properties, activated in Docker viaENV SPRING_PROFILES_ACTIVE=prod),application-local.properties(gitignored, concrete local values, activated via-Dspring-boot.run.profiles=local). A committed.exampletemplate guides developers. The container user setsDATABASE_URL,DATABASE_USERNAME,DATABASE_PASSWORD— never sees Spring internals. -
@ConfigurationPropertiesfor app-specific settings (FetePropertiesclass). Type-safe, validatable, testable. Properties:fete.unsplash.api-key(fromUNSPLASH_API_KEY) andfete.max-active-events(fromMAX_ACTIVE_EVENTS). Both are only scaffolded in T-4; business logic using them comes with US-13/US-16. -
docker-compose example in README only — no standalone
docker-compose.ymlin the repo. Per CLAUDE.md: "A docker-compose example in the README is sufficient." A local docker-compose for development may be added later separately. -
TestApplication pattern for Testcontainers integration. A
TestFeteApplication.javainsrc/test/registers a@ServiceConnectionPostgreSQL container. All@SpringBootTesttests automatically get a database — existing tests continue to work without modification. -
README erweitern with local development setup documentation (how to copy
application-local.properties.example, start with profile, PostgreSQL prerequisites).
Open Questions
-
Testcontainers in CI: The Gitea Actions runner needs Docker available for Testcontainers. This works out-of-the-box on
ubuntu-latestbut should be verified after implementation. -
Baseline changelog content: The first Liquibase changeset should be a minimal, empty changeset that proves the tooling works. No schema needed yet — US-1 will create the first real table.