Files
fete/specs/004-dev-infrastructure/research.md
nitrix 6aeb4b8bca Migrate project artifacts to spec-kit format
- Move cross-cutting docs (personas, design system, implementation phases,
  Ideen.md) to .specify/memory/
- Move cross-cutting research and plans to .specify/memory/research/ and
  .specify/memory/plans/
- Extract 5 setup tasks from spec/setup-tasks.md into individual
  specs/001-005/spec.md files with spec-kit template format
- Extract 20 user stories from spec/userstories.md into individual
  specs/006-026/spec.md files with spec-kit template format
- Relocate feature-specific research and plan docs into specs/[feature]/
- Add spec-kit constitution, templates, scripts, and slash commands
- Slim down CLAUDE.md to Claude-Code-specific config, delegate principles
  to .specify/memory/constitution.md
- Update ralph.sh with stream-json output and per-iteration logging
- Delete old spec/ and docs/agents/ directories
- Gitignore Ralph iteration JSONL logs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:19:41 +01:00

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
research
codebase
t4
database
liquibase
testcontainers
router
test-infrastructure
docker-compose
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-jpa dependency to backend/pom.xml
  • Add liquibase-core dependency (Spring Boot manages the version)
  • Create changelog directory at backend/src/main/resources/db/changelog/
  • Create master changelog: db.changelog-master.xml that 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 @Bean config 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=validate ensures Hibernate validates entities against the Liquibase-managed schema but never modifies it
  • open-in-view=false prevents 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.ts via app.use(router)

Backend SPA support: Already implemented in WebConfig.java:

  • PathResourceResolver falls back to index.html for any path not matching a static file
  • This enables client-side routing — the backend serves index.html for 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.xml at 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:postgresql dependency (test scope)
  • Add org.testcontainers:junit-jupiter dependency (test scope) — JUnit 5 integration
  • Add spring-boot-testcontainers dependency (test scope) — Spring Boot 3.1+ Testcontainers integration
  • Create a test configuration class or use @ServiceConnection annotation (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-utils v2.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.yml example (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/health automatically (via DataSourceHealthIndicator)
  • 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/health returns {"status":"UP"} (which now includes DB connectivity)
  • Verify Liquibase ran the baseline migration (check flyway_schema_history table or app logs)

Code References

Existing Files (will be modified)

  • backend/pom.xml:1-170 — Add JPA, Liquibase, PostgreSQL driver, Testcontainers dependencies
  • backend/src/main/resources/application.properties:1-4 — Add datasource, JPA, Liquibase, app-specific config
  • README.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 configured
  • backend/src/main/java/de/fete/FeteApplication.java — Entry point, no changes needed
  • frontend/src/router/index.ts:1-23 — Router already configured
  • frontend/vitest.config.ts:1-15 — Test infra already configured
  • frontend/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 changelog
  • backend/src/main/resources/application-prod.properties — Production profile with env var placeholders
  • backend/src/main/resources/application-local.properties.example — Template for local development
  • backend/src/test/java/de/fete/TestFeteApplication.java (or similar) — Testcontainers PostgreSQL bean via TestApplication pattern
  • de/fete/config/FeteProperties.java@ConfigurationProperties class 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 placeholders
  • backend/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 @ConfigurationProperties class)

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:

  1. Domain entities in domain.model — plain Java, no JPA annotations
  2. JPA entities in adapter.out.persistence — annotated with @Entity, @Table, etc.
  3. Mapping between domain and JPA entities in the persistence adapter
  4. Repository interfaces (Spring Data) in adapter.out.persistence
  5. Port interfaces in domain.port.out — define what the domain needs from persistence
  6. 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

  1. 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.

  2. 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 via ENV SPRING_PROFILES_ACTIVE=prod), application-local.properties (gitignored, concrete local values, activated via -Dspring-boot.run.profiles=local). A committed .example template guides developers. The container user sets DATABASE_URL, DATABASE_USERNAME, DATABASE_PASSWORD — never sees Spring internals.

  3. @ConfigurationProperties for app-specific settings (FeteProperties class). Type-safe, validatable, testable. Properties: fete.unsplash.api-key (from UNSPLASH_API_KEY) and fete.max-active-events (from MAX_ACTIVE_EVENTS). Both are only scaffolded in T-4; business logic using them comes with US-13/US-16.

  4. docker-compose example in README only — no standalone docker-compose.yml in 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.

  5. TestApplication pattern for Testcontainers integration. A TestFeteApplication.java in src/test/ registers a @ServiceConnection PostgreSQL container. All @SpringBootTest tests automatically get a database — existing tests continue to work without modification.

  6. README erweitern with local development setup documentation (how to copy application-local.properties.example, start with profile, PostgreSQL prerequisites).

Open Questions

  1. Testcontainers in CI: The Gitea Actions runner needs Docker available for Testcontainers. This works out-of-the-box on ubuntu-latest but should be verified after implementation.

  2. 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.