Skip to content

Commit 0ef7f80

Browse files
authored
docs: 프로젝트 README 및 아키텍처 이미지 정리 (#237)
1 parent 364198e commit 0ef7f80

5 files changed

Lines changed: 429 additions & 0 deletions

File tree

README.md

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# TrainUs Backend
2+
> 프로그래머스 클라우드 기반 백엔드 데브코스 최종 프로젝트 최우수상 수상 프로젝트
3+
4+
TrainUs는 지역 기반으로 운동 메이트와 트레이너를 찾고 레슨 개설부터 신청, 결제, 리뷰까지 연결하는 운동 클래스 매칭 플랫폼입니다.
5+
6+
## 사용 스택
7+
8+
### Backend
9+
10+
![Java](https://img.shields.io/badge/Java_21-007396?style=for-the-badge&logo=openjdk&logoColor=white)
11+
![Spring Boot](https://img.shields.io/badge/Spring_Boot_3.5.3-6DB33F?style=for-the-badge&logo=springboot&logoColor=white)
12+
![Spring Security](https://img.shields.io/badge/Spring_Security-6DB33F?style=for-the-badge&logo=springsecurity&logoColor=white)
13+
![JPA](https://img.shields.io/badge/Spring_Data_JPA-6DB33F?style=for-the-badge&logo=spring&logoColor=white)
14+
15+
### Database / Cache
16+
17+
![PostgreSQL](https://img.shields.io/badge/PostgreSQL_17-4169E1?style=for-the-badge&logo=postgresql&logoColor=white)
18+
![PostGIS](https://img.shields.io/badge/PostGIS-336791?style=for-the-badge&logo=postgresql&logoColor=white)
19+
![Redis](https://img.shields.io/badge/Redis-DC382D?style=for-the-badge&logo=redis&logoColor=white)
20+
21+
### Infra / CI/CD
22+
23+
![Docker](https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white)
24+
![AWS](https://img.shields.io/badge/AWS_EC2/S3/CodeDeploy-232F3E?style=for-the-badge&logo=amazonwebservices&logoColor=white)
25+
![GitHub Actions](https://img.shields.io/badge/GitHub_Actions-2088FF?style=for-the-badge&logo=githubactions&logoColor=white)
26+
27+
### Monitoring / Test
28+
29+
![Prometheus](https://img.shields.io/badge/Prometheus-E6522C?style=for-the-badge&logo=prometheus&logoColor=white)
30+
![Grafana](https://img.shields.io/badge/Grafana-F46800?style=for-the-badge&logo=grafana&logoColor=white)
31+
![JUnit5](https://img.shields.io/badge/JUnit5-25A162?style=for-the-badge&logo=junit5&logoColor=white)
32+
![JMeter](https://img.shields.io/badge/JMeter-D22128?style=for-the-badge&logo=apachejmeter&logoColor=white)
33+
34+
## 프로젝트 소개
35+
36+
운동을 꾸준히 하고 싶어도 함께 운동할 사람을 찾기 어렵거나 지역 기반으로 신뢰할 수 있는 트레이너와 레슨을 찾기 어려운 문제가 있습니다.
37+
38+
TrainUs는 사용자가 자신의 지역을 기준으로 운동 메이트 또는 트레이너를 찾고 레슨 개설과 신청, 결제, 후기 작성까지 하나의 흐름으로 이용할 수 있도록 설계한 서비스입니다.
39+
40+
### 주요 목표
41+
42+
- 혼자 하기 어려운 운동을 함께할 운동 메이트를 지역 기반으로 찾을 수 있도록 합니다.
43+
- 축구, 배드민턴 등 다인원 운동의 참여자를 쉽게 모집할 수 있도록 합니다.
44+
- 무료/유료 운동 레슨을 탐색하고 신청할 수 있는 흐름을 제공합니다.
45+
- 리뷰, 평점, 랭킹을 통해 트레이너와 레슨에 대한 신뢰도 판단 기준을 제공합니다.
46+
- 쿠폰과 결제를 통해 유료 레슨 신청 흐름을 확장할 수 있도록 합니다.
47+
48+
### 개선 완료 목표
49+
50+
- 선착순 레슨 신청처럼 순간 트래픽이 몰리는 시나리오를 Redis Stream 기반 비동기 처리 구조로 고도화했습니다.
51+
- 법정동 중심 검색을 사용자 좌표 기반 반경 검색과 가까운 순 정렬까지 확장했습니다.
52+
- API 서버와 Consumer 서버를 분리해 요청 접수와 DB 반영 작업의 리소스 경합을 줄였습니다.
53+
54+
## 팀 구성
55+
56+
| 구분 | 내용 |
57+
| --- | --- |
58+
| 팀명 | 3성 |
59+
| 협업 방식 | WBS 기반 일정 관리, API 명세 공유, Daily Scrum, PR 리뷰, 피어 리뷰어 지정 |
60+
61+
<table>
62+
<tr>
63+
<th width="10%"><div align="center">항목</div></th>
64+
<th width="18%"><div align="center">지민혁</div></th>
65+
<th width="18%"><div align="center">김태호</div></th>
66+
<th width="18%"><div align="center">임창인</div></th>
67+
<th width="18%"><div align="center">김지은</div></th>
68+
<th width="18%"><div align="center">나상연</div></th>
69+
</tr>
70+
<tr align="center">
71+
<td><strong>프로필</strong></td>
72+
<td><a href="https://github.com/Ji-minhyeok"><img src="https://github.com/Ji-minhyeok.png" width="64" /></a></td>
73+
<td><a href="https://github.com/taeho4523"><img src="https://github.com/taeho4523.png" width="64" /></a></td>
74+
<td><a href="https://github.com/cba700"><img src="https://github.com/cba700.png" width="64" /></a></td>
75+
<td><a href="https://github.com/iamjieunkim"><img src="https://github.com/iamjieunkim.png" width="64" /></a></td>
76+
<td><a href="https://github.com/ense333"><img src="https://github.com/ense333.png" width="64" /></a></td>
77+
</tr>
78+
<tr align="center">
79+
<td><strong>역할</strong></td>
80+
<td>PO, BE</td>
81+
<td>BE 팀장</td>
82+
<td>BE</td>
83+
<td>BE</td>
84+
<td>BE</td>
85+
</tr>
86+
<tr align="center">
87+
<td><strong>주요 담당 영역</strong></td>
88+
<td>유저 레슨<br />CI/CD<br />검색/레슨 선착순 고도화</td>
89+
<td>유저 쿠폰<br />CI/CD<br />쿠폰 선착순 고도화</td>
90+
<td>로그인/회원가입<br />프로필<br />랭킹</td>
91+
<td>강사용 레슨<br />관리자 쿠폰</td>
92+
<td>댓글<br />리뷰<br />Toss Payments 결제</td>
93+
</tr>
94+
</table>
95+
96+
## 주요 기능
97+
98+
### 회원 및 프로필
99+
100+
- 이메일 인증 기반 회원가입과 로그인
101+
- JWT 기반 인증
102+
- 내 프로필 조회/수정
103+
- 다른 사용자 프로필 조회
104+
105+
### 레슨
106+
107+
- 레슨 생성, 수정, 삭제
108+
- 레슨 상세 조회
109+
- 지역, 키워드, 카테고리 기반 검색
110+
- 개설자가 신청자를 승인/거절하는 수락제 레슨 관리
111+
112+
**고도화/개선 사항:**
113+
114+
- 기존 법정동 기반 검색을 사용자 좌표 기반 반경 검색으로 확장했습니다.
115+
- PostGIS `geography` 타입과 GiST 인덱스를 활용해 반경 검색과 가까운 순 정렬을 지원했습니다.
116+
117+
### 레슨 신청
118+
119+
- 수락제 레슨 신청
120+
- 선착순 레슨 신청
121+
- 비동기 신청 상태 조회
122+
- 내 신청 목록 조회
123+
124+
**고도화/개선 사항:**
125+
126+
- 정원이 제한된 선착순 레슨에 다수의 사용자가 동시에 신청하는 상황을 병목 시나리오로 정의했습니다.
127+
- API 서버는 Redis에서 중복 신청과 재고를 먼저 확인한 뒤 접수 ID를 반환하고 DB 반영은 비동기 처리로 분리했습니다.
128+
- Consumer 서버가 Redis Stream 메시지를 batch 단위로 DB에 반영하고 사용자는 접수 ID로 처리 상태를 조회합니다.
129+
130+
![TrainUs Async Processing](./docs/architecture/trainus-architecture-async-processing.png)
131+
132+
### 결제 및 쿠폰
133+
134+
- Toss Payments 결제 승인/취소
135+
- 쿠폰 발급, 적용, 복원
136+
- 쿠폰 상태 스케줄링
137+
138+
### 커뮤니티
139+
140+
- 댓글/대댓글
141+
- 리뷰와 평점
142+
- 리뷰와 평점 기반 트레이너 랭킹
143+
144+
## 주요 API
145+
146+
| 기능 | Method | Endpoint |
147+
| --- | --- | --- |
148+
| 레슨 검색 | GET | `/api/v1/lessons` |
149+
| 위치 기반 레슨 검색 | GET | `/api/v1/lessons/search/nearby` |
150+
| 레슨 상세 조회 | GET | `/api/v1/lessons/{lessonId}` |
151+
| 수락제 레슨 신청 | POST | `/api/v1/lessons/{lessonId}/applications/approval` |
152+
| 선착순 레슨 신청 | POST | `/api/v1/lessons/{lessonId}/applications/open-run` |
153+
| 비동기 신청 상태 조회 | GET | `/api/v1/lessons/apply/status/{requestId}` |
154+
| 레슨 신청 취소 | DELETE | `/api/v1/lessons/{lessonId}/application` |
155+
| 내 신청 목록 조회 | GET | `/api/v1/lessons/my-applications` |
156+
| 개설 레슨 신청자 조회 | GET | `/api/v1/lessons/{lessonId}/applications` |
157+
| 레슨 신청 승인/거절 | POST | `/api/v1/lessons/applications/{lessonApplicationId}` |
158+
| 랭킹 조회 | GET | `/api/v1/rankings` |
159+
| 결제 승인 | POST | `/api/v1/payments/confirm` |
160+
| S3 업로드 URL 발급 | GET | `/api/v1/s3/posturl` |
161+
162+
Swagger 설정이 포함되어 있어 실행 환경에서 API 문서를 확인할 수 있습니다.
163+
164+
## 시스템 구조
165+
166+
![TrainUs Architecture](./docs/architecture/trainus-architecture.png)
167+
168+
운영 환경에서는 API 서버와 Consumer 서버를 역할별 profile로 분리합니다.
169+
170+
| Role | App Port | Actuator Port | 주요 책임 |
171+
| --- | ---: | ---: | --- |
172+
| `api` | 8080 | 8082 | HTTP 요청 수신, 인증, 레슨 검색, 신청 접수 |
173+
| `consumer` | 8081 | 8083 | Redis Stream 소비, DB 반영, 보정 스케줄링 |
174+
175+
Redis는 목적에 따라 Core Redis와 MQ Redis로 분리해 사용합니다.
176+
177+
| Redis | 기본 포트 | 용도 |
178+
| --- | ---: | --- |
179+
| `redis-core` | 6379 | 재고, 중복 신청, 대기열, 분산 락 |
180+
| `redis-mq` | 6380 | Redis Stream, 신청 처리 상태 |
181+
182+
## 배포 자동화
183+
184+
배포는 GitHub Actions, S3, CodeDeploy를 사용합니다.
185+
GitHub Actions가 빌드 산출물을 S3에 업로드하고 CodeDeploy가 EC2에서 배포 hook을 실행하는 Pull 방식으로 구성했습니다.
186+
187+
```text
188+
develop push
189+
-> GitHub Actions build
190+
-> deploy.zip 생성
191+
-> S3 업로드
192+
-> CodeDeploy 배포 생성
193+
-> EC2에서 appspec hook 실행
194+
```
195+
196+
CodeDeploy hook:
197+
198+
| Script | 역할 |
199+
| --- | --- |
200+
| `scripts/setup.sh` | 배포 디렉터리 준비, S3에서 환경변수 파일 동기화 |
201+
| `scripts/stop.sh` | 기존 애플리케이션 종료 |
202+
| `scripts/start.sh` | 서버 역할에 맞는 Spring profile로 애플리케이션 실행 |
203+
204+
## 협업 방식
205+
206+
- Notion 기반 WBS와 작업 보드로 기능 단위 진행 상황을 관리했습니다.
207+
- API 명세를 공유하며 프론트엔드와 요청/응답 형식을 조율했습니다.
208+
- Git commit convention, code convention, class naming convention을 사전에 정리했습니다.
209+
- PR 리뷰와 피어 리뷰어 지정 방식을 통해 코드 변경 사항을 검토했습니다.
210+
- Daily Scrum과 회의록을 통해 이슈와 의사결정 내용을 기록했습니다.
211+
212+
## 주요 도메인 구조
213+
214+
```text
215+
src/main/java/com/threestar/trainus
216+
├── domain
217+
│ ├── user # 회원, 이메일 인증
218+
│ ├── profile # 프로필
219+
│ ├── lesson # 레슨 생성/검색/신청/선착순 처리
220+
│ ├── coupon # 쿠폰 생성/발급/사용
221+
│ ├── payment # Toss 결제/취소
222+
│ ├── review # 리뷰
223+
│ ├── comment # 댓글/대댓글
224+
│ ├── ranking # 랭킹
225+
│ └── file # S3 URL 발급
226+
└── global
227+
├── config # Security, Redis, S3, Swagger, Scheduler
228+
├── resolver # @LoginUser
229+
├── aop # Redisson distributed lock
230+
└── exception # 공통 예외 처리
231+
```
124 KB
Loading
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from diagrams import Cluster, Diagram, Edge
2+
from diagrams.aws.compute import EC2
3+
from diagrams.aws.network import ALB
4+
from diagrams.onprem.database import PostgreSQL
5+
from diagrams.onprem.inmemory import Redis
6+
7+
8+
graph_attr = {
9+
"fontsize": "18",
10+
"pad": "0.6",
11+
"splines": "polyline",
12+
"nodesep": "1.0",
13+
"ranksep": "1.3",
14+
}
15+
16+
17+
with Diagram(
18+
"TrainUs Architecture - Async Processing",
19+
show=False,
20+
filename="trainus-architecture-async-processing",
21+
outformat="png",
22+
direction="LR",
23+
graph_attr=graph_attr,
24+
):
25+
client = ALB("Client / ALB")
26+
27+
with Cluster("API Server"):
28+
api = EC2("API Server\n(api profile)")
29+
30+
with Cluster("Redis Core"):
31+
core = Redis("Redis Core\nstock / duplicate")
32+
33+
with Cluster("Consumer Server - Admission"):
34+
admission = EC2("Consumer Server\n(admission dequeue)")
35+
stream = Redis("Redis Stream\nlesson:apply:stream")
36+
37+
with Cluster("Consumer Server"):
38+
consumer = EC2("Consumer Server\n(batch insert)")
39+
40+
with Cluster("Database"):
41+
db = PostgreSQL("PostgreSQL / PostGIS")
42+
43+
client >> Edge(label="apply request") >> api
44+
api >> Edge(label="duplicate / stock check") >> core
45+
core >> Edge(label="dequeue requestIds") >> admission
46+
admission >> Edge(label="SET PROCESSING + XADD") >> stream
47+
stream >> Edge(label="XREADGROUP batch") >> consumer
48+
consumer >> Edge(label="batch insert") >> db
49+
consumer >> Edge(label="XACK / XDEL", dir="back") >> stream
800 KB
Loading

0 commit comments

Comments
 (0)