Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .claude/commands/create-pr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
현재 브랜치의 변경사항을 분석하여 `.github/pull_request_template.md` 양식에 맞는 PR을 자동 생성한다.

## 수행 절차

### 1단계: 변경사항 분석
아래 명령어를 **병렬로** 실행하여 정보를 수집한다:
- `git status` (변경된 파일 목록)
- `git log main..HEAD --oneline` (현재 브랜치의 커밋 내역)
- `git diff main...HEAD --stat` (변경된 파일 통계)
- `git diff main...HEAD` (전체 변경 내용)

### 2단계: PR 본문 작성
`.github/pull_request_template.md` 양식을 읽고, 수집한 정보를 기반으로 아래 규칙에 따라 본문을 작성한다.

#### 📌 Summary
- **배경**: 이 변경이 필요한 이유 (기존 문제, 요구사항)
- **목표**: 이번 PR에서 달성하려는 것
- **결과**: 변경 후 달라지는 점

#### 🧭 Context & Decision
- **문제 정의**: 현재 동작/제약, 문제(리스크), 성공 기준을 구체적으로 기술
- **선택지와 결정**: 코드에서 실제 사용된 기술적 선택(패턴, 라이브러리, 구조)과 그 이유를 기술. 대안이 명확하지 않으면 "단일 접근" 으로 표기

#### 🏗️ Design Overview
- **변경 범위**: 실제 변경된 모듈/도메인, 신규 추가 파일, 제거/대체된 파일을 나열
- **주요 컴포넌트 책임**: 변경된 주요 클래스/파일의 역할을 `ComponentName`: 설명 형태로 기술

#### 🔁 Flow Diagram
- **핵심 API 흐름마다** Mermaid `sequenceDiagram`을 작성한다
- 참여자(participant)는 실제 클래스명을 사용한다
- `autonumber`를 포함한다
- 정상 흐름과 예외 흐름(alt/else)을 모두 포함한다
- API가 여러 개면 각각 별도 다이어그램으로 작성한다

### 3단계: PR 생성
- 브랜치가 리모트에 push되지 않았으면 `git push -u origin <branch>` 실행
- `gh pr create` 명령어로 PR 생성
- PR 제목은 70자 이내, 변경의 핵심을 요약
- PR 본문은 HEREDOC으로 전달

```bash
gh pr create --title "PR 제목" --body "$(cat <<'EOF'
... 작성된 PR 본문 ...
EOF
)"
```

### 4단계: 결과 보고
- 생성된 PR URL을 사용자에게 반환한다
45 changes: 0 additions & 45 deletions .codeguide/loopers-1-week.md

This file was deleted.

132 changes: 132 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# CLAUDE.md

이 파일은 Claude Code가 프로젝트를 이해하는 데 필요한 컨텍스트를 제공합니다.

## 프로젝트 개요

**프로젝트명**: loopers-java-spring-template
**그룹 ID**: com.loopers
**라이선스**: LICENSE 파일 참조

커머스 도메인을 위한 Java/Spring Boot 기반 멀티 모듈 백엔드 템플릿 프로젝트입니다.

## 기술 스택 및 버전

| 기술 | 버전 |
|------|------|
| Java | 21 |
| Spring Boot | 3.4.4 |
| Spring Cloud Dependencies | 2024.0.1 |
| Spring Dependency Management | 1.1.7 |
| Lombok | Spring Boot BOM |
| QueryDSL | Spring Boot BOM (Jakarta) |
| SpringDoc OpenAPI | 2.7.0 |
| Micrometer | Spring Boot BOM |
| Testcontainers | Spring Boot BOM |
| JUnit 5 | Spring Boot BOM |
| Mockito | 5.14.0 |
| SpringMockK | 4.0.2 |
| Instancio JUnit | 5.0.2 |
| Slack Appender | 1.6.1 |

## 모듈 구조

```
loopers-java-spring-template/
├── apps/ # 실행 가능한 애플리케이션 (BootJar)
│ ├── commerce-api/ # REST API 서버 (Web, OpenAPI)
│ ├── commerce-streamer/ # Kafka 스트림 처리 서버
│ └── commerce-batch/ # Spring Batch 애플리케이션
├── modules/ # 공유 라이브러리 모듈
│ ├── jpa/ # JPA + QueryDSL + MySQL
│ ├── redis/ # Spring Data Redis
│ └── kafka/ # Spring Kafka
├── supports/ # 횡단 관심사 지원 모듈
│ ├── jackson/ # Jackson 직렬화 설정
│ ├── logging/ # 로깅 + Slack Appender
│ └── monitoring/ # Prometheus + Micrometer
├── docker/ # Docker 관련 설정
└── http/ # HTTP 요청 파일 (IntelliJ HTTP Client)
```

### 모듈 의존성 관계

- **commerce-api**: jpa, redis, jackson, logging, monitoring
- **commerce-streamer**: jpa, redis, kafka, jackson, logging, monitoring
- **commerce-batch**: jpa, redis, jackson, logging, monitoring

## 빌드 및 실행

```bash
# 전체 빌드
./gradlew build

# 특정 앱 실행
./gradlew :apps:commerce-api:bootRun
./gradlew :apps:commerce-streamer:bootRun
./gradlew :apps:commerce-batch:bootRun

# 테스트 실행
./gradlew test
```

## 테스트 환경

- 테스트 시 `Asia/Seoul` 타임존 사용
- 테스트 프로파일: `test`
- Testcontainers 사용 (MySQL, Redis, Kafka)
- JaCoCo 코드 커버리지 리포트 생성 (XML 포맷)

## 주요 설정

- **버전 관리**: Git 커밋 해시를 기본 버전으로 사용
- **빌드 타입**:
- `apps/*` 모듈: BootJar (실행 가능한 JAR)
- `modules/*`, `supports/*` 모듈: 일반 JAR (라이브러리)

## 코드 스타일

- Lombok 사용
- Jackson JSR310 모듈로 Java Time API 직렬화
- QueryDSL Jakarta 스펙 사용


## 개발 규칙
### 진행 Workflow - 증강 코딩
- **대원칙** : 방향성 및 주요 의사 결정은 개발자에게 제안만 할 수 있으며, 최종 승인된 사항을 기반으로 작업을 수행.
- **중간 결과 보고** : AI 가 반복적인 동작을 하거나, 요청하지 않은 기능을 구현, 테스트 삭제를 임의로 진행할 경우 개발자가 개입.
- **설계 주도권 유지** : AI 가 임의판단을 하지 않고, 방향성에 대한 제안 등을 진행할 수 있으나 개발자의 승인을 받은 후 수행.

### 개발 Workflow - TDD (Red > Green > Refactor)
- 모든 테스트는 3A 원칙으로 작성할 것 (Arrange - Act - Assert)
#### 1. Red Phase : 실패하는 테스트 먼저 작성
- 요구사항을 만족하는 기능 테스트 케이스 작성
- 테스트 예시
#### 2. Green Phase : 테스트를 통과하는 코드 작성
- Red Phase 의 테스트가 모두 통과할 수 있는 코드 작성
- 오버엔지니어링 금지
#### 3. Refactor Phase : 불필요한 코드 제거 및 품질 개선
- 불필요한 private 함수 지양, 객체지향적 코드 작성
- unused import 제거
- 성능 최적화
- 모든 테스트 케이스가 통과해야 함
## 주의사항
### 1. Never Do
- 실제 동작하지 않는 코드, 불필요한 Mock 데이터를 이요한 구현을 하지 말 것
- null-safety 하지 않게 코드 작성하지 말 것 (Java 의 경우, Optional 을 활용할 것)
- println 코드 남기지 말 것

### 2. Recommendation
- 실제 API 를 호출해 확인하는 E2E 테스트 코드 작성
- 재사용 가능한 객체 설계
- 성능 최적화에 대한 대안 및 제안
- 개발 완료된 API 의 경우, `.http/**.http` 에 분류해 작성

### 3. Priority
1. 실제 동작하는 해결책만 고려
2. null-safety, thread-safety 고려
3. 테스트 가능한 구조로 설계
4. 기존 코드 패턴 분석 후 일관성 유지
3 changes: 3 additions & 0 deletions apps/commerce-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:${project.properties["springDocOpenApiVersion"]}")

// security
implementation("org.springframework.security:spring-security-crypto")

// querydsl
annotationProcessor("com.querydsl:querydsl-apt::jakarta")
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.loopers.application.user;

import com.loopers.domain.user.User;
import com.loopers.domain.user.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class UserFacade {

private final UserService userService;

public UserInfo register(String loginId, String password, String name, String birthDate, String email) {
User user = userService.register(loginId, password, name, birthDate, email);
return UserInfo.from(user);
}

public UserInfo getMyInfo(String loginId, String password) {
User user = userService.authenticate(loginId, password);
return UserInfo.from(user);
}

public void changePassword(String loginId, String currentPassword, String newPassword) {
userService.changePassword(loginId, currentPassword, newPassword);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.loopers.application.user;

import com.loopers.domain.user.User;

public record UserInfo(
Long id,
String loginId,
String name,
String maskedName,
String birthDate,
String email
) {
public static UserInfo from(User user) {
return new UserInfo(
user.getId(),
user.getLoginId(),
user.getName(),
user.getMaskedName(),
user.getBirthDate(),
user.getEmail()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.loopers.config;

import com.loopers.interfaces.api.auth.AuthenticatedUserArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@RequiredArgsConstructor
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

private final AuthenticatedUserArgumentResolver authenticatedUserArgumentResolver;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authenticatedUserArgumentResolver);
}
}
Loading