727 lines
17 KiB
Markdown
727 lines
17 KiB
Markdown
# Spring Boot 멀티모듈 프로젝트 설계 가이드라인
|
|
|
|
## 개요
|
|
|
|
이 문서는 Spring Boot 프로젝트를 멀티모듈 구조로 설계하고 구현하기 위한 가이드라인입니다.
|
|
본 프로젝트(balance)의 실제 구조 변경 경험을 바탕으로 작성되었습니다.
|
|
|
|
## 목차
|
|
|
|
1. [멀티모듈 구조의 이점](#멀티모듈-구조의-이점)
|
|
2. [모듈 분리 원칙](#모듈-분리-원칙)
|
|
3. [표준 모듈 구조](#표준-모듈-구조)
|
|
4. [Gradle 설정](#gradle-설정)
|
|
5. [의존성 관리 전략](#의존성-관리-전략)
|
|
6. [패키지 구조 및 네이밍](#패키지-구조-및-네이밍)
|
|
7. [주의사항 및 베스트 프랙티스](#주의사항-및-베스트-프랙티스)
|
|
|
|
---
|
|
|
|
## 멀티모듈 구조의 이점
|
|
|
|
### 1. 관심사의 분리 (Separation of Concerns)
|
|
- 각 모듈이 명확한 책임을 가짐
|
|
- 도메인, 데이터 접근, API 레이어를 물리적으로 분리
|
|
|
|
### 2. 재사용성 (Reusability)
|
|
- Storage 모듈처럼 독립적인 모듈은 다른 프로젝트에서 재사용 가능
|
|
- 공통 기능을 별도 모듈로 추출하여 여러 프로젝트에서 활용
|
|
|
|
### 3. 빌드 최적화
|
|
- 변경된 모듈만 재빌드
|
|
- 병렬 빌드 가능
|
|
|
|
### 4. 팀 협업
|
|
- 모듈별로 소유권 분리 가능
|
|
- 충돌 최소화
|
|
|
|
### 5. 의존성 제어
|
|
- 모듈 간 의존성을 명시적으로 관리
|
|
- 순환 참조 방지
|
|
|
|
---
|
|
|
|
## 모듈 분리 원칙
|
|
|
|
### 레이어드 아키텍처 기반 분리
|
|
|
|
```
|
|
┌─────────────────────┐
|
|
│ API/Presentation │ ← balance-core-api
|
|
│ (Controller) │
|
|
├─────────────────────┤
|
|
│ Business Logic │ ← balance-core-api
|
|
│ (Service) │
|
|
├─────────────────────┤
|
|
│ Data Access │ ← storage
|
|
│ (Entity, Repo) │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
### 권장 모듈 분리 전략
|
|
|
|
#### 1. Storage/Domain 모듈
|
|
**목적:** 데이터 엔티티 및 레포지토리
|
|
|
|
**포함 요소:**
|
|
- JPA 엔티티 (`@Entity`)
|
|
- Repository 인터페이스 (`JpaRepository`)
|
|
- 도메인 모델
|
|
- 데이터 접근 로직
|
|
|
|
**특징:**
|
|
- 다른 모듈에 의존하지 않음 (독립적)
|
|
- Spring Boot 실행 불가 (라이브러리 모듈)
|
|
- 다른 프로젝트에서 재사용 가능
|
|
|
|
#### 2. Core API 모듈
|
|
**목적:** REST API 및 비즈니스 로직
|
|
|
|
**포함 요소:**
|
|
- REST 컨트롤러 (`@RestController`)
|
|
- 서비스 계층 (`@Service`)
|
|
- DTO (Data Transfer Objects)
|
|
- 애플리케이션 진입점 (`@SpringBootApplication`)
|
|
|
|
**특징:**
|
|
- Storage 모듈에 의존
|
|
- 실행 가능한 Spring Boot 애플리케이션
|
|
- HTTP 엔드포인트 제공
|
|
|
|
#### 3. (선택) Common 모듈
|
|
**목적:** 공통 유틸리티 및 설정
|
|
|
|
**포함 요소:**
|
|
- 공통 유틸리티 클래스
|
|
- 공통 예외 클래스
|
|
- 공통 설정
|
|
- 공통 DTO
|
|
|
|
---
|
|
|
|
## 표준 모듈 구조
|
|
|
|
### 디렉토리 구조 예시
|
|
|
|
```
|
|
project-root/
|
|
├── settings.gradle
|
|
├── build.gradle
|
|
├── storage/
|
|
│ ├── build.gradle
|
|
│ └── src/
|
|
│ ├── main/
|
|
│ │ └── kotlin/
|
|
│ │ └── com/company/project/storage/
|
|
│ │ ├── entity/
|
|
│ │ │ ├── User.kt
|
|
│ │ │ └── Order.kt
|
|
│ │ └── repository/
|
|
│ │ ├── UserRepository.kt
|
|
│ │ └── OrderRepository.kt
|
|
│ └── test/
|
|
│ └── kotlin/
|
|
│ └── com/company/project/storage/
|
|
│
|
|
├── core-api/
|
|
│ ├── build.gradle
|
|
│ └── src/
|
|
│ ├── main/
|
|
│ │ ├── kotlin/
|
|
│ │ │ └── com/company/project/
|
|
│ │ │ ├── Application.kt
|
|
│ │ │ ├── controller/
|
|
│ │ │ │ ├── UserController.kt
|
|
│ │ │ │ └── OrderController.kt
|
|
│ │ │ └── service/
|
|
│ │ │ ├── UserService.kt
|
|
│ │ │ └── OrderService.kt
|
|
│ │ └── resources/
|
|
│ │ └── application.properties
|
|
│ └── test/
|
|
│ └── kotlin/
|
|
│ └── com/company/project/
|
|
│
|
|
└── common/ (선택사항)
|
|
├── build.gradle
|
|
└── src/
|
|
└── main/
|
|
└── kotlin/
|
|
└── com/company/project/common/
|
|
├── util/
|
|
├── exception/
|
|
└── config/
|
|
```
|
|
|
|
---
|
|
|
|
## Gradle 설정
|
|
|
|
### 1. 루트 프로젝트 `settings.gradle`
|
|
|
|
```gradle
|
|
rootProject.name = 'project-name'
|
|
include 'storage'
|
|
include 'core-api'
|
|
// include 'common' // 필요시
|
|
```
|
|
|
|
### 2. 루트 프로젝트 `build.gradle`
|
|
|
|
```gradle
|
|
plugins {
|
|
id 'org.jetbrains.kotlin.jvm' version '2.2.21' apply false
|
|
id 'org.jetbrains.kotlin.plugin.spring' version '2.2.21' apply false
|
|
id 'org.springframework.boot' version '4.0.1' apply false
|
|
id 'io.spring.dependency-management' version '1.1.7' apply false
|
|
id 'org.hibernate.orm' version '7.2.0.Final' apply false
|
|
id 'org.jetbrains.kotlin.plugin.jpa' version '2.2.21' apply false
|
|
}
|
|
|
|
group = 'com.company'
|
|
version = '0.0.1-SNAPSHOT'
|
|
|
|
subprojects {
|
|
apply plugin: 'org.jetbrains.kotlin.jvm'
|
|
apply plugin: 'io.spring.dependency-management'
|
|
|
|
repositories {
|
|
mavenCentral()
|
|
}
|
|
|
|
java {
|
|
toolchain {
|
|
languageVersion = JavaLanguageVersion.of(21) // 또는 17, 24
|
|
}
|
|
}
|
|
|
|
kotlin {
|
|
compilerOptions {
|
|
freeCompilerArgs.addAll '-Xjsr305=strict', '-Xannotation-default-target=param-property'
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation 'org.jetbrains.kotlin:kotlin-reflect'
|
|
}
|
|
|
|
tasks.withType(Test) {
|
|
useJUnitPlatform()
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Storage 모듈 `storage/build.gradle`
|
|
|
|
```gradle
|
|
plugins {
|
|
id 'org.jetbrains.kotlin.plugin.spring'
|
|
id 'org.springframework.boot'
|
|
id 'org.hibernate.orm'
|
|
id 'org.jetbrains.kotlin.plugin.jpa'
|
|
}
|
|
|
|
dependencies {
|
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
|
|
// 데이터베이스 드라이버
|
|
runtimeOnly 'com.h2database:h2'
|
|
runtimeOnly 'org.postgresql:postgresql'
|
|
// runtimeOnly 'com.mysql:mysql-connector-j'
|
|
}
|
|
|
|
hibernate {
|
|
enhancement {
|
|
enableAssociationManagement = true
|
|
}
|
|
}
|
|
|
|
allOpen {
|
|
annotation 'jakarta.persistence.Entity'
|
|
annotation 'jakarta.persistence.MappedSuperclass'
|
|
annotation 'jakarta.persistence.Embeddable'
|
|
}
|
|
|
|
// 라이브러리 모듈이므로 bootJar 비활성화
|
|
bootJar {
|
|
enabled = false
|
|
}
|
|
|
|
jar {
|
|
enabled = true
|
|
}
|
|
```
|
|
|
|
### 4. Core API 모듈 `core-api/build.gradle`
|
|
|
|
```gradle
|
|
plugins {
|
|
id 'org.jetbrains.kotlin.plugin.spring'
|
|
id 'org.jetbrains.kotlin.plugin.jpa'
|
|
id 'org.springframework.boot'
|
|
}
|
|
|
|
dependencies {
|
|
// 내부 모듈 의존성
|
|
implementation project(':storage')
|
|
|
|
// Spring Boot Starters
|
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
|
|
implementation 'org.springframework.boot:spring-boot-h2console'
|
|
implementation 'tools.jackson.module:jackson-module-kotlin'
|
|
|
|
// 개발 도구
|
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
|
|
|
// 테스트
|
|
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
|
|
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
|
|
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
|
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
|
}
|
|
|
|
// 실행 가능한 jar 생성 (기본값이므로 생략 가능)
|
|
// bootJar {
|
|
// enabled = true
|
|
// }
|
|
```
|
|
|
|
---
|
|
|
|
## 의존성 관리 전략
|
|
|
|
### 의존성 방향 규칙
|
|
|
|
```
|
|
core-api ──→ storage
|
|
↓
|
|
common (선택)
|
|
```
|
|
|
|
**규칙:**
|
|
1. 상위 레이어는 하위 레이어에 의존 가능
|
|
2. 하위 레이어는 상위 레이어에 의존하면 안 됨
|
|
3. 순환 의존성 절대 금지
|
|
|
|
### 모듈 간 의존성 선언
|
|
|
|
```gradle
|
|
dependencies {
|
|
// 올바른 예
|
|
implementation project(':storage')
|
|
implementation project(':common')
|
|
|
|
// 잘못된 예 (storage 모듈에서)
|
|
// implementation project(':core-api') // ❌ 순환 의존성
|
|
}
|
|
```
|
|
|
|
### 외부 의존성 관리
|
|
|
|
**루트 프로젝트에서 버전 관리:**
|
|
|
|
```gradle
|
|
// build.gradle (root)
|
|
ext {
|
|
springCloudVersion = '2024.0.0'
|
|
}
|
|
|
|
subprojects {
|
|
apply plugin: 'io.spring.dependency-management'
|
|
|
|
dependencyManagement {
|
|
imports {
|
|
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 패키지 구조 및 네이밍
|
|
|
|
### 패키지 네이밍 컨벤션
|
|
|
|
```
|
|
com.company.project.{module}.{layer}
|
|
```
|
|
|
|
**예시:**
|
|
```
|
|
com.quantbench.balance.storage.entity
|
|
com.quantbench.balance.storage.repository
|
|
com.quantbench.balance.controller
|
|
com.quantbench.balance.service
|
|
```
|
|
|
|
### 모듈별 패키지 구조
|
|
|
|
#### Storage 모듈
|
|
```
|
|
com.quantbench.balance.storage/
|
|
├── entity/
|
|
│ ├── User.kt
|
|
│ ├── Order.kt
|
|
│ └── Product.kt
|
|
└── repository/
|
|
├── UserRepository.kt
|
|
├── OrderRepository.kt
|
|
└── ProductRepository.kt
|
|
```
|
|
|
|
#### Core API 모듈
|
|
```
|
|
com.quantbench.balance/
|
|
├── Application.kt
|
|
├── controller/
|
|
│ ├── UserController.kt
|
|
│ ├── OrderController.kt
|
|
│ └── ProductController.kt
|
|
├── service/
|
|
│ ├── UserService.kt
|
|
│ ├── OrderService.kt
|
|
│ └── ProductService.kt
|
|
└── dto/
|
|
├── UserDto.kt
|
|
├── OrderDto.kt
|
|
└── ProductDto.kt
|
|
```
|
|
|
|
### Spring Boot 애플리케이션 클래스 설정
|
|
|
|
```kotlin
|
|
package com.quantbench.balance
|
|
|
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
|
import org.springframework.boot.runApplication
|
|
|
|
@SpringBootApplication(scanBasePackages = ["com.quantbench.balance"])
|
|
class BalanceApplication
|
|
|
|
fun main(args: Array<String>) {
|
|
runApplication<BalanceApplication>(*args)
|
|
}
|
|
```
|
|
|
|
**중요:**
|
|
- `scanBasePackages`를 명시하여 모든 모듈의 컴포넌트를 스캔
|
|
- 패키지 구조는 일관성 있게 유지 (`com.quantbench.balance.*`)
|
|
|
|
---
|
|
|
|
## 주의사항 및 베스트 프랙티스
|
|
|
|
### 1. 모듈 설계 원칙
|
|
|
|
#### ✅ DO (권장)
|
|
- 각 모듈은 단일 책임을 가져야 함
|
|
- 모듈 간 인터페이스는 명확하게 정의
|
|
- Storage 모듈은 가능한 한 독립적으로 유지
|
|
- 공통 기능은 별도 모듈로 분리
|
|
|
|
#### ❌ DON'T (비권장)
|
|
- 순환 의존성 생성
|
|
- 너무 많은 모듈로 과도하게 분리 (3-5개 권장)
|
|
- 하위 레이어가 상위 레이어에 의존
|
|
- 모듈 간 패키지 직접 접근
|
|
|
|
### 2. Gradle 설정 주의사항
|
|
|
|
#### bootJar 설정
|
|
```gradle
|
|
// Storage 모듈 (라이브러리)
|
|
bootJar {
|
|
enabled = false // 실행 불가능
|
|
}
|
|
jar {
|
|
enabled = true // 일반 jar 생성
|
|
}
|
|
|
|
// API 모듈 (실행 가능)
|
|
bootJar {
|
|
enabled = true // 기본값이므로 생략 가능
|
|
}
|
|
```
|
|
|
|
#### 플러그인 적용
|
|
- 루트 프로젝트: `apply false` 사용
|
|
- 서브 프로젝트: 필요한 플러그인만 적용
|
|
|
|
### 3. Spring Boot 4.0+ 사용 시 주의사항
|
|
|
|
Spring Boot 4.0에서는 일부 어노테이션이 변경되거나 제거되었습니다.
|
|
|
|
**컴포넌트 스캔 설정:**
|
|
```kotlin
|
|
// ✅ 권장 (Spring Boot 4.0+)
|
|
@SpringBootApplication(scanBasePackages = ["com.quantbench.balance"])
|
|
class BalanceApplication
|
|
|
|
// ❌ 비권장 (구버전 방식, Spring Boot 4.0에서 문제 발생 가능)
|
|
@SpringBootApplication
|
|
@EntityScan("com.quantbench.balance.storage.entity")
|
|
@EnableJpaRepositories("com.quantbench.balance.storage.repository")
|
|
class BalanceApplication
|
|
```
|
|
|
|
### 4. 빌드 및 실행
|
|
|
|
#### 전체 빌드
|
|
```bash
|
|
./gradlew clean build
|
|
```
|
|
|
|
#### 특정 모듈 빌드
|
|
```bash
|
|
./gradlew :storage:build
|
|
./gradlew :core-api:build
|
|
```
|
|
|
|
#### 애플리케이션 실행
|
|
```bash
|
|
./gradlew :core-api:bootRun
|
|
```
|
|
|
|
#### 의존성 확인
|
|
```bash
|
|
./gradlew :core-api:dependencies --configuration runtimeClasspath
|
|
```
|
|
|
|
### 5. 테스트 전략
|
|
|
|
#### Storage 모듈 테스트
|
|
```kotlin
|
|
@DataJpaTest
|
|
class UserRepositoryTest {
|
|
@Autowired
|
|
lateinit var userRepository: UserRepository
|
|
|
|
@Test
|
|
fun `should save and find user`() {
|
|
// 테스트 코드
|
|
}
|
|
}
|
|
```
|
|
|
|
#### API 모듈 통합 테스트
|
|
```kotlin
|
|
@SpringBootTest
|
|
@AutoConfigureMockMvc
|
|
class UserControllerIntegrationTest {
|
|
@Autowired
|
|
lateinit var mockMvc: MockMvc
|
|
|
|
@Test
|
|
fun `should return user by id`() {
|
|
mockMvc.perform(get("/api/users/1"))
|
|
.andExpect(status().isOk)
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. 성능 최적화
|
|
|
|
#### Gradle 빌드 최적화
|
|
```properties
|
|
# gradle.properties
|
|
org.gradle.parallel=true
|
|
org.gradle.caching=true
|
|
org.gradle.configureondemand=true
|
|
```
|
|
|
|
#### 모듈별 병렬 빌드
|
|
```bash
|
|
./gradlew build --parallel
|
|
```
|
|
|
|
---
|
|
|
|
## 실제 적용 예시 (balance 프로젝트)
|
|
|
|
### 적용 전 (단일 모듈)
|
|
```
|
|
balance/
|
|
├── build.gradle
|
|
├── settings.gradle
|
|
└── src/
|
|
├── main/
|
|
│ └── kotlin/com/quantbench/balance/
|
|
│ └── BalanceApplication.kt
|
|
└── test/
|
|
```
|
|
|
|
### 적용 후 (멀티 모듈)
|
|
```
|
|
balance/
|
|
├── build.gradle # 공통 설정
|
|
├── settings.gradle # 모듈 정의
|
|
├── storage/ # 데이터 접근 레이어
|
|
│ ├── build.gradle
|
|
│ └── src/main/kotlin/com/quantbench/balance/storage/
|
|
│ ├── entity/
|
|
│ └── repository/
|
|
└── balance-core-api/ # API 레이어
|
|
├── build.gradle
|
|
└── src/
|
|
├── main/
|
|
│ ├── kotlin/com/quantbench/balance/
|
|
│ │ ├── BalanceApplication.kt
|
|
│ │ ├── controller/
|
|
│ │ └── service/
|
|
│ └── resources/application.properties
|
|
└── test/
|
|
```
|
|
|
|
### 핵심 변경 사항
|
|
|
|
1. **모듈 분리:** storage와 balance-core-api로 분리
|
|
2. **의존성 방향:** balance-core-api → storage
|
|
3. **실행 가능성:** balance-core-api만 실행 가능, storage는 라이브러리
|
|
4. **재사용성:** storage 모듈은 다른 프로젝트에서 재사용 가능
|
|
|
|
---
|
|
|
|
## 추가 확장 시나리오
|
|
|
|
### 1. Admin API 추가
|
|
```
|
|
balance/
|
|
├── storage/
|
|
├── balance-core-api/
|
|
└── balance-admin-api/ # 관리자 API
|
|
├── build.gradle
|
|
└── src/
|
|
```
|
|
|
|
**의존성 구조:**
|
|
```
|
|
balance-core-api ──→ storage
|
|
balance-admin-api ──→ storage
|
|
```
|
|
|
|
### 2. Batch 모듈 추가
|
|
```
|
|
balance/
|
|
├── storage/
|
|
├── balance-core-api/
|
|
└── balance-batch/ # 배치 작업
|
|
├── build.gradle
|
|
└── src/
|
|
```
|
|
|
|
### 3. Common 모듈 추가
|
|
```
|
|
balance/
|
|
├── common/ # 공통 유틸리티
|
|
├── storage/
|
|
├── balance-core-api/
|
|
└── balance-admin-api/
|
|
```
|
|
|
|
**의존성 구조:**
|
|
```
|
|
balance-core-api ──→ storage ──→ common
|
|
balance-admin-api ──→ storage ──→ common
|
|
```
|
|
|
|
---
|
|
|
|
## 문제 해결 (Troubleshooting)
|
|
|
|
### 1. 컴파일 에러: Unresolved reference
|
|
|
|
**문제:**
|
|
```
|
|
e: Unresolved reference 'EntityScan'
|
|
e: Unresolved reference 'EnableJpaRepositories'
|
|
```
|
|
|
|
**해결:**
|
|
- Spring Boot 4.0+에서는 `@EntityScan`, `@EnableJpaRepositories` 대신
|
|
- `@SpringBootApplication(scanBasePackages = [...])`를 사용
|
|
|
|
### 2. bootJar 에러
|
|
|
|
**문제:**
|
|
```
|
|
Main class name has not been configured and it could not be resolved
|
|
```
|
|
|
|
**해결:**
|
|
```gradle
|
|
// Storage 모듈에서
|
|
bootJar {
|
|
enabled = false
|
|
}
|
|
jar {
|
|
enabled = true
|
|
}
|
|
```
|
|
|
|
### 3. 컴포넌트 스캔 실패
|
|
|
|
**문제:** Repository나 Service가 스캔되지 않음
|
|
|
|
**해결:**
|
|
```kotlin
|
|
@SpringBootApplication(scanBasePackages = ["com.company.project"])
|
|
```
|
|
패키지 구조가 일관성 있게 유지되는지 확인
|
|
|
|
### 4. 순환 의존성
|
|
|
|
**문제:**
|
|
```
|
|
Circular dependency between the following tasks:
|
|
:module-a:jar
|
|
:module-b:jar
|
|
```
|
|
|
|
**해결:**
|
|
- 모듈 간 의존성 구조 재검토
|
|
- 공통 기능은 별도 모듈로 분리
|
|
|
|
---
|
|
|
|
## 마이그레이션 체크리스트
|
|
|
|
기존 프로젝트를 멀티모듈로 마이그레이션할 때:
|
|
|
|
- [ ] 1. 모듈 구조 설계 (어떤 모듈로 분리할지 결정)
|
|
- [ ] 2. `settings.gradle`에 모듈 추가
|
|
- [ ] 3. 루트 `build.gradle` 수정 (공통 설정 분리)
|
|
- [ ] 4. 각 모듈별 `build.gradle` 생성
|
|
- [ ] 5. 디렉토리 구조 생성
|
|
- [ ] 6. 기존 소스 코드를 적절한 모듈로 이동
|
|
- [ ] 7. 패키지 구조 정리
|
|
- [ ] 8. Application 클래스 수정 (컴포넌트 스캔 설정)
|
|
- [ ] 9. 빌드 테스트 (`./gradlew clean build`)
|
|
- [ ] 10. 애플리케이션 실행 테스트
|
|
- [ ] 11. 단위 테스트 및 통합 테스트 검증
|
|
- [ ] 12. CI/CD 파이프라인 업데이트 (필요시)
|
|
|
|
---
|
|
|
|
## 참고 자료
|
|
|
|
- [Spring Boot Reference Documentation](https://docs.spring.io/spring-boot/index.html)
|
|
- [Gradle Multi-Project Builds](https://docs.gradle.org/current/userguide/multi_project_builds.html)
|
|
- [Kotlin and Spring Boot](https://spring.io/guides/tutorials/spring-boot-kotlin)
|
|
|
|
---
|
|
|
|
## 버전 정보
|
|
|
|
- Spring Boot: 4.0.1
|
|
- Kotlin: 2.2.21
|
|
- Gradle: 9.2.1
|
|
- Java: 24 (또는 21, 17)
|
|
|
|
---
|
|
|
|
**작성일:** 2026-01-11
|
|
**프로젝트:** balance
|
|
**작성자:** Claude Code
|