From 50401670d8f5e45d401a9f2beb76920dfc8c12c6 Mon Sep 17 00:00:00 2001 From: kgy1008 Date: Sun, 21 Sep 2025 17:25:50 +0900 Subject: [PATCH 1/7] [refac] refactor package structure for student record metrics --- .../converter/StudentRecordTypeConverter.java | 15 --------------- .../RecordGenerationTracker.java | 2 +- .../StudentRecordMetricsAspect.java | 4 +--- .../StudentRecordMetricsCounter.java | 2 +- 4 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 edukit-core/src/main/java/com/edukit/core/common/converter/StudentRecordTypeConverter.java rename edukit-core/src/main/java/com/edukit/core/studentrecord/{service => metric}/RecordGenerationTracker.java (97%) rename edukit-core/src/main/java/com/edukit/core/studentrecord/{aop => metric}/StudentRecordMetricsAspect.java (94%) rename edukit-core/src/main/java/com/edukit/core/studentrecord/{service => metric}/StudentRecordMetricsCounter.java (97%) diff --git a/edukit-core/src/main/java/com/edukit/core/common/converter/StudentRecordTypeConverter.java b/edukit-core/src/main/java/com/edukit/core/common/converter/StudentRecordTypeConverter.java deleted file mode 100644 index 5f79ca93..00000000 --- a/edukit-core/src/main/java/com/edukit/core/common/converter/StudentRecordTypeConverter.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.edukit.core.common.converter; - - -import com.edukit.core.studentrecord.db.enums.StudentRecordType; -import org.springframework.core.convert.converter.Converter; -import org.springframework.stereotype.Component; - -@Component -public class StudentRecordTypeConverter implements Converter { - - @Override - public StudentRecordType convert(final String source) { - return StudentRecordType.from(source); - } -} diff --git a/edukit-core/src/main/java/com/edukit/core/studentrecord/service/RecordGenerationTracker.java b/edukit-core/src/main/java/com/edukit/core/studentrecord/metric/RecordGenerationTracker.java similarity index 97% rename from edukit-core/src/main/java/com/edukit/core/studentrecord/service/RecordGenerationTracker.java rename to edukit-core/src/main/java/com/edukit/core/studentrecord/metric/RecordGenerationTracker.java index c41686ca..b65bb2c1 100644 --- a/edukit-core/src/main/java/com/edukit/core/studentrecord/service/RecordGenerationTracker.java +++ b/edukit-core/src/main/java/com/edukit/core/studentrecord/metric/RecordGenerationTracker.java @@ -1,4 +1,4 @@ -package com.edukit.core.studentrecord.service; +package com.edukit.core.studentrecord.metric; import java.util.Collections; import lombok.RequiredArgsConstructor; diff --git a/edukit-core/src/main/java/com/edukit/core/studentrecord/aop/StudentRecordMetricsAspect.java b/edukit-core/src/main/java/com/edukit/core/studentrecord/metric/StudentRecordMetricsAspect.java similarity index 94% rename from edukit-core/src/main/java/com/edukit/core/studentrecord/aop/StudentRecordMetricsAspect.java rename to edukit-core/src/main/java/com/edukit/core/studentrecord/metric/StudentRecordMetricsAspect.java index 729e2958..2e8a586e 100644 --- a/edukit-core/src/main/java/com/edukit/core/studentrecord/aop/StudentRecordMetricsAspect.java +++ b/edukit-core/src/main/java/com/edukit/core/studentrecord/metric/StudentRecordMetricsAspect.java @@ -1,9 +1,7 @@ -package com.edukit.core.studentrecord.aop; +package com.edukit.core.studentrecord.metric; import com.edukit.core.studentrecord.db.entity.StudentRecord; import com.edukit.core.studentrecord.db.enums.StudentRecordType; -import com.edukit.core.studentrecord.service.RecordGenerationTracker; -import com.edukit.core.studentrecord.service.StudentRecordMetricsCounter; import com.edukit.core.studentrecord.service.StudentRecordService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/edukit-core/src/main/java/com/edukit/core/studentrecord/service/StudentRecordMetricsCounter.java b/edukit-core/src/main/java/com/edukit/core/studentrecord/metric/StudentRecordMetricsCounter.java similarity index 97% rename from edukit-core/src/main/java/com/edukit/core/studentrecord/service/StudentRecordMetricsCounter.java rename to edukit-core/src/main/java/com/edukit/core/studentrecord/metric/StudentRecordMetricsCounter.java index 1ff99e06..cfd6539b 100644 --- a/edukit-core/src/main/java/com/edukit/core/studentrecord/service/StudentRecordMetricsCounter.java +++ b/edukit-core/src/main/java/com/edukit/core/studentrecord/metric/StudentRecordMetricsCounter.java @@ -1,4 +1,4 @@ -package com.edukit.core.studentrecord.service; +package com.edukit.core.studentrecord.metric; import com.edukit.core.studentrecord.db.enums.StudentRecordType; import io.micrometer.core.instrument.MeterRegistry; From 993d4b77eb32a16b3cc3b1bd0ae35a1e801dba3a Mon Sep 17 00:00:00 2001 From: kgy1008 Date: Mon, 22 Sep 2025 01:04:55 +0900 Subject: [PATCH 2/7] [refac] remove unused StudentRecordService from StudentRecordAIController --- .../studentrecord/controller/StudentRecordAIController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/edukit-api/src/main/java/com/edukit/studentrecord/controller/StudentRecordAIController.java b/edukit-api/src/main/java/com/edukit/studentrecord/controller/StudentRecordAIController.java index 43dc787f..33ffa2af 100644 --- a/edukit-api/src/main/java/com/edukit/studentrecord/controller/StudentRecordAIController.java +++ b/edukit-api/src/main/java/com/edukit/studentrecord/controller/StudentRecordAIController.java @@ -4,7 +4,6 @@ import com.edukit.common.annotation.MemberId; import com.edukit.core.studentrecord.exception.StudentRecordErrorCode; import com.edukit.core.studentrecord.exception.StudentRecordException; -import com.edukit.core.studentrecord.service.StudentRecordService; import com.edukit.studentrecord.controller.request.StudentRecordPromptRequest; import com.edukit.studentrecord.facade.StudentRecordAIFacade; import com.edukit.studentrecord.facade.response.StudentRecordTaskResponse; @@ -26,7 +25,6 @@ public class StudentRecordAIController implements StudentRecordAIApi { private final StudentRecordAIFacade studentRecordAIFacade; - private final StudentRecordService studentRecordService; @PostMapping("/ai-generate/{recordId}") public ResponseEntity> aiGenerateStudentRecord( From f3f1b9418e728f6d5e9e5d337a5457764fe7dcd1 Mon Sep 17 00:00:00 2001 From: kgy1008 Date: Mon, 22 Sep 2025 01:29:13 +0900 Subject: [PATCH 3/7] [feat] add JMX Prometheus Agent to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 71f3b83b..f76fdc1c 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,6 @@ Temporary Items ### log file ### *.log + +### JMX Prometheus Agent ### +jmx_prometheus_javaagent*.jar From 3849302e0872306e7a838a3265f87f627bc9fa6e Mon Sep 17 00:00:00 2001 From: kgy1008 Date: Mon, 22 Sep 2025 01:34:06 +0900 Subject: [PATCH 4/7] [feat] add JMX configuration for monitoring Tomcat and JVM metrics --- jmx-config.yml | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 jmx-config.yml diff --git a/jmx-config.yml b/jmx-config.yml new file mode 100644 index 00000000..998001bb --- /dev/null +++ b/jmx-config.yml @@ -0,0 +1,64 @@ +rules: + # Tomcat Thread Pool metrics + - pattern: 'Catalina<>(currentThreadCount|currentThreadsBusy|maxThreads|connectionCount): (\d+)' + name: tomcat_threads_$2_total + labels: + port: "$1" + help: Tomcat thread pool metrics + type: GAUGE + + # Tomcat Connector metrics + - pattern: 'Catalina<>(requestCount|bytesReceived|bytesSent): (\d+)' + name: tomcat_connector_$2_total + labels: + port: "$1" + help: Tomcat connector metrics + type: COUNTER + + # JVM Memory metrics + - pattern: 'java.lang<>HeapMemoryUsage.(\w+): (\d+)' + name: jvm_memory_heap_$1_bytes + help: JVM heap memory usage + type: GAUGE + + - pattern: 'java.lang<>NonHeapMemoryUsage.(\w+): (\d+)' + name: jvm_memory_nonheap_$1_bytes + help: JVM non-heap memory usage + type: GAUGE + + # GC metrics + - pattern: 'java.lang<>CollectionCount: (\d+)' + name: jvm_gc_collections_total + labels: + gc: "$1" + help: JVM garbage collection count + type: COUNTER + + - pattern: 'java.lang<>CollectionTime: (\d+)' + name: jvm_gc_collection_time_milliseconds_total + labels: + gc: "$1" + help: JVM garbage collection time + type: COUNTER + + # CPU metrics + - pattern: 'java.lang<>ProcessCpuLoad: (.+)' + name: jvm_process_cpu_load_ratio + help: JVM process CPU load + type: GAUGE + + - pattern: 'java.lang<>SystemCpuLoad: (.+)' + name: jvm_system_cpu_load_ratio + help: System CPU load + type: GAUGE + + # Thread metrics + - pattern: 'java.lang<>ThreadCount: (\d+)' + name: jvm_threads_current + help: Current thread count + type: GAUGE + + - pattern: 'java.lang<>DaemonThreadCount: (\d+)' + name: jvm_threads_daemon + help: Daemon thread count + type: GAUGE \ No newline at end of file From d306984673492df01f18554a59a7b3ba06e8d8c3 Mon Sep 17 00:00:00 2001 From: kgy1008 Date: Mon, 22 Sep 2025 01:34:10 +0900 Subject: [PATCH 5/7] [feat] add JMX Prometheus Java agent configuration to Docker Compose files --- docker-compose.dev.yml | 8 ++++++++ docker-compose.prod.yml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 40aa71f6..b4f7ef9f 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -6,13 +6,17 @@ services: PROFILE: dev TZ: Asia/Seoul HOSTNAME: ${HOSTNAME_BLUE:-dev-1-blue} + JAVA_OPTS: "-javaagent:/app/jmx_prometheus_javaagent.jar=8081:/app/jmx-config.yml" ports: - "8080:8080" + - "8081:8081" env_file: - .env restart: always volumes: - ./log/blue:/log + - /opt/jmx_prometheus_javaagent.jar:/app/jmx_prometheus_javaagent.jar + - ./jmx-config.yml:/app/jmx-config.yml networks: - app-network @@ -23,13 +27,17 @@ services: PROFILE: dev TZ: Asia/Seoul HOSTNAME: ${HOSTNAME_GREEN:-dev-1-green} + JAVA_OPTS: "-javaagent:/app/jmx_prometheus_javaagent.jar=8082:/app/jmx-config.yml" ports: - "8081:8080" + - "8082:8082" env_file: - .env restart: always volumes: - ./log/green:/log + - /opt/jmx_prometheus_javaagent.jar:/app/jmx_prometheus_javaagent.jar + - ./jmx-config.yml:/app/jmx-config.yml networks: - app-network diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 5b7bd9a5..8273d284 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,13 +6,17 @@ services: PROFILE: prod TZ: Asia/Seoul HOSTNAME: ${HOSTNAME_BLUE:-prod-1-blue} + JAVA_OPTS: "-javaagent:/app/jmx_prometheus_javaagent.jar=8081:/app/jmx-config.yml" ports: - "8080:8080" + - "8081:8081" env_file: - .env restart: always volumes: - ./log/blue:/log + - /opt/jmx_prometheus_javaagent.jar:/app/jmx_prometheus_javaagent.jar + - ./jmx-config.yml:/app/jmx-config.yml networks: - app-network @@ -23,13 +27,17 @@ services: PROFILE: prod TZ: Asia/Seoul HOSTNAME: ${HOSTNAME_GREEN:-prod-1-green} + JAVA_OPTS: "-javaagent:/app/jmx_prometheus_javaagent.jar=8082:/app/jmx-config.yml" ports: - "8081:8080" + - "8082:8082" env_file: - .env restart: always volumes: - ./log/green:/log + - /opt/jmx_prometheus_javaagent.jar:/app/jmx_prometheus_javaagent.jar + - ./jmx-config.yml:/app/jmx-config.yml networks: - app-network From 27dca5a8698000ceac5123a8b3975e321c7b9c0d Mon Sep 17 00:00:00 2001 From: kgy1008 Date: Mon, 22 Sep 2025 01:42:17 +0900 Subject: [PATCH 6/7] [docs] add CLAUDE.md for project guidance and architecture overview --- .claude/CLAUDE.md | 166 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 .claude/CLAUDE.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 00000000..eb24814a --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,166 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview +EduKit is an AI-powered student record management system for Korean teachers (생활기록부 작성 및 관리 서비스). The project uses a multi-module Spring Boot architecture with Java 21. + +## Module Architecture + +The project follows a layered architecture with clear separation of concerns: + +### 🎯 Module Dependencies +``` +edukit-api (REST API Layer) +├── edukit-core (Business Logic) +│ └── edukit-common (Shared Utilities) +└── edukit-external (External Integrations) + ├── edukit-core + └── edukit-common +``` + +### 📦 Module Descriptions + +- **edukit-api**: REST API controllers, security config, main application entry point + - Contains: Controllers (v1/v2), security, validation, Swagger/OpenAPI + - Technologies: Spring Security, Spring Boot Actuator, Swagger, Prometheus metrics + +- **edukit-core**: Core business logic and domain services + - Contains: JPA entities, repositories, business services, domain logic + - Technologies: Spring Data JPA, QueryDSL, Redis, JWT, Apache POI, Micrometer + +- **edukit-external**: External service integrations + - Contains: AI services (OpenAI), AWS services (S3, SES, SQS), Slack integration + - Technologies: Spring AI, AWS SDK, WebFlux, Resilience4j, Thymeleaf + +- **edukit-common**: Shared utilities and constants + +## Build System & Common Commands + +### 🔧 Build Commands +```bash +# Build entire project +./gradlew build + +# Build specific module +./gradlew :edukit-api:build + +# Run tests +./gradlew test + +# Run specific module tests +./gradlew :edukit-core:test + +# Clean build +./gradlew clean build + +# Generate bootJar (executable) +./gradlew bootJar + +# Run application locally +./gradlew bootRun +``` + +### 🐳 Docker Commands +```bash +# Development environment +docker-compose -f docker-compose.dev.yml up + +# Production environment +docker-compose -f docker-compose.prod.yml up +``` + +## Key Technologies & Integrations + +### 🗄️ Database & Storage +- **MySQL**: Primary database with Flyway migrations +- **Redis**: Caching and session management +- **AWS S3**: File storage + +### 🤖 AI & External Services +- **Spring AI**: OpenAI integration for student record generation +- **AWS SES**: Email notifications +- **AWS SQS**: Message queuing +- **Slack**: Integration for notifications + +### 📊 Monitoring & Observability +- **JMX + Prometheus**: Application metrics collection +- **Micrometer**: Metrics instrumentation +- **Spring Boot Actuator**: Health checks and monitoring endpoints +- **JMX Config**: Custom Tomcat and JVM metrics (see `jmx-config.yml`) + +## Environment Configuration + +### 🌍 Profiles +- `local`: Local development +- `dev`: Development environment (AWS RDS/Redis) +- `prod`: Production environment + +### 📁 Configuration Files +- `application-{profile}.yml`: Profile-specific configurations +- Environment variables managed via `.env` files +- JMX monitoring configured in `jmx-config.yml` + +## Domain Structure + +### 📚 Core Domains +- **Student**: Student management and information +- **StudentRecord**: AI-powered student record generation and management +- **Member**: User/teacher management and authentication +- **Auth**: JWT-based authentication (v1 and v2 APIs) +- **Notice**: Announcements and notifications +- **Admin**: Administrative functions + +### 🏗️ Package Structure Pattern +``` +com.edukit.{domain} +├── controller/ # REST endpoints +├── service/ # Business logic +├── repository/ # Data access +├── entity/ # JPA entities +├── dto/ # Data transfer objects +└── exception/ # Domain-specific exceptions +``` + +## Testing Strategy + +### 🧪 Test Structure +- Unit tests: `src/test/java` +- Integration tests: Available in all modules +- Test profile: `application-test.yml` + +## Development Guidelines + +### 🔐 Security Considerations +- JWT tokens for authentication +- AWS credentials managed via environment variables +- Database credentials in profile-specific configs +- Never commit `.env` files or sensitive data + +### 🎯 Jira Integration +- Branch naming: `feat/EDMT-{ticket-number}` +- Commit format: `[feat/refac/fix] description` +- PR template includes Jira ticket references (Korean descriptions preferred) + +### 🚀 Deployment +- **Blue-Green Deployment**: Configured in Docker Compose +- **Monitoring Ports**: 8081 (blue), 8082 (green) for JMX metrics +- **Health Checks**: Available via Spring Boot Actuator +- **Log Management**: Structured logging with Logback + Logstash encoder + +## Common Development Patterns + +### 🔄 Metrics Collection +- Custom metrics using `@Aspect` and Micrometer +- Student record generation tracking +- Performance monitoring for AI operations + +### 🌐 API Versioning +- v1 and v2 controller patterns +- Backward compatibility considerations + +### 📝 File Processing +- Excel file handling with Apache POI +- File upload/download via AWS S3 + +This architecture supports scalable AI-powered educational services with robust monitoring, security, and Korean localization support. \ No newline at end of file From 54be744f4740b391377848517f9e33c66301a6b0 Mon Sep 17 00:00:00 2001 From: kgy1008 Date: Mon, 22 Sep 2025 01:58:10 +0900 Subject: [PATCH 7/7] [refac] enhance JMX configuration for Tomcat Thread Pool metrics --- jmx-config.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/jmx-config.yml b/jmx-config.yml index 998001bb..60d59bf1 100644 --- a/jmx-config.yml +++ b/jmx-config.yml @@ -1,14 +1,22 @@ rules: - # Tomcat Thread Pool metrics - - pattern: 'Catalina<>(currentThreadCount|currentThreadsBusy|maxThreads|connectionCount): (\d+)' - name: tomcat_threads_$2_total + # Tomcat Thread Pool GAUGE metrics (current state) + - pattern: 'Catalina<>(currentThreadCount|currentThreadsBusy|maxThreads): (\d+)' + name: tomcat_threads_$2 labels: port: "$1" - help: Tomcat thread pool metrics + help: Tomcat thread pool current state metrics type: GAUGE + # Tomcat Thread Pool COUNTER metrics (cumulative) + - pattern: 'Catalina<>(connectionCount): (\d+)' + name: tomcat_threads_$2_total + labels: + port: "$1" + help: Tomcat thread pool cumulative metrics + type: COUNTER + # Tomcat Connector metrics - - pattern: 'Catalina<>(requestCount|bytesReceived|bytesSent): (\d+)' + - pattern: 'Catalina<>(requestCount|bytesReceived|bytesSent): (\d+)' name: tomcat_connector_$2_total labels: port: "$1" @@ -61,4 +69,4 @@ rules: - pattern: 'java.lang<>DaemonThreadCount: (\d+)' name: jvm_threads_daemon help: Daemon thread count - type: GAUGE \ No newline at end of file + type: GAUGE