|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | +Spring Boot 3.5.3 backend application implementing Clean Architecture with JWT authentication. This is the "Gamchi API" project. |
| 7 | + |
| 8 | +## Development Commands |
| 9 | + |
| 10 | +### Build and Run |
| 11 | +```bash |
| 12 | +# Build the project |
| 13 | +./gradlew build |
| 14 | + |
| 15 | +# Run tests |
| 16 | +./gradlew test |
| 17 | + |
| 18 | +# Run a specific test class |
| 19 | +./gradlew test --tests com.nexters.teamace.auth.presentation.AuthControllerTest |
| 20 | + |
| 21 | +# Run a specific test method |
| 22 | +./gradlew test --tests "com.nexters.teamace.auth.presentation.AuthControllerTest.login_success" |
| 23 | + |
| 24 | +# Clean and build |
| 25 | +./gradlew clean build |
| 26 | + |
| 27 | +# Run the application |
| 28 | +./gradlew bootRun |
| 29 | +``` |
| 30 | + |
| 31 | +### Docker & Database |
| 32 | +```bash |
| 33 | +# Start MySQL database (required for application) |
| 34 | +docker-compose up -d db |
| 35 | + |
| 36 | +# Stop database |
| 37 | +docker-compose down |
| 38 | + |
| 39 | +# View database logs |
| 40 | +docker-compose logs -f db |
| 41 | +``` |
| 42 | + |
| 43 | +### Testing |
| 44 | +```bash |
| 45 | +# Run all tests (includes unit, integration, and E2E tests) |
| 46 | +./gradlew test |
| 47 | + |
| 48 | +# Run only integration tests |
| 49 | +./gradlew test --tests "*UseCaseIntegrationTest" |
| 50 | + |
| 51 | +# Run only E2E tests |
| 52 | +./gradlew test --tests "*E2ETest" |
| 53 | + |
| 54 | +# Run tests with Testcontainers (automatically starts MySQL container) |
| 55 | +./gradlew test --tests "*E2ETest" |
| 56 | +``` |
| 57 | + |
| 58 | +### Code Quality |
| 59 | +```bash |
| 60 | +# Apply code formatting (automatically runs before compile) |
| 61 | +./gradlew spotlessApply |
| 62 | + |
| 63 | +# Check code formatting without applying |
| 64 | +./gradlew spotlessCheck |
| 65 | +``` |
| 66 | + |
| 67 | +### API Documentation |
| 68 | +```bash |
| 69 | +# Generate OpenAPI documentation |
| 70 | +./gradlew openapi3 |
| 71 | + |
| 72 | +# Output: build/api-spec/openapi3.yaml |
| 73 | +``` |
| 74 | + |
| 75 | +### Database Migration (Flyway) |
| 76 | + |
| 77 | +#### 새 마이그레이션 만들기 |
| 78 | +1. `src/main/resources/db/migration/` 폴더에 새 파일 생성 |
| 79 | +2. 파일명 형식: `V{번호}__{설명}.sql` |
| 80 | +3. 예시: `V2__Add_user_email_column.sql`, `V3__Create_chat_room_table.sql` |
| 81 | + |
| 82 | +#### 주요 명령어 |
| 83 | +```bash |
| 84 | +# 마이그레이션 검증하기 (PR 올리기 전에 실행) |
| 85 | +./scripts/validate-migration-files.sh |
| 86 | + |
| 87 | +# 마이그레이션 실행 (앱 시작할 때 자동으로 됨) |
| 88 | +./gradlew flywayMigrate |
| 89 | + |
| 90 | +# 마이그레이션 상태 확인 |
| 91 | +./gradlew flywayInfo |
| 92 | + |
| 93 | +# SQL 문법 검증 |
| 94 | +./gradlew flywayValidate |
| 95 | +``` |
| 96 | + |
| 97 | +## Architecture Overview |
| 98 | + |
| 99 | +### Clean Architecture Layers |
| 100 | +The project follows Clean Architecture with unidirectional dependencies: |
| 101 | +``` |
| 102 | +presentation → application → domain ← infrastructure |
| 103 | + ↑ |
| 104 | + config |
| 105 | +``` |
| 106 | + |
| 107 | +### Key Architectural Decisions |
| 108 | + |
| 109 | +1. **Dependency Inversion**: Application layer depends on interfaces, not implementations |
| 110 | + - Example: `TokenService` interface in application layer, `JwtTokenProvider` implementation in infrastructure |
| 111 | + |
| 112 | +2. **Command/Result Pattern**: Application services use Command objects for input and Result objects for output |
| 113 | + - Prevents application layer from depending on presentation DTOs |
| 114 | + - Example: `LoginCommand` → `AuthService` → `LoginResult` |
| 115 | + |
| 116 | +3. **Configuration Properties**: Using record types with constructor binding |
| 117 | + - Example: `JwtProperties` as a record with `@ConfigurationProperties` |
| 118 | + - Requires `@ConfigurationPropertiesScan` on main application class |
| 119 | + |
| 120 | +4. **Security Architecture**: |
| 121 | + - Stateless authentication (JWT) with access/refresh token pattern |
| 122 | + - Custom error handling through `SecurityErrorHandler` |
| 123 | + - JWT filter processes tokens before Spring Security authentication |
| 124 | + |
| 125 | +5. **AI Integration Architecture**: |
| 126 | + - Spring AI framework with custom Gemini adapter |
| 127 | + - Conversation domain manages AI interaction patterns |
| 128 | + - External prompt templates for maintainable AI responses |
| 129 | + - Context-aware conversation management |
| 130 | + |
| 131 | +6. **Testing Architecture**: |
| 132 | + - Three-tier testing: Unit → Integration → E2E |
| 133 | + - Testcontainers for database integration testing |
| 134 | + - Fixture Monkey for realistic test data generation |
| 135 | + - REST Docs for automated API documentation |
| 136 | + |
| 137 | +### Bounded Context Architecture |
| 138 | +The project is organized into bounded contexts following Domain-Driven Design: |
| 139 | + |
| 140 | +- **auth**: Authentication and authorization |
| 141 | +- **user**: User management and domain logic |
| 142 | +- **conversation**: AI-powered conversation system with Gemini integration |
| 143 | +- **chat**: Chat room functionality |
| 144 | +- **common**: Shared utilities and infrastructure |
| 145 | + |
| 146 | +Each bounded context follows Clean Architecture layers with strict dependency rules. |
| 147 | + |
| 148 | +### Authentication Flow |
| 149 | +1. **Login**: `POST /api/v1/auth/login` with username (password-less for current implementation) |
| 150 | +2. **Token Generation**: `AuthService` creates access and refresh tokens via `TokenService` |
| 151 | +3. **Response**: Returns both tokens in `LoginResponse` |
| 152 | +4. **API Access**: Subsequent requests include `Authorization: Bearer {accessToken}` header |
| 153 | +5. **Token Validation**: `JwtAuthenticationFilter` validates tokens and sets `SecurityContext` |
| 154 | +6. **Token Refresh**: Use refresh token to get new access token when expired |
| 155 | + |
| 156 | +### Conversation System Architecture |
| 157 | +1. **Domain Models**: `ConversationScript` defines conversation structure and types |
| 158 | +2. **Service Layer**: `ConversationService` orchestrates conversation flow |
| 159 | +3. **AI Integration**: `ConversationClient` interface with Gemini implementation |
| 160 | +4. **Context Management**: `ConversationContext` maintains conversation state |
| 161 | +5. **Prompt Management**: External prompt files loaded from resources |
| 162 | +6. **Response Processing**: Structured responses based on conversation type |
| 163 | + |
| 164 | +### Testing Strategy |
| 165 | + |
| 166 | +#### Controller Tests |
| 167 | +- **Base Class**: `ControllerTest` with `@WebMvcTest` and security configurations |
| 168 | +- **Authentication**: `@WithMockCustomUser` annotation for authenticated endpoints |
| 169 | +- **Mocking**: `@MockitoBean` for services to isolate controller logic |
| 170 | +- **Documentation**: REST Docs integration with `@AutoConfigureRestDocs` |
| 171 | +- **JSON Handling**: ObjectMapper and JsonPath utilities for response parsing |
| 172 | + |
| 173 | +#### Integration Tests |
| 174 | +- **Base Class**: `UseCaseIntegrationTest` for service layer testing |
| 175 | +- **Configuration**: `@SpringBootTest(webEnvironment = NONE)` for non-web tests |
| 176 | +- **Real Beans**: No mocking - tests actual Spring bean interactions |
| 177 | +- **Assertions**: BDD-style using `BDDAssertions.then()` with `extracting()` |
| 178 | + |
| 179 | +#### E2E Tests |
| 180 | +- **Base Class**: `E2ETest` with full Spring Boot context |
| 181 | +- **Database**: Testcontainers for MySQL integration |
| 182 | +- **HTTP Client**: REST Assured for API testing |
| 183 | +- **Test Data**: Fixture Monkey for generating realistic test data |
| 184 | + |
| 185 | +#### Test Data Generation |
| 186 | +- **Fixture Monkey**: Generates realistic test objects with minimal configuration |
| 187 | +- **Custom Factories**: Domain-specific object creation patterns |
| 188 | +- **Database Population**: Automated test data setup for integration tests |
| 189 | + |
| 190 | +##### Describe-Context-It Pattern |
| 191 | +This pattern focuses on describing the behavior of code through test cases. While it shares a similar philosophy with the well-known BDD pattern Given-When-Then, it has subtle differences. Describe-Context-It is more suitable for describing behaviors in detail with the test subject as the protagonist, rather than merely describing situations. |
| 192 | + |
| 193 | +| Keyword | Description | |
| 194 | +|---------|-------------| |
| 195 | +| **Describe** | Specifies the test subject. Names the class or method being tested. | |
| 196 | +| **Context** | Describes the circumstances in which the test subject is placed. Explains the parameters to be input to the method being tested. | |
| 197 | +| **It** | Describes the behavior of the test subject. Explains what the test subject method returns. | |
| 198 | + |
| 199 | +Writing Guidelines: |
| 200 | +- Context statements must start with **with** or **when** |
| 201 | +- It statements should be simple, like "It returns true" or "It responses 404" |
| 202 | +- Use Fixture Monkey for generating test data: `fixture.giveMeOne(User.class)` |
| 203 | +- Use BDD assertions with `extracting()` for multiple field validation |
| 204 | +- Mock external dependencies like AI services in unit tests |
| 205 | + |
| 206 | +Example structure: |
| 207 | +```java |
| 208 | +@Nested |
| 209 | +@DisplayName("createUser") |
| 210 | +class Describe_createUser { |
| 211 | + @Nested |
| 212 | + @DisplayName("when valid username and nickname are provided") |
| 213 | + class Context_with_valid_username_and_nickname { |
| 214 | + @Test |
| 215 | + @DisplayName("it returns user with generated id") |
| 216 | + void it_returns_user_with_generated_id() { |
| 217 | + // Given |
| 218 | + CreateUserCommand command = fixture.giveMeOne(CreateUserCommand.class); |
| 219 | + |
| 220 | + // When |
| 221 | + CreateUserResult result = userService.createUser(command); |
| 222 | + |
| 223 | + // Then - BDD assertions with extracting() |
| 224 | + then(result) |
| 225 | + .extracting("id", "username", "nickname") |
| 226 | + .containsExactly(result.id(), command.username(), command.nickname()); |
| 227 | + } |
| 228 | + } |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +## Important Configuration |
| 233 | + |
| 234 | +### Environment Variables |
| 235 | +```yaml |
| 236 | +# Database |
| 237 | +DB_HOST: localhost |
| 238 | +DB_USERNAME: root |
| 239 | +DB_PASSWORD: 1234 |
| 240 | + |
| 241 | +# JWT Authentication |
| 242 | +JWT_SECRET: base64-encoded-secret-key |
| 243 | +JWT_ACCESS_TOKEN_VALIDITY: 3600000 # 1 hour in milliseconds |
| 244 | +JWT_REFRESH_TOKEN_VALIDITY: 604800000 # 7 days in milliseconds |
| 245 | + |
| 246 | +# AI Integration |
| 247 | +GEMINI_API_KEY: your-gemini-api-key |
| 248 | +OPENAI_API_KEY: fake-key # Required but unused (Spring AI constraint) |
| 249 | +``` |
| 250 | +
|
| 251 | +### Database Configuration |
| 252 | +- **Database**: MySQL 8.x with utf8mb4 charset |
| 253 | +- **Migration**: Flyway with validation enabled |
| 254 | +- **Connection Pool**: HikariCP (Spring Boot default) |
| 255 | +- **SQL Logging**: P6Spy with formatted output for development |
| 256 | +
|
| 257 | +### AI Configuration |
| 258 | +- **Primary Model**: Google Gemini 2.0 Flash |
| 259 | +- **Framework**: Spring AI with custom Gemini integration |
| 260 | +- **Conversation Types**: Emotion analysis, message generation |
| 261 | +- **Prompt Management**: External prompt files in resources/prompts/ |
| 262 | +
|
| 263 | +### Monitoring Configuration |
| 264 | +- **Actuator**: Health, metrics, and Prometheus endpoints |
| 265 | +- **Metrics**: Micrometer with Prometheus registry |
| 266 | +- **Endpoints**: `/actuator/health`, `/actuator/metrics`, `/actuator/prometheus` |
| 267 | + |
| 268 | +### Security Configuration |
| 269 | +- **Authentication**: JWT-based with access/refresh token pattern |
| 270 | +- **Session Management**: STATELESS (no server-side sessions) |
| 271 | +- **CSRF**: Disabled for REST API |
| 272 | +- **CORS**: Configured for cross-origin requests |
| 273 | +- **Public Endpoints**: `/api/v1/auth/**`, `/docs/**`, `/swagger-ui/**`, `/actuator/**` |
| 274 | +- **Protected Endpoints**: All other endpoints require valid JWT |
| 275 | +- **Error Handling**: `SecurityErrorHandler` for authentication/authorization errors |
| 276 | +- **Filter Chain**: Custom `JwtAuthenticationFilter` validates tokens before Spring Security |
| 277 | +- **Test Configuration**: `@ConditionalOnWebApplication` prevents security loading in non-web tests |
| 278 | + |
| 279 | +## Common Development Patterns |
| 280 | + |
| 281 | +### Adding a New Domain |
| 282 | +1. Create package structure: `domain/application/infrastructure/presentation` |
| 283 | +2. Define domain interfaces in domain layer |
| 284 | +3. Implement interfaces in infrastructure layer |
| 285 | +4. Use dependency injection with constructor parameters |
| 286 | +5. Follow Command/Result pattern for application services |
| 287 | + |
| 288 | +### Adding New Endpoints |
| 289 | +1. Create Request/Response DTOs in presentation layer |
| 290 | +2. Create Command/Result objects in application layer |
| 291 | +3. Implement business logic in application service |
| 292 | +4. Add controller method with proper validation |
| 293 | +5. Write controller tests extending `ControllerTest` |
| 294 | +6. Add integration tests extending `UseCaseIntegrationTest` |
| 295 | +7. Consider E2E tests extending `E2ETest` for critical flows |
| 296 | + |
| 297 | +### Working with AI Features |
| 298 | +1. Define conversation types in `ConversationType` enum |
| 299 | +2. Create prompt templates in `src/main/resources/prompts/` |
| 300 | +3. Implement conversation logic in `ConversationService` |
| 301 | +4. Use `ConversationClient` for AI model interactions |
| 302 | +5. Test with mocked AI responses using `@MockitoBean` |
| 303 | + |
| 304 | +### Database Development |
| 305 | +1. Create new migration files following `V{number}__{description}.sql` pattern |
| 306 | +2. Run `./scripts/validate-migration-files.sh` before committing |
| 307 | +3. Use `./gradlew flywayInfo` to check migration status |
| 308 | +4. Domain entities go in domain layer, JPA entities in infrastructure |
| 309 | +5. Repository interfaces in domain, implementations in infrastructure |
| 310 | + |
| 311 | +### Error Handling |
| 312 | +- Use `CustomException` with appropriate `ErrorType` |
| 313 | +- Global exception handling via `GlobalExceptionHandler` |
| 314 | +- Security exceptions handled by `SecurityErrorHandler` |
| 315 | + |
| 316 | +## Database Migration Guidelines |
| 317 | + |
| 318 | +### Migration File Naming Convention |
| 319 | +- Format: `V{version}__{description}.sql` |
| 320 | +- Version: Sequential numbers (V1, V2, V3, etc.) |
| 321 | +- Description: Use underscores for spaces, be descriptive |
| 322 | +- Examples: |
| 323 | + - `V1__Create_user_schema.sql` |
| 324 | + - `V2__Add_user_email_column.sql` |
| 325 | + - `V3__Create_chat_room_participants_table.sql` |
| 326 | + |
| 327 | +### Migration Best Practices |
| 328 | +1. **Never modify existing migrations** - Create new migrations instead |
| 329 | +2. **Use descriptive names** - Make it clear what the migration does |
| 330 | +3. **Test migrations** - Always test against a copy of production data |
| 331 | +4. **Backward compatibility** - Consider rollback scenarios |
| 332 | +5. **Small incremental changes** - Avoid large, complex migrations |
| 333 | +6. **Sequential versioning** - Use V1, V2, V3... for simple and clear ordering |
| 334 | +7. **자동 검증** - PR 올릴 때 GitHub에서 자동으로 파일 이름과 SQL 문법을 확인해줍니다 |
| 335 | + |
| 336 | +### Migration Location |
| 337 | +- All migration files must be placed in: `src/main/resources/db/migration/` |
| 338 | +- Flyway automatically runs migrations on application startup |
| 339 | +- Migrations are executed in version order |
0 commit comments