Skip to content

Commit 2efd687

Browse files
author
Daniel
committed
Update documentation
1 parent 012c202 commit 2efd687

2 files changed

Lines changed: 268 additions & 81 deletions

File tree

CLAUDE.md

Lines changed: 199 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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`
127209
5. 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**:
362496
1. 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
364498
3. Run `./gradlew bootRun` to apply migration
365499
4. 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

Comments
 (0)