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>
This commit is contained in:
2026-03-06 20:19:41 +01:00
parent 0b2b84dafc
commit 6aeb4b8bca
83 changed files with 6486 additions and 660 deletions

View File

@@ -0,0 +1,322 @@
# Research Report: API-First Approach
**Date:** 2026-03-04
**Scope:** API-first development with Spring Boot backend and Vue 3 frontend
**Status:** Complete
## Context
The fete project needs a strategy for API design and implementation. Two fundamental approaches exist:
- **Code-first:** Write annotated Java controllers, generate OpenAPI spec from code (e.g., springdoc-openapi)
- **API-first (spec-first):** Write OpenAPI spec as YAML, generate server interfaces and client types from it
This report evaluates API-first for the fete stack (Spring Boot 3.5.x, Java 25, Vue 3, TypeScript).
## Why API-First
| Aspect | Code-First | API-First |
|--------|-----------|-----------|
| Source of truth | Java source code | OpenAPI YAML file |
| Parallel development | Backend must exist first | Frontend + backend from day one |
| Contract stability | Implicit, can drift | Explicit, version-controlled, reviewed |
| Spec review in PRs | Derived artifact | First-class reviewable diff |
| Runtime dependency | springdoc library at runtime | None (build-time only) |
| Hexagonal fit | Controllers define contract | Spec defines contract, controllers implement |
API-first aligns with the project statutes:
- **No vibe coding**: the spec forces deliberate API design before implementation.
- **Research → Spec → Test → Implement**: the OpenAPI spec IS the specification for the API layer.
- **Privacy**: no runtime documentation library needed (no springdoc serving endpoints).
- **KISS**: one YAML file is the single source of truth for both sides.
## Backend: openapi-generator-maven-plugin
### Tool Assessment
- **Project:** [OpenAPITools/openapi-generator](https://github.com/OpenAPITools/openapi-generator)
- **Current version:** 7.20.0 (released 2026-02-16)
- **GitHub stars:** ~22k
- **License:** Apache 2.0
- **Maintenance:** Active, frequent releases (monthly cadence)
- **Spring Boot 3.5.x compatibility:** Confirmed via `useSpringBoot3: true` (Jakarta EE namespace)
- **Java 25 compatibility:** No blocking issues reported for Java 21+
### Generator: `spring` with `interfaceOnly: true`
The `spring` generator offers two modes:
1. **`interfaceOnly: true`** — generates API interfaces and model classes only. You write controllers that implement the interfaces.
2. **`delegatePattern: true`** — generates controllers + delegate interfaces. You implement the delegates.
**Recommendation: `interfaceOnly: true`** — cleaner integration with hexagonal architecture. The generated interface is the port definition, the controller is the driving adapter.
### What Gets Generated
From a spec like:
```yaml
paths:
/events:
post:
operationId: createEvent
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateEventRequest'
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/EventResponse'
```
The generator produces:
- `EventsApi.java` — interface with `@RequestMapping` annotations
- `CreateEventRequest.java` — POJO with Jackson annotations + Bean Validation
- `EventResponse.java` — POJO with Jackson annotations
You then write:
```java
@RestController
public class EventController implements EventsApi {
private final CreateEventUseCase createEventUseCase;
@Override
public ResponseEntity<EventResponse> createEvent(CreateEventRequest request) {
// Map DTO → domain command
// Call use case
// Map domain result → DTO
}
}
```
### Recommended Plugin Configuration
```xml
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.20.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi/api.yaml</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>de.fete.adapter.in.web.api</apiPackage>
<modelPackage>de.fete.adapter.in.web.model</modelPackage>
<generateSupportingFiles>true</generateSupportingFiles>
<supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<useSpringBoot3>true</useSpringBoot3>
<useBeanValidation>true</useBeanValidation>
<performBeanValidation>true</performBeanValidation>
<openApiNullable>false</openApiNullable>
<skipDefaultInterface>true</skipDefaultInterface>
<useResponseEntity>true</useResponseEntity>
<documentationProvider>none</documentationProvider>
<annotationLibrary>none</annotationLibrary>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
```
Key options rationale:
| Option | Value | Why |
|--------|-------|-----|
| `interfaceOnly` | `true` | Only interfaces + models; controllers are yours |
| `useSpringBoot3` | `true` | Jakarta EE namespace (required for Spring Boot 3.x) |
| `useBeanValidation` | `true` | `@Valid`, `@NotNull` on parameters |
| `openApiNullable` | `false` | Avoids `jackson-databind-nullable` dependency |
| `skipDefaultInterface` | `true` | No default method stubs — forces full implementation |
| `documentationProvider` | `none` | No Swagger UI / springdoc annotations |
| `annotationLibrary` | `none` | Minimal annotations on generated code |
### Build Integration
- Runs in Maven's `generate-sources` phase (before compilation)
- Output: `target/generated-sources/openapi/` — already gitignored
- `mvn clean compile` always regenerates from spec
- No generated code in git — the spec is the source of truth
### Additional Dependencies
With `openApiNullable: false` and `annotationLibrary: none`, minimal additional dependencies are needed. `jakarta.validation-api` is already transitively provided by `spring-boot-starter-web`.
### Hexagonal Architecture Mapping
```
adapter.in.web/
├── api/ ← generated interfaces (EventsApi.java)
├── model/ ← generated DTOs (CreateEventRequest.java, EventResponse.java)
└── controller/ ← your implementations (EventController implements EventsApi)
application.port.in/
└── CreateEventUseCase.java
domain.model/
└── Event.java ← clean domain object (can be a record)
```
Rules:
1. Generated DTOs exist ONLY in `adapter.in.web.model`
2. Domain objects are never exposed to the web layer
3. Controllers map between generated DTOs and domain objects
4. Mapping is manual (project is small enough; no MapStruct needed)
## Frontend: openapi-typescript + openapi-fetch
### Tool Comparison
| Tool | npm Weekly DL | Approach | Runtime | Active |
|------|--------------|----------|---------|--------|
| **openapi-typescript** | ~2.5M | Types only (.d.ts) | 0 kb | Yes |
| **openapi-fetch** | ~1.2M | Type-safe fetch wrapper | 6 kb | Yes |
| orval | ~828k | Full client codegen | Varies | Yes |
| @hey-api/openapi-ts | ~200-400k | Full client codegen | Varies | Yes (volatile API) |
| openapi-generator TS | ~500k | Full codegen (Java needed) | Heavy | Yes |
| swagger-typescript-api | ~43 | Full codegen | Varies | Declining |
### Recommendation: openapi-typescript + openapi-fetch
**Why this combination wins for fete:**
1. **Minimal footprint.** Types-only generation = zero generated runtime code. The `.d.ts` file disappears after TypeScript compilation.
2. **No Axios.** Uses native `fetch` — no unnecessary dependency.
3. **No phone home, no CDN.** Pure TypeScript types + a 6 kb fetch wrapper.
4. **Vue 3 Composition API fit.** Composables wrap `api.GET()`/`api.POST()` calls naturally.
5. **Actively maintained.** High download counts, regular releases, OpenAPI 3.0 + 3.1 support.
6. **Compile-time safety.** Wrong paths, missing parameters, wrong body types = TypeScript errors.
**Why NOT the alternatives:**
- **orval / hey-api:** Generate full runtime code (functions, classes). More than needed. Additional abstraction layer.
- **openapi-generator TypeScript:** Requires Java for generation. Produces verbose classes. Heavyweight.
- **swagger-typescript-api:** Declining maintenance. Not recommended for new projects.
### How It Works
#### Step 1: Generate Types
```bash
npx openapi-typescript ../backend/src/main/resources/openapi/api.yaml -o src/api/schema.d.ts
```
Produces a `.d.ts` file with `paths` and `components` interfaces that mirror the OpenAPI spec exactly.
#### Step 2: Create Client
```typescript
// src/api/client.ts
import createClient from "openapi-fetch";
import type { paths } from "./schema";
export const api = createClient<paths>({ baseUrl: "/api" });
```
#### Step 3: Use in Composables
```typescript
// src/composables/useEvent.ts
import { ref } from "vue";
import { api } from "@/api/client";
export function useEvent(eventId: string) {
const event = ref(null);
const error = ref(null);
async function load() {
const { data, error: err } = await api.GET("/events/{eventId}", {
params: { path: { eventId } },
});
if (err) error.value = err;
else event.value = data;
}
return { event, error, load };
}
```
Type safety guarantees:
- Path must exist in spec → TypeScript error if not
- Path parameters enforced → TypeScript error if missing
- Request body must match schema → TypeScript error if wrong
- Response `data` is typed as the 2xx response schema
### Build Integration
```json
{
"scripts": {
"generate:api": "openapi-typescript ../backend/src/main/resources/openapi/api.yaml -o src/api/schema.d.ts",
"dev": "npm run generate:api && vite",
"build": "npm run generate:api && vue-tsc && vite build"
}
}
```
The generated `schema.d.ts` can be committed to git (it is a stable, deterministic output) or gitignored and regenerated on each build. For simplicity, committing it is pragmatic — it allows IDE support without running the generator first.
### Dependencies
```json
{
"devDependencies": {
"openapi-typescript": "^7.x"
},
"dependencies": {
"openapi-fetch": "^0.13.x"
}
}
```
Requirements: Node.js 20+, TypeScript 5.x, `"module": "ESNext"` + `"moduleResolution": "Bundler"` in tsconfig.
## End-to-End Workflow
```
1. WRITE/EDIT SPEC
backend/src/main/resources/openapi/api.yaml
├──── 2. BACKEND: mvnw compile
│ → target/generated-sources/openapi/
│ ├── de/fete/adapter/in/web/api/EventsApi.java
│ └── de/fete/adapter/in/web/model/*.java
│ → Compiler errors show what controllers need updating
└──── 3. FRONTEND: npm run generate:api
→ frontend/src/api/schema.d.ts
→ TypeScript errors show what composables/views need updating
```
On spec change, both sides get compile-time feedback. The spec is a **compile-time contract**.
## Open Questions
1. **Spec location sharing.** The spec lives in `backend/src/main/resources/openapi/`. The frontend references it via relative path (`../backend/...`). This works in a monorepo. Alternative: symlink or copy step. Relative path is simplest.
2. **Generated `schema.d.ts` — commit or gitignore?** Committing is pragmatic (IDE support without running generator). Gitignoring is purist (derived artifact). Recommend: commit it, regenerate during build to catch drift.
3. **Spec validation in CI.** The openapi-generator-maven-plugin validates the spec during build. Frontend side could add `openapi-typescript` as a build step. Both fail on invalid specs.
## Conclusion
API-first with `openapi-generator-maven-plugin` (backend) and `openapi-typescript` + `openapi-fetch` (frontend) is a strong fit for fete:
- Single source of truth (one YAML file)
- Compile-time contract enforcement on both sides
- Minimal dependencies (no Swagger UI, no Axios, no runtime codegen libraries)
- Clean hexagonal architecture integration
- Actively maintained, well-adopted tooling