T-4: add JPA, Liquibase, Testcontainers, and deployment docs
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>
This commit is contained in:
567
docs/agents/plan/2026-03-04-t4-development-infrastructure.md
Normal file
567
docs/agents/plan/2026-03-04-t4-development-infrastructure.md
Normal file
@@ -0,0 +1,567 @@
|
||||
---
|
||||
date: 2026-03-04T20:09:31.044992+00:00
|
||||
git_commit: cb0bcad145b03fec63be0ee3c1fca46ee545329e
|
||||
branch: master
|
||||
topic: "T-4: Development Infrastructure Setup"
|
||||
tags: [plan, database, liquibase, testcontainers, configuration, docker-compose]
|
||||
status: draft
|
||||
---
|
||||
|
||||
# T-4: Development Infrastructure Setup — Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Set up the remaining development infrastructure needed before the first user story (US-1) can be implemented with TDD. This adds JPA + Liquibase for database migrations, PostgreSQL connectivity via environment variables, Testcontainers for integration tests, app-specific configuration properties, and README deployment documentation with a docker-compose example.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
**Already complete (no work needed):**
|
||||
- SPA router: Vue Router with `createWebHistory`, backend SPA fallback in `WebConfig.java`
|
||||
- Frontend test infrastructure: Vitest + `@vue/test-utils`, sample test passing
|
||||
- Both test suites executable: `./mvnw test` (3 tests) and `npm run test:unit` (1 test)
|
||||
|
||||
**Missing (all work in this plan):**
|
||||
- JPA, Liquibase, PostgreSQL driver — no database dependencies in `pom.xml`
|
||||
- Testcontainers — not configured
|
||||
- Database connectivity — no datasource properties
|
||||
- App-specific config — no `@ConfigurationProperties`
|
||||
- Profile separation — no `application-prod.properties`
|
||||
- Deployment docs — no docker-compose in README
|
||||
|
||||
### Key Discoveries:
|
||||
- `backend/pom.xml:1-170` — Spring Boot 3.5.11, no DB dependencies
|
||||
- `backend/src/main/resources/application.properties:1-4` — Only app name + actuator
|
||||
- `HexagonalArchitectureTest.java:22` — `config` is already an adapter in ArchUnit rules
|
||||
- `FeteApplicationTest.java` — Uses `@SpringBootTest` + MockMvc; will need datasource after JPA is added
|
||||
- `Dockerfile:26` — No `SPRING_PROFILES_ACTIVE` set
|
||||
- `.gitignore:47-51` — `.env*` patterns exist but no `application-local.properties`
|
||||
|
||||
## Desired End State
|
||||
|
||||
After this plan is complete:
|
||||
- `./mvnw test` runs all backend tests (including new Testcontainers-backed integration tests) against a real PostgreSQL without external setup
|
||||
- `./mvnw spring-boot:run -Dspring-boot.run.profiles=local` starts the app against a local PostgreSQL
|
||||
- Docker container starts with `DATABASE_URL`/`DATABASE_USERNAME`/`DATABASE_PASSWORD` env vars, runs Liquibase migrations, and responds to health checks
|
||||
- README contains a copy-paste-ready docker-compose example for deployment
|
||||
- `FeteProperties` scaffolds `fete.unsplash.api-key` and `fete.max-active-events` (no business logic yet)
|
||||
|
||||
### Verification:
|
||||
- `cd backend && ./mvnw verify` — all tests green, checkstyle + spotbugs pass
|
||||
- `cd frontend && npm run test:unit -- --run` — unchanged, still green
|
||||
- `docker build .` — succeeds
|
||||
- docker-compose (app + postgres) — container starts, `/actuator/health` returns `{"status":"UP"}`
|
||||
|
||||
## What We're NOT Doing
|
||||
|
||||
- No JPA entities or repositories — those come with US-1
|
||||
- No domain model classes — those come with US-1
|
||||
- No business logic for `FeteProperties` (Unsplash, max events) — US-13/US-16
|
||||
- No standalone `docker-compose.yml` file in repo — inline in README per CLAUDE.md
|
||||
- No `application-local.properties` committed — only the `.example` template
|
||||
- No changes to frontend code — AC 4/6/7 are already met
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: JPA + Liquibase + PostgreSQL Dependencies
|
||||
|
||||
### Overview
|
||||
Add all database-related dependencies to `pom.xml`, create the Liquibase changelog structure with an empty baseline changeset, and update `application.properties` with JPA and Liquibase settings.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Add database dependencies to `pom.xml`
|
||||
**File**: `backend/pom.xml`
|
||||
**Changes**: Add four dependencies after the existing `spring-boot-starter-validation` block.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Add Testcontainers dependencies in test scope (after `archunit-junit5`):
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Spring Boot's dependency management handles versions for all of these — no explicit version tags needed (except `archunit-junit5` which is already versioned).
|
||||
|
||||
#### [x] 2. Create Liquibase master changelog
|
||||
**File**: `backend/src/main/resources/db/changelog/db.changelog-master.xml` (new)
|
||||
**Changes**: Create the master changelog that includes individual changesets.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<include file="db/changelog/000-baseline.xml"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
#### [x] 3. Create empty baseline changeset
|
||||
**File**: `backend/src/main/resources/db/changelog/000-baseline.xml` (new)
|
||||
**Changes**: Empty changeset that proves the tooling works. Liquibase creates its tracking tables (`databasechangelog`, `databasechangeloglock`) automatically.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<!-- T-4: Baseline changeset. Proves Liquibase tooling works.
|
||||
First real schema comes with US-1. -->
|
||||
<changeSet id="000-baseline" author="fete">
|
||||
<comment>Baseline changeset — Liquibase tooling verification</comment>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
#### [x] 4. Update application.properties with JPA and Liquibase settings
|
||||
**File**: `backend/src/main/resources/application.properties`
|
||||
**Changes**: Add JPA and Liquibase configuration (environment-independent, always active).
|
||||
|
||||
```properties
|
||||
spring.application.name=fete
|
||||
|
||||
# JPA
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
spring.jpa.open-in-view=false
|
||||
|
||||
# Liquibase
|
||||
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=health
|
||||
management.endpoint.health.show-details=never
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw compile` succeeds (dependencies resolve, checkstyle passes)
|
||||
- [ ] Changelog XML files are well-formed (Maven compile does not fail on resource processing)
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] Verify `pom.xml` has all six new dependencies with correct scopes
|
||||
- [ ] Verify changelog directory structure: `db/changelog/db.changelog-master.xml` includes `000-baseline.xml`
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Profile-Based Configuration and App Properties
|
||||
|
||||
### Overview
|
||||
Create the profile-based property files for production and local development, add the `FeteProperties` configuration class, update `.gitignore`, and set the production profile in the Dockerfile.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Create production properties file
|
||||
**File**: `backend/src/main/resources/application-prod.properties` (new)
|
||||
**Changes**: Production profile with environment variable placeholders. Activated in Docker via `SPRING_PROFILES_ACTIVE=prod`.
|
||||
|
||||
```properties
|
||||
# Database (required)
|
||||
spring.datasource.url=${DATABASE_URL}
|
||||
spring.datasource.username=${DATABASE_USERNAME}
|
||||
spring.datasource.password=${DATABASE_PASSWORD}
|
||||
|
||||
# App-specific (optional)
|
||||
fete.unsplash.api-key=${UNSPLASH_API_KEY:}
|
||||
fete.max-active-events=${MAX_ACTIVE_EVENTS:0}
|
||||
```
|
||||
|
||||
#### [x] 2. Create local development properties template
|
||||
**File**: `backend/src/main/resources/application-local.properties.example` (new)
|
||||
**Changes**: Template that developers copy to `application-local.properties` (which is gitignored).
|
||||
|
||||
```properties
|
||||
# Local development database
|
||||
# Copy this file to application-local.properties and adjust as needed.
|
||||
# Start with: ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/fete
|
||||
spring.datasource.username=fete
|
||||
spring.datasource.password=fete
|
||||
```
|
||||
|
||||
#### [x] 3. Add `application-local.properties` to `.gitignore`
|
||||
**File**: `.gitignore`
|
||||
**Changes**: Add the gitignore entry for the local properties file (under the Environment section).
|
||||
|
||||
```
|
||||
# Spring Boot local profile (developer-specific, not committed)
|
||||
backend/src/main/resources/application-local.properties
|
||||
```
|
||||
|
||||
#### ~~4. Create `FeteProperties` configuration properties class~~ (deferred)
|
||||
**File**: `backend/src/main/java/de/fete/config/FeteProperties.java` (new)
|
||||
**Changes**: Type-safe configuration for app-specific settings. Both properties are only scaffolded — business logic comes with US-13/US-16.
|
||||
|
||||
```java
|
||||
package de.fete.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Application-specific configuration properties.
|
||||
*
|
||||
* <p>Mapped from {@code fete.*} properties. Both properties are optional:
|
||||
* <ul>
|
||||
* <li>{@code fete.unsplash.api-key} — Unsplash API key (empty = feature disabled)
|
||||
* <li>{@code fete.max-active-events} — Maximum active events (0 = unlimited)
|
||||
* </ul>
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "fete")
|
||||
public class FeteProperties {
|
||||
|
||||
private final Unsplash unsplash;
|
||||
private final int maxActiveEvents;
|
||||
|
||||
/** Creates FeteProperties with the given values. */
|
||||
public FeteProperties(Unsplash unsplash, int maxActiveEvents) {
|
||||
this.unsplash = unsplash != null ? unsplash : new Unsplash("");
|
||||
this.maxActiveEvents = maxActiveEvents;
|
||||
}
|
||||
|
||||
/** Returns the Unsplash configuration. */
|
||||
public Unsplash getUnsplash() {
|
||||
return unsplash;
|
||||
}
|
||||
|
||||
/** Returns the maximum number of active events (0 = unlimited). */
|
||||
public int getMaxActiveEvents() {
|
||||
return maxActiveEvents;
|
||||
}
|
||||
|
||||
/** Unsplash-related configuration. */
|
||||
public record Unsplash(String apiKey) {
|
||||
|
||||
/** Creates Unsplash config with the given API key. */
|
||||
public Unsplash {
|
||||
if (apiKey == null) {
|
||||
apiKey = "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if an API key is configured. */
|
||||
public boolean isEnabled() {
|
||||
return !apiKey.isBlank();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ~~5. Create `FetePropertiesConfig` configuration class~~ (deferred)
|
||||
**File**: `backend/src/main/java/de/fete/config/FetePropertiesConfig.java` (new)
|
||||
**Changes**: Separate `@Configuration` that enables `FeteProperties`.
|
||||
|
||||
```java
|
||||
package de.fete.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/** Activates {@link FeteProperties} binding. */
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(FeteProperties.class)
|
||||
public class FetePropertiesConfig {
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 6. Set production profile in Dockerfile
|
||||
**File**: `Dockerfile`
|
||||
**Changes**: Add `ENV SPRING_PROFILES_ACTIVE=prod` in the runtime stage, before `ENTRYPOINT`.
|
||||
|
||||
```dockerfile
|
||||
# Stage 3: Runtime
|
||||
FROM eclipse-temurin:25-jre-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=backend-build /app/backend/target/*.jar app.jar
|
||||
EXPOSE 8080
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw compile` succeeds (FeteProperties compiles, checkstyle passes)
|
||||
- [ ] `docker build .` succeeds
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] `application-prod.properties` contains all five env-var placeholders
|
||||
- [ ] `application-local.properties.example` is committed; `application-local.properties` is gitignored
|
||||
- [ ] `FeteProperties` fields: `unsplash.apiKey` (String), `maxActiveEvents` (int)
|
||||
- [ ] Dockerfile has `ENV SPRING_PROFILES_ACTIVE=prod` before `ENTRYPOINT`
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Testcontainers Integration
|
||||
|
||||
### Overview
|
||||
Set up the TestApplication pattern so that all `@SpringBootTest` tests automatically get a Testcontainers-managed PostgreSQL instance. This is critical: once JPA is on the classpath, every `@SpringBootTest` needs a datasource. Without this, all three existing `@SpringBootTest` tests break.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Create Testcontainers configuration
|
||||
**File**: `backend/src/test/java/de/fete/TestcontainersConfig.java` (new)
|
||||
**Changes**: Registers a PostgreSQL Testcontainer with `@ServiceConnection` for automatic datasource wiring.
|
||||
|
||||
```java
|
||||
package de.fete;
|
||||
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
/** Provides a Testcontainers PostgreSQL instance for integration tests. */
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
public class TestcontainersConfig {
|
||||
|
||||
@Bean
|
||||
@ServiceConnection
|
||||
PostgreSQLContainer<?> postgresContainer() {
|
||||
return new PostgreSQLContainer<>("postgres:17-alpine");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 2. Create TestFeteApplication for `spring-boot:test-run`
|
||||
**File**: `backend/src/test/java/de/fete/TestFeteApplication.java` (new)
|
||||
**Changes**: Entry point that imports `TestcontainersConfig`. Enables `./mvnw spring-boot:test-run` for local development with Testcontainers (no external PostgreSQL needed).
|
||||
|
||||
```java
|
||||
package de.fete;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
|
||||
/** Test entry point — starts the app with Testcontainers PostgreSQL. */
|
||||
public class TestFeteApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.from(FeteApplication::main)
|
||||
.with(TestcontainersConfig.class)
|
||||
.run(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 3. Import TestcontainersConfig in existing `@SpringBootTest` tests
|
||||
**File**: `backend/src/test/java/de/fete/FeteApplicationTest.java`
|
||||
**Changes**: Add `@Import(TestcontainersConfig.class)` so the test gets a datasource.
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Import(TestcontainersConfig.class)
|
||||
class FeteApplicationTest {
|
||||
// ... existing tests unchanged
|
||||
}
|
||||
```
|
||||
|
||||
**File**: `backend/src/test/java/de/fete/config/WebConfigTest.java`
|
||||
**Changes**: Same — add `@Import(TestcontainersConfig.class)`.
|
||||
|
||||
Note: `HexagonalArchitectureTest` uses `@AnalyzeClasses` (ArchUnit), not `@SpringBootTest` — it needs no changes.
|
||||
|
||||
#### [x] 4. Add SpotBugs exclusion for Testcontainers resource management
|
||||
**File**: `backend/spotbugs-exclude.xml`
|
||||
**Changes**: Testcontainers `PostgreSQLContainer` bean intentionally has container lifecycle managed by Spring, not try-with-resources. SpotBugs may flag this. Add exclusion if needed — check after running `./mvnw verify`.
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw test` — all existing tests pass (context loads, health endpoint, ArchUnit)
|
||||
- [ ] `cd backend && ./mvnw verify` — full verify including SpotBugs passes
|
||||
- [ ] Testcontainers starts a PostgreSQL container during test execution (visible in test output)
|
||||
- [ ] Liquibase baseline migration runs against Testcontainers PostgreSQL
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] `./mvnw spring-boot:test-run` starts the app with a Testcontainers PostgreSQL (for local dev without external DB)
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: README Deployment Documentation
|
||||
|
||||
### Overview
|
||||
Add a deployment section to the README with a docker-compose example, environment variable documentation, and local development setup instructions.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Add deployment section to README
|
||||
**File**: `README.md`
|
||||
**Changes**: Add a `## Deployment` section after the existing `## Code quality` section and before `## License`. Contains the docker-compose example, environment variable table, and notes.
|
||||
|
||||
```markdown
|
||||
## Deployment
|
||||
|
||||
### Docker Compose
|
||||
|
||||
The app ships as a single Docker image. It requires an external PostgreSQL database.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: fete
|
||||
POSTGRES_USER: fete
|
||||
POSTGRES_PASSWORD: changeme
|
||||
volumes:
|
||||
- fete-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U fete"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
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
|
||||
# UNSPLASH_API_KEY: your-key-here
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
fete-db:
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------------------|----------|-----------|------------------------------------------------|
|
||||
| `DATABASE_URL` | Yes | — | JDBC connection string for PostgreSQL |
|
||||
| `DATABASE_USERNAME` | Yes | — | Database username |
|
||||
| `DATABASE_PASSWORD` | Yes | — | Database password |
|
||||
| `MAX_ACTIVE_EVENTS` | No | Unlimited | Maximum number of simultaneously active events |
|
||||
| `UNSPLASH_API_KEY` | No | — | Unsplash API key for header image search |
|
||||
```
|
||||
|
||||
#### [x] 2. Add local development setup section to README
|
||||
**File**: `README.md`
|
||||
**Changes**: Extend the `## Development` section with database setup instructions.
|
||||
|
||||
```markdown
|
||||
### Local database setup
|
||||
|
||||
**Option A: Testcontainers (no external PostgreSQL needed)**
|
||||
|
||||
```bash
|
||||
cd backend && ./mvnw spring-boot:test-run
|
||||
```
|
||||
|
||||
This starts the app with a Testcontainers-managed PostgreSQL that is created and destroyed automatically.
|
||||
|
||||
**Option B: External PostgreSQL**
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
cp src/main/resources/application-local.properties.example \
|
||||
src/main/resources/application-local.properties
|
||||
# Edit application-local.properties if your PostgreSQL uses different credentials
|
||||
./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
```
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd frontend && npm run test:unit -- --run` — frontend tests still pass (no regression)
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] README docker-compose example is syntactically correct YAML
|
||||
- [ ] Environment variable table lists all five variables with correct Required/Default values
|
||||
- [ ] Local development section documents both Testcontainers and external PostgreSQL options
|
||||
- [ ] docker-compose startup: `docker compose up` starts app + postgres, `/actuator/health` returns `{"status":"UP"}`
|
||||
|
||||
**Implementation Note**: After completing this phase, all T-4 acceptance criteria should be met. Run the full verification checklist below.
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests:
|
||||
- `FeteProperties` — verify defaults (empty API key = disabled, maxActiveEvents=0 = unlimited)
|
||||
- No other new unit tests in T-4 — the infrastructure is verified by integration tests
|
||||
|
||||
### Integration Tests:
|
||||
- Existing `FeteApplicationTest.contextLoads()` — validates that Spring context starts with JPA + Liquibase + Testcontainers
|
||||
- Existing `FeteApplicationTest.healthEndpointReturns200()` — validates health check includes DB health
|
||||
- Existing `WebConfigTest` — validates SPA routing still works with JPA on classpath
|
||||
- ArchUnit rules — validate `FeteProperties`/`FetePropertiesConfig` in `config` adapter is properly isolated
|
||||
|
||||
### Manual Testing Steps:
|
||||
1. `cd backend && ./mvnw verify` — full backend pipeline green
|
||||
2. `cd frontend && npm run test:unit -- --run` — frontend unchanged
|
||||
3. `docker build .` — image builds successfully
|
||||
4. docker-compose (app + postgres) — start, wait for health, verify `/actuator/health` returns UP
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Testcontainers PostgreSQL startup adds ~3-5 seconds to backend test execution. This is acceptable for integration tests.
|
||||
- Testcontainers reuses the container across all `@SpringBootTest` classes in a single Maven run (Spring's test context caching).
|
||||
- The empty baseline changeset adds negligible startup time.
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- **Existing tests**: `FeteApplicationTest` and `WebConfigTest` need `@Import(TestcontainersConfig.class)` — without it, they fail because JPA requires a datasource.
|
||||
- **CI pipeline**: `./mvnw -B verify` now requires Docker for Testcontainers. Gitea Actions `ubuntu-latest` runners have Docker available. If the runner uses Docker-in-Docker, `DOCKER_HOST` may need configuration — verify after implementation.
|
||||
- **Local development**: Developers now need either Docker (for Testcontainers via `./mvnw spring-boot:test-run`) or a local PostgreSQL (with `application-local.properties`).
|
||||
|
||||
## References
|
||||
|
||||
- Research: `docs/agents/research/2026-03-04-t4-development-infrastructure.md`
|
||||
- T-4 spec: `spec/setup-tasks.md` (lines 79-98)
|
||||
- Spring Boot Testcontainers: `@ServiceConnection` pattern (Spring Boot 3.1+)
|
||||
- Liquibase Spring Boot integration: auto-configured when `liquibase-core` is on classpath
|
||||
359
docs/agents/research/2026-03-04-t4-development-infrastructure.md
Normal file
359
docs/agents/research/2026-03-04-t4-development-infrastructure.md
Normal file
@@ -0,0 +1,359 @@
|
||||
---
|
||||
date: 2026-03-04T19:37:59.203261+00:00
|
||||
git_commit: cb0bcad145b03fec63be0ee3c1fca46ee545329e
|
||||
branch: master
|
||||
topic: "T-4: Development Infrastructure Setup"
|
||||
tags: [research, codebase, t4, database, liquibase, testcontainers, router, test-infrastructure, docker-compose]
|
||||
status: 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:
|
||||
```properties
|
||||
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`:
|
||||
```properties
|
||||
spring.datasource.url=${DATABASE_URL}
|
||||
spring.datasource.username=${DATABASE_USERNAME}
|
||||
spring.datasource.password=${DATABASE_PASSWORD}
|
||||
```
|
||||
|
||||
**`application-local.properties`** — gitignored, developer creates from `.example` template:
|
||||
```properties
|
||||
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:**
|
||||
```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:
|
||||
```properties
|
||||
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.
|
||||
|
||||
```java
|
||||
// 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:**
|
||||
```yaml
|
||||
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.
|
||||
Reference in New Issue
Block a user