diff --git a/README.md b/README.md index e5b1409..f5d6379 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,55 @@ # Fullstack Bike-Leasing Blueprint > [!NOTE] -> **๐Ÿšง Work in progress.** This is a **solution template** โ€” a reference to fork and build on, for -> our consultants and anyone else โ€” not a product that ships. It's still being fleshed out, so parts +> **๐Ÿšง Work in progress.** This is a **solution template**, a reference to fork and build on, for +> our consultants and anyone else, not a product that ships. It's still being fleshed out, so parts > may be incomplete and it may not yet fully demonstrate what it's meant to. Treat it as a > living example, and expect it to keep evolving. -A ready-to-fork **fullstack** starting point for automating a business process end to end: a -[CIB seven](https://cibseven.org) (community fork of Camunda 7) **embedded-engine** backend, a -**React frontend**, and an **AI-agent setup** โ€” one complete, runnable BPMN application with an -enforced architecture on *both* sides of the stack. +**One opinionated way to start a greenfield process-automation project, wired end to end.** One BPMN +process, automated with a [CIB seven](https://cibseven.org) (community Camunda 7 fork) **embedded +engine**, a **React frontend**, and an **AI-agent setup**. Its **architecture is kept honest by +tests**, so it can't quietly rot as the process grows. -![MiraVelo's React frontend โ€” the leasing applications list with each case's bike and live status, a link to the CIB seven Cockpit, and a German/English language switch](docs/assets/frontend-applications.png) +![The MiraVelo bike-leasing BPMN process, from leasing request to active lease, with credit check, contract signing, insurance, bike order, and compensation](docs/assets/bike-leasing.png) -## Why this template exists +## Why fork this -Most process-automation examples stop at the engine: a `.bpmn` file, a handful of delegates, maybe a -REST endpoint. Real projects need more โ€” a UI customers actually use, a contract between front and -back, tests that mean something, and an architecture that doesn't rot as the process grows. This -template is our answer to *"give me a sane place to start a greenfield process-automation project"*. +Most automation examples stop at a `.bpmn` file and a few delegates. Real projects need a UI, a +contract between front and back, tests that mean something, and an architecture that survives the +tenth feature. Fork this and you inherit all of it, **guardrailed**: -It is a **proposal, not just a demo.** Fork it and you inherit a hexagonal backend, a Feature-Sliced -frontend, a committed API contract, and a full quality net โ€” all of it **machine-enforced**, so the -structure stays intact whether the next feature is written by a consultant or an AI agent. The -guardrails (architecture tests, mutation testing, linting, typed contracts) exist precisely so that -agent-generated and hand-written code get the same fast, local "you broke a rule" feedback. - -The scenario below is fictional; the architecture, the guardrails, and the end-to-end wiring are the -part you keep. +- ๐ŸงŠ **Hexagonal backend**, enforced by ArchUnit + Konsist, so violations fail the build. +- ๐Ÿงฉ **Feature-Sliced frontend**, enforced by steiger + ESLint + knip. +- ๐Ÿ”— **A committed, drift-gated OpenAPI contract**: the backend generates it, orval turns it into the + frontend's typed client. +- ๐Ÿงช **A real quality net**: mutation testing (gated at 80), Bruno API scenarios, Playwright journeys. +- ๐Ÿค– **AI-ready**: `AGENTS.md`, eight skills, and two review subagents, so agent- and hand-written + code get the same instant "you broke a rule" feedback. ## The scenario -Meet **MiraVelo** โ€” a (fictional) lifestyle bike brand for the quarter-life-crisis crowd: gravel -bikes for the weekends that count, road bikes for everyone who just wants to feel the asphalt. -MiraVelo sells its bikes on a **leasing model** for private and corporate customers, and this project -automates that leasing application from the first request to an active lease. - -It's a made-up company, so nobody gets hurt when the DMN politely declines a 15-year-old's -application for a carbon road bike. - -In the UI a customer submits an application (name, income, a bike from the catalogue), watches the -case advance on its own โ€” the detail page polls while the engine works asynchronously โ€” and signs the -contract or reports the handover when the process asks for it. When a bike turns out to be out of -stock, the case lands in a **back-office inbox** (`/aufgaben`), where an agent picks an alternative -and the process continues. Every business action goes through the domain; timers and the deliberately -UI-less `clarify-return` task stay engine concerns. - -## What is inside - -- **Backend** โ€” Kotlin / Spring Boot 4, **hexagonal**, CIB seven 2.2.0 embedded (JavaDelegates). The - full BPMN palette (message start, DMN, event-based gateway, timers, parallel fork/join, user tasks, - compensation) with architecture (ArchUnit + Konsist), model validation (`bpmn-to-code`), mutation - testing (pitest, gated at 80) and API scenarios (Bruno) enforced at build time. -- **Frontend** โ€” React 19, **Feature-Sliced Design**, Tailwind v4 (canonical Miragon CI), TanStack - Router/Query, a shadcn-style design system, forms with react-hook-form + zod. Layering enforced by - steiger + ESLint + knip; MSW-backed vitest; Playwright e2e mapped 1:1 to the Bruno scenarios. -- **The contract** โ€” springdoc generates `openapi/openapi.json`, which is **committed and - drift-gated**; orval regenerates the frontend's typed TanStack Query client from it. No contract, - no fullstack story. -- **AI-ready** โ€” `AGENTS.md` + `frontend/AGENTS.md`, eight skills and two review subagents in - `.claude/`, and the ADRs in `docs/adr/`. - -## Technology choices - -Every non-obvious decision is recorded as an ADR โ€” read the *why* before changing the *what*: - -- **CIB seven, embedded** โ€” the engine runs *inside* the Spring Boot app, so delegates are plain - beans and the process is unit-testable without a remote engine. (community Camunda 7 fork) -- **Hexagonal backend, machine-enforced** โ€” business logic stays engine- and framework-agnostic - behind ports; ArchUnit + Konsist fail the build on a violation โ€” [ADR-0002](docs/adr/0002-hexagonal-architecture-for-the-backend.md) -- **Feature-Sliced Design frontend** (and why `processes/` is banned) โ€” [ADR-0003](docs/adr/0003-feature-sliced-design-for-the-frontend.md) -- **OpenAPI as the checked-in contract** โ€” the backend is the single source of truth; drift fails - CI โ€” [ADR-0004](docs/adr/0004-openapi-as-the-checked-in-contract.md) -- **Frontend stays out of the Gradle build** โ€” npm-only, so `./gradlew build` needs no Node โ€” [ADR-0005](docs/adr/0005-frontend-stays-out-of-the-gradle-build.md) -- **Mutation testing as a blocking PR gate** โ€” a test that runs without asserting fails at score - 80; the PR gate is diff-scoped, the full-module sweep runs nightly โ€” [ADR-0006](docs/adr/0006-mutation-testing-as-a-blocking-pr-gate.md) -- **`AGENTS.md` as the single source** of agent instructions โ€” [ADR-0007](docs/adr/0007-agents-md-as-the-single-source.md) -- **Fixed ports for v1, portless as the upgrade** โ€” [ADR-0008](docs/adr/0008-fixed-ports-for-v1-portless-as-the-upgrade.md) -- **Always on the latest major** โ€” a deliberate stance, not drift: the template shows the current - technological stand (and AI-driven development against it), gated so bumps stay safe โ€” [ADR-0011](docs/adr/0011-track-the-latest-major-versions.md) -- **Production-shaped from the start** โ€” actuator health/liveness/readiness probes and a Prometheus - scrape endpoint ship out of the box โ€” [ADR-0012](docs/adr/0012-actuator-probes-and-prometheus-metrics.md) -- **One-command deployment** โ€” a `bootBuildImage` OCI image plus an nginx frontend and a full-stack - compose bring up the whole system, not just the DB โ€” [ADR-0014](docs/adr/0014-build-and-deployment-approach.md) -- **Versioned schema migrations** โ€” Flyway owns the schema and Hibernate only `validate`s it, so a - fork evolves its database with reviewable, repeatable SQL instead of `ddl-auto: create` โ€” [ADR-0013](docs/adr/0013-flyway-for-database-migrations.md) +**MiraVelo** is a (fictional) lifestyle bike brand that leases gravel and road bikes to private and +corporate customers. The process automates a leasing application from first request to active lease: +credit check, contract signing, insurance, and bike order, plus timers, a DMN decision, compensation, +and a back-office inbox for the awkward cases. + +In the UI a customer submits an application, watches the case advance on its own (the detail page +polls while the engine works), and signs the contract when asked. When a bike is out of stock the +case lands in the back-office inbox (`/aufgaben`), where an agent picks an alternative and the process +continues. + +![MiraVelo's React frontend, the leasing applications list with each case's bike and live status](docs/assets/frontend-applications.png) + +## What's inside + +- **Backend:** Kotlin / Spring Boot 4, hexagonal, CIB seven 2.2.0 embedded (JavaDelegates). The full + BPMN palette: message start, DMN, event-based gateway, timers, parallel fork/join, user tasks, + compensation. +- **Frontend:** React 19, Feature-Sliced Design, Tailwind v4, TanStack Router/Query, forms with + react-hook-form + zod, MSW-backed vitest, Playwright e2e mapped 1:1 to the Bruno scenarios. +- **The contract:** springdoc generates `openapi/openapi.json` (committed, drift-gated); orval + regenerates the frontend's typed TanStack Query client from it. ## Getting started @@ -96,18 +61,15 @@ docker compose -f stack/docker-compose.yml up -d ./gradlew :service:app:bootRun # 3. run the UI (http://localhost:5173) -npm --prefix frontend ci -npm --prefix frontend run dev +npm --prefix frontend ci && npm --prefix frontend run dev ``` Then open , submit an application with the out-of-stock bike, sign the -contract, open `/aufgaben`, resolve the clarification with an available bike, and watch the detail -page's status advance on its own. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full dev loop (Bruno, -Playwright, contract regeneration). +contract, open `/aufgaben`, resolve the clarification, and watch the status advance on its own. Prefer containers? `./gradlew :service:app:bootBuildImage` then `docker compose -f stack/docker-compose.full.yml up --build` brings up frontend + app + Postgres on - โ€” see [Run it in containers](CONTRIBUTING.md#run-it-in-containers). +. Verify the whole thing: @@ -118,6 +80,14 @@ git diff --exit-code openapi/openapi.json npm --prefix frontend run verify # format, eslint, steiger, knip, tsc, vitest ``` +The full dev loop (Bruno, Playwright, contract regeneration) is in [CONTRIBUTING.md](CONTRIBUTING.md). + +## Why it's shaped this way + +Every non-obvious decision (the embedded engine, hexagonal, FSD, the OpenAPI contract, the mutation +gate, tracking the latest majors) is recorded as an **Architecture Decision Record**. Read the *why* +before changing the *what*: start at the [docs index](docs/README.md) and its [ADRs](docs/adr/). + ## Repository structure ``` @@ -128,32 +98,19 @@ fullstack-example/ โ”‚ โ””โ”€โ”€ app/ # hexagonal Kotlin/Spring Boot 4 + CIB seven โ”œโ”€โ”€ openapi/openapi.json # GENERATED by a test, COMMITTED, drift-gated in CI โ”œโ”€โ”€ frontend/ # React + FSD (npm-only; e2e/ Playwright specs) -โ”œโ”€โ”€ bruno/ # 6 API scenario collections +โ”œโ”€โ”€ bruno/ # API scenario collections โ”œโ”€โ”€ tools/ # bpmnlint + git-hook installer -โ”œโ”€โ”€ stack/ # docker-compose.yml (dev Postgres) ยท docker-compose.full.yml (full stack) -โ”œโ”€โ”€ docs/{README.md, adr/, assets/} # ADRs (decisions) + diagrams +โ”œโ”€โ”€ stack/ # docker-compose.yml (dev DB) ยท docker-compose.full.yml (full stack) +โ”œโ”€โ”€ docs/{README.md, adr/, assets/} # ADRs + diagrams โ”œโ”€โ”€ .claude/{skills/, agents/} # 8 skills, 2 subagents -โ””โ”€โ”€ .github/workflows/ # pre-merge (5 parallel jobs) + nightly +โ””โ”€โ”€ .github/workflows/ # pre-merge (parallel jobs) + nightly ``` **Stack:** Kotlin ยท Spring Boot 4 ยท CIB seven 2.2.0 ยท React 19 ยท TanStack Router/Query ยท Tailwind v4 ยท orval ยท Vite ยท Vitest ยท Playwright. +**Ports:** Postgres `5432` ยท backend + engine `8080` ยท Cockpit `8080/camunda` ยท Vite `5173`. -**Ports:** Postgres `5432` ยท backend + engine `8080` ยท Cockpit `8080/camunda` ยท spec -`8080/v3/api-docs` ยท actuator `8080/actuator` ยท Vite `5173`. - -## Documentation - -The README stays deliberately compact; the reasoning behind its shape lives as the Architecture -Decision Records under [`docs/adr/`](docs/adr/) โ€” start at the [docs index](docs/README.md). Setup, the -ports, the dev loop and the manual smoke test are in [CONTRIBUTING.md](CONTRIBUTING.md). - -## Contributing - -Contributions are welcome โ€” bug reports, feature ideas, docs, and code. See -[CONTRIBUTING.md](CONTRIBUTING.md) to get set up, and use -[Conventional Commits](https://www.conventionalcommits.org) for commit messages and PR titles. - -## License +## Contributing & License -MIT โ€” see [LICENSE](LICENSE). +Contributions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) and use +[Conventional Commits](https://www.conventionalcommits.org). Licensed under MIT (see [LICENSE](LICENSE)). diff --git a/docs/assets/bike-leasing.png b/docs/assets/bike-leasing.png index f11e92a..e13df41 100644 Binary files a/docs/assets/bike-leasing.png and b/docs/assets/bike-leasing.png differ