Skip to content

Repository files navigation

Customer Management Service

CI Java Kotlin Spring Boot Cloud Run

🔗 Live demo: https://customer-management-hj57iqzdmq-nw.a.run.app — sign in as admin / admin123 (or user / user123). API docs at /swagger-ui.html.

A reactive customer-management backend built with Spring Boot 3.5 (WebFlux), Kotlin coroutines, and R2DBC. It provides JWT authentication (login only — no self-registration), customer CRUD with search/pagination, per-IP rate limiting, a database-level audit trail, and a multi-environment deployment setup.

Tech stack

Area Choice
Language / runtime Kotlin 1.9, Java 21
Framework Spring Boot 3.5, Spring WebFlux (reactive), Kotlin coroutines
Persistence Spring Data R2DBC, H2 (in-memory)
Migrations Flyway (runs over JDBC)
Security Spring Security resource server, HS256 JWT, BCrypt
Rate limiting Token bucket — in-memory or Redis
API docs springdoc-openapi (Swagger UI)
Build / test Gradle (Kotlin DSL), JUnit 5, MockK, WebTestClient, Testcontainers, JaCoCo

Features

  • AuthenticationPOST /api/auth/login issues a stateless HS256 JWT; login is the only entry point (accounts are seeded). Constant-time credential check (BCrypt off the event loop) to resist user enumeration.
  • Self-service password change — any authenticated user changes their own password.
  • Authorization — two roles (USER, ADMIN) enforced per endpoint with @PreAuthorize.
  • Customer CRUD — create / read / update / delete plus list with search, pagination, and sorting. ADMIN writes; USER + ADMIN read.
  • Rate limiting — per-IP token bucket; a strict tier for /api/auth/** and a looser tier elsewhere; returns 429 + Retry-After. In-memory locally, Redis in deployed envs.
  • Audit trail — every customer update/delete is recorded in audit_log by a database trigger; row-level created_by / last_modified_by / timestamps via Spring Data auditing.
  • Operability — Actuator health (liveness/readiness), OpenAPI docs, Docker image, Kubernetes manifests, and a CI pipeline.

Getting started

Prerequisites: JDK 21 (the Gradle wrapper is included).

./gradlew bootRun

The app starts on http://localhost:8080 with the in-memory (local) profile. bootRun injects a development JWT_SECRET automatically.

Seeded accounts

Username Password Role
admin admin123 ADMIN
user user123 USER

Try it

# 1) Log in and capture the token
TOKEN=$(curl -s localhost:8080/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"admin123"}' | jq -r .accessToken)

# 2) Call a protected endpoint
curl -s localhost:8080/api/me -H "Authorization: Bearer $TOKEN"

# 3) Create a customer (ADMIN)
curl -s localhost:8080/api/customers -X POST \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"firstName":"Jane","lastName":"Doe","email":"jane@example.com"}'

API

Method Path Access Notes
POST /api/auth/login public Returns { accessToken, tokenType, expiresIn, username, role }
POST /api/auth/change-password authenticated { currentPassword, newPassword }204
GET /api/me USER, ADMIN Current principal (username + roles)
GET /api/admin/ping ADMIN Role-check probe
POST /api/customers ADMIN Create customer
GET /api/customers/{id} USER, ADMIN Fetch by id
GET /api/customers USER, ADMIN List — search, page, size, sort=field,dir
PUT /api/customers/{id} ADMIN Update
DELETE /api/customers/{id} ADMIN Delete

API docs: Swagger UI at /swagger-ui.html, OpenAPI JSON at /v3/api-docs (both public). Every endpoint carries a description and example responses, and running the login operation auto-applies the returned token — no manual Authorize step needed. A Postman collection + environment live in postman/; see postman/README.md for the run/Newman guide.

Configuration & environments

The environment is selected with SPRING_PROFILES_ACTIVE:

Profile Rate-limit backend
local (default) in-memory
dev, qa, sandbox, prod Redis

Key settings (env-overridable):

Variable Purpose Default
JWT_SECRET HS256 signing key, ≥ 32 bytes (required; app fails fast otherwise) — (dev/test injected)
SPRING_PROFILES_ACTIVE Active environment profile local
RATELIMIT_BACKEND memory or redis per profile
RATELIMIT_ENABLED Toggle rate limiting true
RATELIMIT_TRUST_XFF Trust X-Forwarded-For (only behind a known proxy) false
REDIS_HOST / REDIS_PORT Redis location (Redis profiles) localhost / 6379

Database & migrations

The app reads/writes via R2DBC; Flyway owns the schema and seed data and runs over JDBC (Flyway has no R2DBC support). Both point at the same in-memory H2 database (DB_CLOSE_DELAY=-1 keeps it alive for the JVM). Migrations are in src/main/resources/db/migration:

  • V1__init.sql — tables + the audit trigger
  • V2__seed_users.sql — seeded admin / user accounts

H2 is used for every environment today. Moving to Postgres means adding r2dbc-postgresql, a JDBC URL for Flyway, and Postgres-flavoured migrations (the audit trigger is H2-specific). This also unlocks running more than one replica.

Testing

./gradlew test            # unit + integration tests; writes a JaCoCo report

88 tests covering services (MockK) and full HTTP flows (WebTestClient), including a Testcontainers-backed Redis test for the distributed rate limiter. Coverage report: build/reports/jacoco/test/html/index.html.

Docker

docker compose up --build        # app (prod profile) + Redis on :8080

The multi-stage Dockerfile builds a non-root JRE image with an Actuator healthcheck. Override the profile with SPRING_PROFILES_ACTIVE=qa docker compose up.

Kubernetes

Manifests are in k8s/ (namespace, per-env ConfigMap, Secret, Redis, app Deployment/Service with probes and a hardened security context). See k8s/README.md for the build → load → apply flow.

Deploy to Google Cloud Run

terraform/ provisions a minimal, scale-to-zero (≈ $0 idle) Cloud Run stack — Artifact Registry, a Secret Manager JWT, a runtime service account, and the service itself. The React SPA is built same-origin and bundled into the backend jar (an SpaWebFilter forwards client-side routes to index.html), so a single Cloud Run URL serves both the UI and the API with no CORS between them. See terraform/README.md for the apply → build/push → deploy flow.

CI/CD

.github/workflows/ci.yml runs build + tests on main, the env branches, and PRs, then builds and (on non-PR pushes) publishes a per-environment image to GHCR. On pushes to main a deploy job authenticates to GCP keylessly (Workload Identity Federation), pushes the full-stack image to Artifact Registry, and runs gcloud run deploy — see terraform/README.md for the one-time WIF setup and the repo variables it needs.

Project structure

src/main/kotlin/com/allica/customermanagement/
├── auth/        # login, password change, JWT minting service, DTOs
├── security/    # Spring Security config, JWT decoder, properties, user details
├── user/        # AppUser entity + repository
├── customer/    # Customer entity, repository, service, controller, DTOs
├── audit/       # audit_log entity/repository + the H2 audit trigger
├── ratelimit/   # token bucket, in-memory & Redis limiters, WebFilter
├── web/         # /api/me and /api/admin endpoints
└── config/      # OpenAPI + R2DBC auditing configuration

Branches

main is the integration branch; dev, qa, sandbox, and prod are long-lived environment branches (same code — the environment is chosen at deploy time via the profile).

AI usage

This project was built with AI assistance (Claude). AI_USAGE.md is the transparency record of how — documented phase by phase: what was asked, the key decisions (and who made them), what was implemented, and how each phase was verified. It spans the initial setup/research, then authentication & login, customer management (CRUD + search), method-level authorization & audit logging, containerization & Kubernetes, the CI pipeline, a final review/hardening pass, self-service password change, the Flyway integration, the React frontend, and the API documentation (OpenAPI examples + Swagger auto-auth). Each entry separates human decisions from AI-generated work and notes the trade-offs that were consciously accepted.

About

Customer management service (Spring Boot WebFlux + React) with Cloud Run CI/CD

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages