# 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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: ```java TenantScope scope = TenantContext.require(); // throws if not set Optional 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: ```java ORDER_NOT_FOUND(404, "订单不存在"), ORDER_AMOUNT_INVALID(422, "订单金额不合法") ``` **DomainException** (`tmpl-domain/exception/DomainException.java`): ```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). ```java // ✅ 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