@@ -4,7 +4,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44
55## Project Overview
66
7- This is a Java 21 Spring Boot 3.5.3 microservice implementing user authentication and management with both REST and gRPC APIs. The service features JWT authentication with refresh token rotation, token families for multi-device support, role-based access control (ADMIN/MEMBER), and PostgreSQL persistence with Flyway migrations.
7+ This is a ** Java 25 Spring Boot 4.0** modular monolith implementing user authentication and management with both REST and gRPC APIs. The application uses ** Spring Modulith** for module boundaries and event-driven communication between modules. The service features JWT authentication with refresh token rotation, token families for multi-device support, role-based access control (ADMIN/MEMBER), and PostgreSQL persistence with Flyway migrations.
8+
9+ ### Key Technologies
10+ - ** Java 25** with virtual threads (Project Loom)
11+ - ** Spring Boot 4.0** with Spring Framework 7.0
12+ - ** Spring Modulith 2.0** for modular architecture
13+ - ** PostgreSQL** with Flyway migrations
14+ - ** gRPC** alongside REST APIs
815
916## Build & Development Commands
1017
@@ -40,11 +47,14 @@ docker compose up -d
4047# Run all tests
4148./gradlew test
4249
50+ # Run module architecture verification
51+ ./gradlew test --tests " org.nkcoder.ModulithArchitectureTest"
52+
4353# Run specific test class
44- ./gradlew test --tests " org.nkcoder.controller.AuthControllerTest "
54+ ./gradlew test --tests " org.nkcoder.user.application.service.AuthApplicationServiceTest "
4555
46- # Run specific test method
47- ./gradlew test --tests " org.nkcoder.controller.AuthControllerTest.shouldRegisterUser "
56+ # Run integration tests
57+ ./gradlew test --tests " org.nkcoder.user.integration.* "
4858
4959# Test with coverage
5060./gradlew test jacocoTestReport
@@ -76,28 +86,100 @@ docker compose up -d
7686
7787## Architecture
7888
79- ### Layered Structure
89+ ### Spring Modulith Structure
90+
91+ The application is organized as a ** modular monolith** using Spring Modulith. Each module has clear boundaries and communicates via events.
92+
93+ ```
94+ org.nkcoder/
95+ ├── Application.java ← Bootstrap (@Modulith entry point)
96+ │
97+ ├── user/ ← User Module (authentication & management)
98+ │ ├── package-info.java ← @ApplicationModule(allowedDependencies = {"shared", "infrastructure"})
99+ │ ├── interfaces/rest/ ← REST controllers (public API)
100+ │ ├── application/service/ ← Application services (use case orchestration)
101+ │ ├── domain/ ← Domain models, services, repositories (ports)
102+ │ └── infrastructure/ ← Adapters (JPA, Security, JWT)
103+ │
104+ ├── notification/ ← Notification Module (email/SMS)
105+ │ ├── package-info.java ← @ApplicationModule(allowedDependencies = {"shared", "infrastructure"})
106+ │ ├── NotificationService.java ← Public API
107+ │ └── application/ ← Event listeners
108+ │
109+ ├── shared/ ← Shared Kernel (OPEN module)
110+ │ ├── package-info.java ← @ApplicationModule(type = OPEN)
111+ │ ├── kernel/domain/event/ ← Domain events (UserRegisteredEvent, etc.)
112+ │ ├── kernel/exception/ ← Common exceptions
113+ │ └── local/rest/ ← ApiResponse, GlobalExceptionHandler
114+ │
115+ └── infrastructure/ ← Infrastructure Module (OPEN module)
116+ ├── package-info.java ← @ApplicationModule(type = OPEN)
117+ └── config/ ← CORS, OpenAPI, JPA auditing configs
118+ ```
119+
120+ ### Module Dependency Rules
80121
81- The codebase follows a strict layered architecture:
122+ ```
123+ notification ──→ shared ←── user
124+ ↑
125+ infrastructure
126+ ```
127+
128+ - ** user** depends on: ` shared ` , ` infrastructure `
129+ - ** notification** depends on: ` shared ` , ` infrastructure `
130+ - ** shared** and ** infrastructure** are OPEN modules (accessible by all)
131+ - Modules communicate via ** domain events** (not direct calls)
132+
133+ ### Hexagonal Architecture (within User Module)
82134
83135```
84- Controller → Service → Repository → Entity
85- ↓ ↓
86- DTO Mapper
136+ ┌─────────────────────────────────────────────────────────────┐
137+ │ User Module │
138+ ├─────────────────────────────────────────────────────────────┤
139+ │ interfaces/rest/ ← Driving Adapters (REST API) │
140+ │ ├── AuthController │
141+ │ ├── UserController │
142+ │ └── AdminUserController │
143+ ├─────────────────────────────────────────────────────────────┤
144+ │ application/service/ ← Use Case Orchestration │
145+ │ ├── AuthApplicationService │
146+ │ └── UserApplicationService │
147+ ├─────────────────────────────────────────────────────────────┤
148+ │ domain/ ← Core Business Logic │
149+ │ ├── model/ (User, RefreshToken, Value Objects)│
150+ │ ├── service/ (AuthenticationService, TokenGenerator - ports)│
151+ │ └── repository/ (UserRepository, RefreshTokenRepository - ports)│
152+ ├─────────────────────────────────────────────────────────────┤
153+ │ infrastructure/ ← Driven Adapters │
154+ │ ├── persistence/ (JPA entities, repository adapters)│
155+ │ └── security/ (JWT filter, SecurityConfig, BCrypt)│
156+ └─────────────────────────────────────────────────────────────┘
87157```
88158
89- ** Key Directories** (under ` src/main/java/org/nkcoder/ ` ):
90- - ` controller/ ` - REST endpoints (AuthController, UserController, HealthController)
91- - ` service/ ` - Business logic (AuthService, UserService)
92- - ` repository/ ` - JPA repositories (UserRepository, RefreshTokenRepository)
93- - ` entity/ ` - JPA entities (User, RefreshToken)
94- - ` dto/ ` - Data Transfer Objects organized by feature (auth/, user/, common/)
95- - ` mapper/ ` - Entity-to-DTO mappers (UserMapper)
96- - ` security/ ` - Security components (JwtAuthenticationFilter, JwtAuthenticationEntryPoint)
97- - ` config/ ` - Configuration classes (SecurityConfig, JwtProperties, OpenApiConfig, etc.)
98- - ` util/ ` - Utilities (JwtUtil)
99- - ` grpc/ ` - gRPC service implementations (AuthGrpcService, GrpcMapper)
100- - ` exception/ ` - Custom exceptions (AuthenticationException, ValidationException, ResourceNotFoundException)
159+ ### Key Components by Module
160+
161+ ** User Module** (` org.nkcoder.user ` ):
162+ - ` interfaces/rest/ ` - AuthController, UserController, AdminUserController
163+ - ` application/service/ ` - AuthApplicationService, UserApplicationService
164+ - ` domain/model/ ` - User (aggregate), RefreshToken, value objects (Email, UserId, UserRole, etc.)
165+ - ` domain/service/ ` - TokenGenerator, PasswordEncoder, AuthenticationService (ports)
166+ - ` domain/repository/ ` - UserRepository, RefreshTokenRepository (ports)
167+ - ` infrastructure/persistence/ ` - JPA entities, repository adapters
168+ - ` infrastructure/security/ ` - JwtAuthenticationFilter, SecurityConfig, JwtTokenGeneratorAdapter
169+
170+ ** Notification Module** (` org.nkcoder.notification ` ):
171+ - ` NotificationService ` - Public API for sending notifications
172+ - ` application/UserEventListener ` - Listens to UserRegisteredEvent
173+
174+ ** Shared Module** (` org.nkcoder.shared ` ):
175+ - ` kernel/domain/event/ ` - DomainEvent, UserRegisteredEvent, UserProfileUpdatedEvent
176+ - ` kernel/domain/valueobject/ ` - AggregateRoot base class
177+ - ` kernel/exception/ ` - AuthenticationException, ValidationException, ResourceNotFoundException
178+ - ` local/rest/ ` - ApiResponse, GlobalExceptionHandler
179+ - ` local/event/ ` - SpringDomainEventPublisher
180+
181+ ** Infrastructure Module** (` org.nkcoder.infrastructure ` ):
182+ - ` config/ ` - CorsProperties, OpenApiConfig, JpaAuditingConfig, WebConfig
101183
102184### Security & Authentication
103185
@@ -120,10 +202,10 @@ Controller → Service → Repository → Entity
120202- ` POST /api/users/auth/logout-single ` - Deletes only current refresh token (single device)
121203
122204** JWT Authentication Flow** :
123- 1 . JwtAuthenticationFilter extracts token from ` Authorization: Bearer {token} ` header
124- 2 . Token validated and claims extracted via JwtUtil
125- 3 . UsernamePasswordAuthenticationToken created with role authorities (ROLE_MEMBER/ROLE_ADMIN)
126- 4 . Context stored in SecurityContextHolder
205+ 1 . ` JwtAuthenticationFilter ` (in ` user.infrastructure.security ` ) extracts token from ` Authorization: Bearer {token} ` header
206+ 2 . Token validated via ` TokenGenerator ` port (implemented by ` JwtTokenGeneratorAdapter ` )
207+ 3 . ` UsernamePasswordAuthenticationToken ` created with role authorities (ROLE_MEMBER/ROLE_ADMIN)
208+ 4 . Context stored in ` SecurityContextHolder `
1272095 . Request attributes set: userId, email, role (accessible in controllers)
128210
129211** Security Configuration** :
@@ -206,13 +288,36 @@ PATCH /api/users/{userId}/password - Reset password (admin only)
206288- ` ValidationException ` - Business validation failures (duplicate email, password mismatch)
207289- ` ResourceNotFoundException ` - Entity not found by ID
208290
291+ ### Event-Driven Communication
292+
293+ Modules communicate via domain events using Spring Modulith's event infrastructure:
294+
295+ ** Publishing Events** (in User module):
296+ ``` java
297+ // In AuthApplicationService after registration
298+ domainEventPublisher. publish(new UserRegisteredEvent (user. getId(), user. getEmail(), user. getName()));
299+ ```
300+
301+ ** Listening to Events** (in Notification module):
302+ ``` java
303+ @Component
304+ public class UserEventListener {
305+ @ApplicationModuleListener
306+ public void onUserRegistered (UserRegisteredEvent event ) {
307+ notificationService. sendWelcomeEmail(event. email(), event. userName());
308+ }
309+ }
310+ ```
311+
312+ ** Event Publication Table** : Spring Modulith persists events to ` event_publication ` table for reliable delivery (transactional outbox pattern).
313+
209314### Configuration Management
210315
211316** Profiles** :
212317- ` local ` - Local development with Docker Compose
213- - ` docker ` - Docker container environment
318+ - ` dev ` - Development environment with external database
214319- ` prod ` - Production with environment variables
215- - ` test ` - Test profile with in-memory/test database
320+ - ` test ` - Test profile with TestContainers
216321
217322** Environment Variables** (required for production):
218323``` bash
@@ -237,10 +342,11 @@ CLIENT_URL=http://localhost:3000
237342- ` V1.1__create_tables.sql ` - Initial schema (users, refresh_tokens tables)
238343- ` V1.2__seeding_users.sql ` - Seed data (admin@timor.com , demo@timor.com )
239344- ` V1.3__update_users_role.sql ` - Schema updates
345+ - ` V1.4__create_event_publication_table.sql ` - Spring Modulith event publication table
240346
241347** Migration Best Practices** :
242348- Never modify existing migrations (create new ones)
243- - Use sequential versioning: V1.1, V1.2, V1.3, etc.
349+ - Use sequential versioning: V1.1, V1.2, V1.3, etc. (uppercase V required!)
244350- Validate migrations with ` validate-on-migrate: true `
245351- Baseline existing databases with ` baseline-on-migrate: true `
246352
@@ -268,15 +374,28 @@ CLIENT_URL=http://localhost:3000
268374** Test Types** :
269375- Unit tests: ` @WebMvcTest ` for controllers, ` @MockBean ` for services
270376- Integration tests: ` @SpringBootTest ` with TestContainers for PostgreSQL
377+ - Module tests: ` ModulithArchitectureTest ` verifies module boundaries
271378- Security tests: Use ` @WithMockUser ` or custom security setup
272379
273- ** Base Test Classes** :
274- - ` BaseControllerTest ` - Base for controller unit tests (MockMvc setup)
275- - ` BaseSecurityControllerTest ` - Base with security context
276- - ` IntegrationTestingBase ` - Base for integration tests (TestContainers)
380+ ** Module Verification Test** :
381+ ``` java
382+ class ModulithArchitectureTest {
383+ ApplicationModules modules = ApplicationModules . of(Application . class);
384+
385+ @Test
386+ void verifyModuleStructure () {
387+ modules. verify(); // Fails on illegal cross-module dependencies
388+ }
389+ }
390+ ```
391+
392+ ** Integration Test Setup** :
393+ - Tests in ` org.nkcoder.user.integration/ ` package
394+ - Use ` @SpringBootTest(classes = Application.class) ` to specify bootstrap class
395+ - WebTestClient for REST API testing (Spring Boot 4 compatible)
277396
278397** Test Configuration** :
279- - ` TestConfig .java` provides test-specific beans
398+ - ` TestContainersConfiguration .java` provides PostgreSQL container
280399- ` application-test.yml ` configures test profile
281400- TestContainers automatically manages PostgreSQL instance
282401
@@ -301,9 +420,10 @@ CLIENT_URL=http://localhost:3000
301420- Packages: lowercase (e.g., org.nkcoder.service)
302421
303422** Package Organization** :
304- - DTOs grouped by feature: ` dto/auth/ ` , ` dto/user/ ` , ` dto/common/ `
305- - One entity per file
306- - One controller per resource domain (Auth, User)
423+ - Modules are direct sub-packages of ` org.nkcoder `
424+ - Each module follows hexagonal architecture: ` interfaces/ ` , ` application/ ` , ` domain/ ` , ` infrastructure/ `
425+ - Domain events shared across modules go in ` shared.kernel.domain.event/ `
426+ - One controller per resource domain (Auth, User, AdminUser)
307427
308428** Dependency Injection** :
309429- Prefer constructor injection over field injection
@@ -350,17 +470,31 @@ CLIENT_URL=http://localhost:3000
350470
351471## Common Development Tasks
352472
353- ** Adding a New Endpoint** :
354- 1 . Create request/response DTOs in appropriate ` dto/ ` subdirectory
355- 2 . Add method to service layer with business logic
356- 3 . Add controller method with @GetMapping /@PostMapping /@PatchMapping
357- 4 . Add validation annotations to request DTO
358- 5 . Add test cases in controller test class
359- 6 . Run ` ./gradlew test ` to verify
473+ ** Adding a New Endpoint** (in User module):
474+ 1 . Create request DTO in ` user/interfaces/rest/request/ `
475+ 2 . Create command DTO in ` user/application/dto/command/ `
476+ 3 . Add mapper method in ` user/interfaces/rest/mapper/ `
477+ 4 . Add method to application service (` user/application/service/ ` )
478+ 5 . Add controller method in ` user/interfaces/rest/ `
479+ 6 . Add test cases
480+ 7 . Run ` ./gradlew test ` to verify
481+
482+ ** Adding a New Module** :
483+ 1 . Create package ` org.nkcoder.{modulename}/ `
484+ 2 . Create ` package-info.java ` with ` @ApplicationModule(allowedDependencies = {"shared", "infrastructure"}) `
485+ 3 . Create module structure: ` interfaces/ ` , ` application/ ` , ` domain/ ` , ` infrastructure/ `
486+ 4 . Add event listeners if needed to react to events from other modules
487+ 5 . Run ` ModulithArchitectureTest ` to verify module boundaries
488+
489+ ** Publishing Domain Events** :
490+ 1 . Create event record in ` shared/kernel/domain/event/ ` (if cross-module) or ` {module}/domain/event/ ` (if module-internal)
491+ 2 . Inject ` DomainEventPublisher ` in your service
492+ 3 . Call ` domainEventPublisher.publish(event) ` after business logic
493+ 4 . Create ` @ApplicationModuleListener ` in consuming module
360494
361495** Database Schema Change** :
3624961 . Create new migration file: ` V{next_version}__{description}.sql ` in ` src/main/resources/db/migration/ `
363- 2 . Update entity class if needed
497+ 2 . Update JPA entity in ` {module}/infrastructure/persistence/entity/ ` if needed
3644983 . Run ` ./gradlew bootRun ` to apply migration
3654994 . Verify with database client or integration test
366500
@@ -375,3 +509,24 @@ CLIENT_URL=http://localhost:3000
375509
376510** Role Assignment** : New users default to MEMBER role; ADMIN role must be explicitly assigned or seeded
377511
512+ ## Spring Modulith Guidelines
513+
514+ ** Module Boundaries** :
515+ - Never import internal classes from other modules (only public API)
516+ - Use domain events for cross-module communication
517+ - Shared code goes in ` shared ` module (marked as OPEN)
518+ - Run ` ./gradlew test ` regularly - ` ModulithArchitectureTest ` catches violations
519+
520+ ** Event Best Practices** :
521+ - Events are immutable records
522+ - Events should be past-tense (` UserRegistered ` , not ` RegisterUser ` )
523+ - Cross-module events go in ` shared.kernel.domain.event/ `
524+ - Use ` @ApplicationModuleListener ` for reliable event handling (auto-retry, persistence)
525+
526+ ** Future Microservice Extraction** :
527+ When ready to extract a module as a microservice:
528+ 1 . Events become messages (Kafka/RabbitMQ)
529+ 2 . REST/gRPC calls replace direct method calls
530+ 3 . Module's ` infrastructure/ ` adapters change, domain stays the same
531+ 4 . Database can be separated per module
532+
0 commit comments