Skip to content

Commit 052aa1f

Browse files
authored
Merge pull request #104 from Central-MakeUs/refactor/#102-improve-10s-interval-push
Refactor/#102 improve 10s interval push
2 parents 3a18a9c + fb92b7b commit 052aa1f

File tree

12 files changed

+636
-102
lines changed

12 files changed

+636
-102
lines changed

.claude/CLAUDE.md

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Commands
6+
7+
```bash
8+
# 빌드
9+
./gradlew build
10+
./gradlew clean build
11+
12+
# 실행 (local 프로파일)
13+
./gradlew bootRun --args='--spring.profiles.active=local'
14+
15+
# 테스트 전체 실행
16+
./gradlew test
17+
18+
# 특정 테스트 클래스 실행
19+
./gradlew test --tests "akuma.whiplash.domains.alarm.domain.service.AlarmCommandServiceTest"
20+
21+
# 특정 테스트 메서드 실행
22+
./gradlew test --tests "akuma.whiplash.domains.alarm.domain.service.AlarmCommandServiceTest.RingAlarmTest.success"
23+
24+
# Docker Compose로 전체 스택 실행 (MySQL, Redis, Prometheus, Grafana, k6)
25+
docker-compose up -d
26+
```
27+
28+
## 아키텍처
29+
30+
### 레이어 구조 (도메인별로 반복)
31+
32+
```
33+
presentation (Controller)
34+
35+
application (UseCase) ← 도메인 서비스 조합, DTO 변환
36+
37+
domain (CommandService / QueryService) ← 비즈니스 규칙
38+
39+
persistence (Repository / Entity)
40+
```
41+
42+
각 도메인(`alarm`, `auth`, `member`, `place`)은 위 4개 레이어를 독립적으로 가진다.
43+
44+
### 도메인 패키지 구조
45+
46+
```
47+
akuma.whiplash/
48+
├── domains/
49+
│ └── {domain}/
50+
│ ├── application/
51+
│ │ ├── dto/ (request, response, etc)
52+
│ │ ├── mapper/ (Entity ↔ DTO)
53+
│ │ ├── usecase/ (UseCase 인터페이스 + 구현)
54+
│ │ └── scheduler/ (alarm 도메인만 존재)
55+
│ ├── domain/
56+
│ │ ├── constant/ (enum)
57+
│ │ └── service/ (CommandService, QueryService)
58+
│ ├── persistence/
59+
│ │ ├── entity/
60+
│ │ └── repository/
61+
│ ├── presentation/
62+
│ └── exception/ (도메인별 ErrorCode enum)
63+
64+
├── infrastructure/
65+
│ ├── redis/ (RedisRepository, RedisService, RingingAlarmRedisRepository)
66+
│ └── firebase/ (FcmService, MockFcmService)
67+
68+
└── global/
69+
├── config/
70+
│ ├── security/ (JWT, Spring Security)
71+
│ └── scheduler/ (스케줄러 ThreadPool)
72+
├── exception/ (ApplicationException)
73+
└── response/code/ (공통 ErrorCode, SuccessCode)
74+
```
75+
76+
### 예외 처리 규칙
77+
78+
- 모든 도메인 예외는 `ApplicationException.from(ErrorCode)` 패턴 사용
79+
- 각 도메인에 `{Domain}ErrorCode` enum 존재 (예: `AlarmErrorCode`, `AuthErrorCode`)
80+
- ErrorCode enum 포맷: `DOMAIN_STATUS(HttpStatus.STATUS, "Domain_x0n", "message")`
81+
- 허용 HTTP 상태: `400`, `401`, `403`, `404`, `409`만 사용
82+
83+
### API 응답 규칙
84+
85+
- 공통 래퍼: `ApplicationResponse<T>`
86+
- 성공 코드: `SuccessCode` enum
87+
- 오류 코드: `CommonErrorCode` / 도메인별 `*ErrorCode` enum
88+
89+
## 코드 컨벤션
90+
91+
### 레이어별 메서드 명명 규칙
92+
93+
| 동작 | Controller / UseCase / Service | Repository |
94+
|---|---|---|
95+
| 조회 | `getXxx` | `findByXxx`, `countByXxx`, `existsByXxx` |
96+
| 생성 | `createXxx` | `insertXxx` |
97+
| 삭제 | `removeXxx` | `deleteXxx` |
98+
| 수정 | `modifyXxx` | `updateXxx` |
99+
100+
### 요청/응답 객체 필드 규칙
101+
102+
- PK는 반드시 도메인명을 붙인다: `Alarm.id``alarmId`
103+
- URL에 도메인이 명시되는 요청 객체는 도메인명 생략 (PK 제외): `POST /api/alarms` 의 바디는 `purpose` (not `alarmPurpose`)
104+
- 응답 List 필드명: `{도메인명}s` (자료형 명시 X) → `alarms`, `tickets`
105+
- 중첩 객체 속성은 엔티티 이름 생략 (PK 제외)
106+
- Enum 값은 `.name()` 그대로 반환
107+
- 날짜는 `ISO_LOCAL_DATE`(2011-12-03) 또는 `ISO_LOCAL_DATE_TIME`(2011-12-03T10:15:30) 포맷 사용
108+
- 페이지네이션 파라미터: `page`, `size`, `sortType`
109+
110+
## 테스트 컨벤션
111+
112+
### 어떤 테스트를 선택할까
113+
114+
| 목적 | 사용할 어노테이션 |
115+
|---|---|
116+
| 컨트롤러 요청-응답 / 검증 / 예외 핸들링 | `@WebMvcTest` |
117+
| 서비스 비즈니스 로직 / 트랜잭션 경계 | `@ExtendWith(MockitoExtension.class)` |
118+
| 리포지토리 / 엔티티 / JPQL | `@PersistenceTest` |
119+
| 보안 + 필터 + DB/Redis 연동 + 전체 플로우 | `@IntegrationTest` |
120+
| Redis Sorted Set 등 Redis 슬라이스 | `@DataRedisTest` + `RedisContainerInitializer` |
121+
122+
### 테스트 어노테이션 상세
123+
124+
- **`@ExtendWith(MockitoExtension.class)`**: 서비스 단위 테스트. DB/Redis 없음, Mockito만 사용
125+
- **`@WebMvcTest`**: 컨트롤러 슬라이스. 보안 필터 제외 시 `@AutoConfigureMockMvc(addFilters = false)`. 협력 빈은 `@MockitoBean`으로 주입
126+
- **`@PersistenceTest`**: MySQL Testcontainer + `@DataJpaTest`. JPA Auditing 필요 시 `@Import(JpaAuditingConfig.class)`
127+
- **`@IntegrationTest`**: 전체 Spring 컨텍스트, MySQL + Redis Testcontainer, 트랜잭션 롤백
128+
- **`@DataRedisTest`** + `@ContextConfiguration(initializers = RedisContainerInitializer.class)`: Redis 슬라이스
129+
130+
### 테스트 구조 규칙 (@Nested)
131+
132+
하나의 테스트 클래스는 **클래스의 각 메서드마다 `@Nested` inner class 하나**로 구성한다.
133+
각 inner class 안에 성공/실패 케이스를 모두 작성한다.
134+
135+
```
136+
{원본클래스}Test
137+
└── @Nested {메서드명}Test ← 메서드마다 inner class
138+
├── success()
139+
├── fail_{비즈니스실패이유}()
140+
└── fail_{다른실패이유}()
141+
```
142+
143+
예시:
144+
```java
145+
@DisplayName("AlarmCommandService Unit Test")
146+
@ExtendWith(MockitoExtension.class)
147+
class AlarmCommandServiceTest {
148+
149+
@Mock private AlarmRepository alarmRepository;
150+
@InjectMocks private AlarmCommandServiceImpl alarmCommandService;
151+
152+
@Nested
153+
@DisplayName("alarmOff - 알람 끄기(OFF)")
154+
class AlarmOffTest {
155+
156+
@Test
157+
@DisplayName("성공: 주간 OFF 한도 내에서 알람을 끈다")
158+
void success() { ... }
159+
160+
@Test
161+
@DisplayName("실패: 주간 OFF 한도를 초과하면 예외를 던진다")
162+
void fail_weeklyLimitExceeded() { ... }
163+
}
164+
165+
@Nested
166+
@DisplayName("ringAlarm - 알람 울림")
167+
class RingAlarmTest {
168+
169+
@Test
170+
@DisplayName("성공: alarmRinging=true, 로그 저장, Redis 적재가 모두 수행된다")
171+
void success() { ... }
172+
173+
@Test
174+
@DisplayName("실패: 알람 시간이 되지 않았으면 예외를 던진다")
175+
void fail_notAlarmTime() { ... }
176+
}
177+
}
178+
```
179+
180+
### 클래스 / 메서드 네이밍
181+
182+
- 루트 테스트 클래스: `{원본클래스명}Test`
183+
- inner 테스트 클래스: `{메서드명}Test` (ex. `alarmOff``AlarmOffTest`)
184+
- 테스트 메서드: `success` / `fail_{비즈니스_관점_실패이유}` (ex. `fail_weeklyLimitExceeded`, `fail_memberNotFound`)
185+
186+
### DisplayName 규칙
187+
188+
- 문장형으로 작성. "~테스트" 금지
189+
- 결과까지 기술: `"성공: 주간 OFF 한도 내에서 알람을 끈다"` / `"실패: 한도 초과 시 예외를 던진다"`
190+
- inner class의 `@DisplayName`: `"{메서드명} - {한글 기능 설명}"` (ex. `"alarmOff - 알람 끄기(OFF)"`)
191+
- 도메인 용어 사용 (메서드 이름 관점 X, 정책 관점 O)
192+
193+
### BDD 스타일 (Given / When / Then)
194+
195+
모든 테스트는 `// given`, `// when`, `// then` 주석으로 구분한다.
196+
197+
### 테스트 픽스처
198+
199+
`src/test/java/akuma/whiplash/common/fixture/`에 enum 기반 픽스처 존재:
200+
- `MemberFixture` — 테스트용 멤버 (`MEMBER_1` ~ `MEMBER_N`), `toMockEntity()` 제공
201+
- `AlarmFixture` — 테스트용 알람 (`ALARM_01` ~ `ALARM_N`), `toMockEntity()` 제공
202+
- `AlarmOccurrenceFixture`
203+
204+
### FCM 테스트
205+
206+
프로파일 `test`에서는 `MockFcmService`가 자동으로 등록되어 실제 FCM 요청을 보내지 않는다.
207+
208+
## 커밋 메시지 컨벤션
209+
210+
```
211+
[#이슈번호] :Emoji: <type>: <subject>
212+
213+
<body>
214+
- 파일명
215+
- 변경 내용
216+
217+
<footer>
218+
- 해결: #이슈번호
219+
```
220+
221+
| Type | Emoji | 설명 |
222+
|---|---|---|
223+
| Feature || 새로운 기능 추가 |
224+
| Fix | 🐛 | 버그 수정 |
225+
| Docs | 📝 | 문서 수정 |
226+
| Style | 🎨 | 코드 포맷팅 (로직 변경 없음) |
227+
| Refactor | ♻️ | 리팩토링 |
228+
| Test || 테스트 코드 추가/수정 |
229+
| Chore | 🔧 | 빌드, 패키지 매니저 수정 |
230+
231+
Subject 규칙: 50자 이하, 마침표 없음, 한글 개조식 또는 영문 동사원형 대문자 시작.
232+
233+
## 인프라 의존성
234+
235+
| 서비스 | 용도 | 설정 파일 |
236+
|---|---|---|
237+
| MySQL 8.0 | 메인 DB | `mysql.yml` |
238+
| Redis 7.2 | 토큰 캐시, 알람 울림 상태 (`alarm:ringing` Sorted Set) | `redis.yml` |
239+
| Firebase FCM | 푸시 알림 | `whiplash-firebase-key.json` |
240+
| Google Sheets API | 알람 삭제 사유 로깅 | `oauth.yml` |
241+
| Prometheus + Grafana | 메트릭 수집/시각화 | `docker-compose.yml` |
242+
| Sentry | 에러 트래킹 | `sentry.yml` |
243+
244+
245+
## 스프링 프로파일
246+
247+
`local` / `dev` / `qa` / `prod``--spring.profiles.active={profile}`으로 지정.
248+
각 프로파일은 `resources/` 하위의 `mysql.yml`, `redis.yml` 등을 import한다.
249+
250+
## Claude Code 관련 질문 처리
251+
252+
claude-code-guide는 틀린 답을 낼 때가 있다. 사용자가 Claude Code 기능에 대해 추가 질문을 하면, 공식 문서를 curl로 직접 참조해서 답한다.
253+
254+
```bash
255+
curl https://code.claude.com/docs/ko/overview.md
256+
```
257+
258+
문서 URL 패턴: `https://code.claude.com/docs/ko/{페이지명}.md`
259+
260+
답변 후에는 `AskUserQuestion`으로 퀴즈를 내서 사용자가 직접 따라해보도록 안내한다.

logs/app-error.log

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)