Files
Bruno GaoandQwen-Coder a88085a6c1 feat: implement batch 6 BaseAuditPO, MapStruct, Docker, and init script
- Add BaseAuditPO with audit fields (createUserId, updateUserId, createTime, updateTime, deleted, version)
- Implement MapStruct for Order ↔ OrderPO mapping with @BeanMapping(ignoreByDefault=true)
- Create Dockerfile with multi-stage build and runtime-only mode support
- Add .dockerignore for optimized build context
- Create init.sh script for project scaffolding with macOS/Linux compatibility
- Update OrderPO to extend BaseAuditPO
- Update OrderDao to use MapStruct mapper
- Add MapStruct processor dependency to infrastructure module
- Follow M2 v5 frozen contracts for audit trail and persistence layer

Co-authored-by: Qwen-Coder <[email protected]>
2026-02-12 14:18:07 +08:00

12 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Java 21 通用项目模板 using Spring Boot 3.4+ with Clean Architecture (Hexagonal/Ports & Adapters). Multi-tenant SaaS service template implementing strict layer isolation and compile-time architecture enforcement via ArchUnit.

Group ID: com.ljwx.tmpl Java Version: 21 (Virtual Threads enabled by default) Build Tool: Maven (multi-module)

Common Commands

Build & Test

# Run all tests (unit + integration)
mvn test

# Run tests in a specific module
mvn test -pl tmpl-domain
mvn test -pl tmpl-application
mvn test -pl tmpl-infrastructure
mvn test -pl tmpl-api

# Run a single test class
mvn test -Dtest=TenantContextTest
mvn test -pl tmpl-application -Dtest=TenantContextTest

# Run specific test method
mvn test -Dtest=ErrorCodeTest#shouldReturnCorrectHttpStatus

Code Quality

# Apply code formatting (Google Java Format, AOSP style)
mvn spotless:apply

# Check formatting without fixing
mvn spotless:check

# Run full quality gate (format + static analysis + tests + coverage)
mvn verify

# Enable quality gates (normally skipped for fast local builds)
mvn verify -Djacoco.skip=false -Dspotbugs.skip=false -Ddependency-check.skip=false

Running the Application

# Start application (dev profile active by default)
mvn clean spring-boot:run

# From tmpl-api module
cd tmpl-api
mvn spring-boot:run

# With specific profile
mvn spring-boot:run -Dspring-boot.run.profiles=prod

# Access endpoints
# - Health check: http://localhost:8080/actuator/health
# - Swagger UI: http://localhost:8080/swagger-ui.html
# - Actuator: http://localhost:8080/actuator

Database

# Flyway migrations run automatically on startup
# Migration files: tmpl-api/src/main/resources/db/migration/V*.sql

# Start local PostgreSQL (if docker-compose.yml exists)
docker-compose up -d postgres

# Stop
docker-compose down

Architecture

Four-Layer Structure

Dependency direction: outer → inner (strictly enforced by ArchUnit)

tmpl-api (REST Layer)
  ↓ depends on
tmpl-infrastructure (Adapters) + tmpl-application (Use Cases)
  ↓ depends on
tmpl-domain (Pure Business Logic)

tmpl-domain

  • Location: /tmpl-domain
  • Purpose: Pure business logic, framework-agnostic
  • Contains: Domain entities, value objects, repository interfaces, DomainException, ErrorCode enum
  • Constraints:
    • Zero Spring/JPA/Hibernate dependencies
    • Only Java stdlib + jakarta.validation-api (annotations only, no runtime validator injection)
    • All business errors thrown as DomainException(ErrorCode, optionalDetail)

tmpl-application

  • Location: /tmpl-application
  • Purpose: Use case orchestration, transaction boundaries, tenant context management
  • Contains: UseCase interfaces, command/query DTOs, TenantContext (ThreadLocal), TenantScope, @Transactional declarations
  • Constraints:
    • ONLY location for @Transactional - never in infrastructure/api layers
    • No Web (@RestController), Persistence (@Entity), Security, HTTP Client dependencies
    • Depends on: domain + spring-context + spring-tx only

tmpl-infrastructure

  • Location: /tmpl-infrastructure
  • Purpose: Technical implementation of ports (persistence, external services, events)
  • Contains: JPA entities (PO), repositories, adapters, MapStruct mappers
  • Constraints:
    • Implements application layer Port interfaces
    • No @Transactional here (transactions controlled by application layer)
    • Repository pattern: internal/OrderJpaRepository (package-private) → internal/OrderDao (public, tenant-aware) → adapter/OrderPersistenceAdapter (implements Port)

tmpl-api

  • Location: /tmpl-api
  • Purpose: HTTP boundary, Spring Boot entry point, filters, exception handling
  • Contains: TmplApplication.java, controllers, filters (TraceIdFilter, TenantContextFilter), GlobalExceptionHandler, Flyway migrations
  • Constraints:
    • Controllers only inject UseCase interfaces (never infrastructure classes directly)
    • Constructor injection only (no @Autowired on fields)
    • Request DTOs must NOT contain tenantId/deptId/userId (comes from headers)
    • Security rule: Never read tenantId/deptId/userId from request body - only from gateway-injected headers via TenantContext

Multi-Tenant Context

TenantContext (tmpl-application/context/TenantContext.java):

  • ThreadLocal holder for TenantScope (tenantId, deptId, userId, roles)
  • Set by TenantContextFilter from HTTP headers (X-Tenant-Id, X-Dept-Id, X-User-Id, X-User-Roles)
  • Usage:
    TenantScope scope = TenantContext.require();  // throws if not set
    Optional<TenantScope> scope = TenantContext.get();  // safe access
    String tenantId = TenantContext.getTenantId();
    Long userId = TenantContext.getUserIdOrDefault();  // returns 0L if empty
    
  • CRITICAL: Always call TenantContext.clear() in finally block to prevent ThreadLocal leaks

deptId = "0" semantic: Means cross-department access (requires SCOPE_ALL_DEPT role). Never store "0" in database.

Error Handling

ErrorCode enum (tmpl-domain/exception/ErrorCode.java):

  • Frozen contract mapping error scenarios to HTTP status codes + default messages
  • Example:
    ORDER_NOT_FOUND(404, "订单不存在"),
    ORDER_AMOUNT_INVALID(422, "订单金额不合法")
    

DomainException (tmpl-domain/exception/DomainException.java):

// Use default message
throw new DomainException(ErrorCode.ORDER_NOT_FOUND);

// Override with custom detail
throw new DomainException(ErrorCode.ORDER_AMOUNT_INVALID, "金额不能为负: -100");

GlobalExceptionHandler (in tmpl-api):

  • Catches DomainException, reads ErrorCode to determine HTTP status
  • Returns structured JSON: {code, message, details, traceId}

Auditing

BaseAuditPO (inherited by all JPA entities):

  • Fields: createUserId, updateUserId, createTime, updateTime, deleted, version
  • Auto-populated via @CreatedBy, @LastModifiedBy, @CreatedDate, @LastModifiedDate
  • AuditorAwareImpl returns TenantContext.getUserIdOrDefault() for audit fields

Transaction Boundaries

CRITICAL RULE: @Transactional ONLY in application layer (UseCase service implementations).

// ✅ Correct - in tmpl-application
@Service
public class CreateOrderService implements CreateOrderUseCase {
    @Transactional
    @Override
    public OrderDetail execute(CreateOrderCommand cmd, TenantScope scope) {
        // ...
    }
}

// ❌ Wrong - infrastructure adapters must NOT declare @Transactional

Key Constraints (ArchUnit Enforced)

A-Class Rules (CI-blocking):

  1. domain module: No imports from spring.*, jakarta.persistence.*, org.hibernate.*
  2. application module: No Web/Persistence/Security/HTTP Client dependencies
  3. @Transactional only in application layer
  4. Controllers: No direct imports of infrastructure implementation classes
  5. All JPA @Query (native or JPQL) must include tenant_id / tenantId checks
  6. Adapters: Only access OrderDao, never raw JpaRepository

B-Class Rules (strong recommendation):

  1. UseCase methods named execute(...)
  2. Request DTOs must NOT contain tenantId/deptId/userId fields

C-Class Rules (optional):

  1. @Async methods must not call TenantContext.current()
  2. @RequestBody parameters should have @Valid

Adding New Features

Add Domain Entity

  1. Create entity in tmpl-domain/src/main/java/com/ljwx/tmpl/domain/{context}/entity/
    • Use Java 21 record for immutable entities/value objects
    • Use regular class if mutable state needed
    • Include business validation logic (not just getters/setters)
    • Implement business methods (avoid anemic domain model)
  2. Create value objects in domain/{context}/valueobject/ (prefer record)
  3. Add ErrorCode entries in ErrorCode.java for domain exceptions
  4. Create repository interface in domain/{context}/repository/
    • Method signatures reflect business intent
    • Return domain objects (not POJOs)
  5. Create domain events in domain/{context}/event/ if needed
  6. Write unit tests
  7. Verify constraints:
    • ✅ No Spring annotations (@Component, @Service, etc.)
    • ✅ No JPA annotations (@Entity, @Table, etc.)
    • ✅ No framework dependencies

Add Use Case

  1. Create command/query DTO in tmpl-application/src/main/java/com/ljwx/tmpl/application/{context}/port/in/
    • Use Java 21 record for immutable DTOs
  2. Create UseCase interface with execute(...) method (naming convention enforced)
  3. Implement service in tmpl-application/.../service/
    • Add @Service annotation
    • Add @Transactional for write operations
    • Transaction rules: No external HTTP calls or MQ sends inside transactions
  4. Define output port interface in port/out/ if repository/external service access needed

Add Persistence

  1. Create JPA entity (PO) in tmpl-infrastructure/persistence/{context}/internal/
  2. Create JpaRepository (package-private) in internal/
  3. Create Dao class (public, tenant-aware methods) in internal/
  4. Create adapter implementing application port in adapter/
  5. Create MapStruct mapper for domain ↔ PO conversion
  6. Create Flyway migration in tmpl-api/src/main/resources/db/migration/V{version}__{description}.sql

Add Controller

  1. Create controller in tmpl-api/src/main/java/com/ljwx/tmpl/api/controller/
  2. Constructor-inject UseCase interface (not implementation)
  3. Map HTTP request → Command/Query DTO
  4. Never include tenantId/deptId/userId in request body (read from TenantContext)
  5. Add OpenAPI annotations for documentation

Testing

Unit Tests

  • Domain: Pure business logic tests, no mocks needed
  • Application: Mock ports (repositories), test use case logic
  • Run: mvn test -pl tmpl-{module}

Integration Tests

  • Located in tmpl-api/src/test/java/
  • Use @SpringBootTest + Testcontainers (PostgreSQL, Redis)
  • Test full request → response flow with real database

ArchUnit Tests

  • Located in tmpl-api/src/test/java/.../ArchitectureTest.java
  • Runs automatically with mvn test
  • Enforces layer dependencies, naming conventions, annotations

Key Files

  • Architecture spec: /docs/architecture-spec.md - 31 core decisions, frozen contracts
  • Security context: /docs/contracts/security-context.md - Multi-tenancy rules
  • Error handling: /docs/contracts/errors.md - ErrorCode design
  • Transactions: /docs/contracts/transactions.md - Transaction boundaries
  • POM: /pom.xml - Parent POM with all dependency versions locked

Important Design Decisions

  1. No field injection: Always use constructor injection (no @Autowired on fields)
  2. ErrorCode is frozen: Only add new entries or @Deprecated, never change existing name/httpStatus
  3. Transactions only in application layer: Infrastructure never declares @Transactional
  4. Tenant isolation everywhere: All queries filter by tenantId, all writes include tenantId
  5. ThreadLocal cleanup: Always TenantContext.clear() in finally block
  6. No layers skipped: Controller → UseCase → Domain (never Controller → Repository)
  7. MapStruct for mapping: Compile-time code generation, no runtime reflection
  8. Virtual Threads ready: Java 21 Virtual Threads enabled, ThreadLocal usage compatible
  9. Security context trust: tenantId/deptId/userId only from gateway headers, never from request body
  10. Rich domain model: Domain entities contain business logic, not just data (avoid anemic domain model)
  11. Prefer Java 21 records: Use record for immutable value objects and entities when possible
  12. UseCase naming: All UseCase entry methods named execute(...)

CI/CD

Two-job pipeline:

  1. quality-gate (all branches): Compile, test, format check, SpotBugs, OWASP, JaCoCo ≥70%
  2. build-and-deploy (main/release only): Docker build, push to Harbor, update deploy repo

Quality gates skipped by default for fast local builds:

  • jacoco.skip=true
  • spotbugs.skip=true
  • dependency-check.skip=true

CI explicitly enables with -D{check}.skip=false.

Implementation Batches

Current: Batch 2 complete (Domain exceptions + Tenant context)

Remaining:

  • Batch 3: Filters (TraceIdFilter, TenantContextFilter)
  • Batch 4: ArchUnit tests
  • Batch 5: Persistence layer + Flyway
  • Batch 6: Docker + Kubernetes deployment