μΌμ μ μ§§μ μκ°μ νμ©νμ¬ ν¨κ»νλ μ΄λ 릴λ μ΄ νλ«νΌ
Snack Exerciseλ μΉκ΅¬λ€κ³Ό ν¨κ» μ§§μ μ΄λμ 릴λ μ΄ νμμΌλ‘ μννλ©° 건κ°ν μ΅κ΄μ λ§λ€μ΄κ°λ μλΉμ€μ
λλ€.
κ°μμ λ¨Ήλ― κ°λ³κ², νμ§λ§ κΎΈμ€ν μ΄λν μ μλλ‘ λμ΅λλ€.
- π₯ κ·Έλ£Ή μ΄λ 릴λ μ΄: μΉκ΅¬λ€κ³Ό ν¨κ» μ΄λ λͺ©νλ₯Ό λ¬μ±
- β° μλ λ―Έμ ν λΉ: μ€μ λ μκ°μ μλμΌλ‘ μ΄λ λ―Έμ λ°°μ
- π μ€μκ° λνΉ: λ―Έμ μν μλ κΈ°λ° κ°μΈ/κ·Έλ£Ή λνΉ
- π μ€λ§νΈ μλ¦Ό: FCM κΈ°λ° λ μ΄ μλ¦Ό λ° λ―Έμ μλ¦Ό
- π― λͺ©ν λ¬μ± μμ€ν : κ·Έλ£Ήλ³ λ¦΄λ μ΄ λͺ©ν μ€μ λ° λ¬μ±
graph TB
subgraph "Client Layer"
A[Web React]
B[Mobile App]
end
subgraph "API Gateway"
C[RESTful API]
end
subgraph "Load Balancer"
D[AWS ALB]
end
subgraph "Application Layer"
E[Spring Boot]
F[Controllers]
G[Services]
H[Security JWT/OAuth2]
I[Scheduler]
J[FCM Service]
end
subgraph "Data Layer"
K[JPA/Hibernate]
L[Redis Cache]
M[(MariaDB)]
end
subgraph "Infrastructure"
N[EC2 Auto Scaling]
O[S3]
P[CodeDeploy]
Q[Docker]
R[GitHub Actions]
end
A --> C
B --> C
C --> D
D --> E
E --> F
F --> G
G --> K
K --> M
G --> L
E --> H
E --> I
E --> J
- Framework: Spring Boot 3.1.1
- Language: Java 17
- Build Tool: Gradle
- ORM: Spring Data JPA / Hibernate
- Security: Spring Security + JWT + OAuth2 (Kakao)
- Database: MariaDB (Production), H2 (Development)
- Cache: Redis
- Push Notification: Firebase Cloud Messaging (FCM)
- Container: Docker & Docker Compose
- CI/CD: GitHub Actions
- Cloud: AWS (EC2, S3, CodeDeploy, ALB)
- Monitoring: Spring Actuator
- API Docs: Swagger/OpenAPI 3.0
classDiagram
class Member {
+Long id
+String email
+String nickname
+String profileImage
+Role role
+Gender gender
+Integer birthYear
+String fcmToken
}
class Group {
+Long id
+String name
+String code
+Integer maxMemberNum
+Integer goalRelayNum
+LocalTime startTime
+LocalTime endTime
+Integer existDays
+String penalty
+Boolean isGoalAchieved
+Long currentDoingMemberId
}
class JoinList {
+Long id
+Member member
+Group group
+JoinType joinType
+Integer outCount
+Integer executedMissionCount
+Status status
}
class Mission {
+Long id
+Exercise exercise
+Member member
+Group group
+Integer calory
+LocalDateTime startAt
+LocalDateTime endAt
+Integer alarmCount
}
class Exercise {
+Long id
+String name
+ExerciseCategory category
+String videoLink
+String description
+Integer minPerKcal
}
Member "1" --> "*" JoinList
Group "1" --> "*" JoinList
Member "1" --> "*" Mission
Group "1" --> "*" Mission
Exercise "1" --> "*" Mission
- κ·Έλ£Ή μμ±: μ΄λ μκ°, λͺ©ν 릴λ μ΄ νμ, λ²μΉ λ± μ€μ
- κ·Έλ£Ή μ°Έμ¬: 6μ리 μ½λλ‘ κ°νΈνκ² κ·Έλ£Ή μ°Έμ¬
- κ·Έλ£Ή κ΄λ¦¬: λ°©μ₯ κΆνμΌλ‘ λ©€λ² κ΄λ¦¬ λ° κ·Έλ£Ή μ€μ λ³κ²½
- μλ ν λΉ: μ€μ λ μκ°μ μλμΌλ‘ λ―Έμ ν λΉ
- 릴λ μ΄ λ°©μ: ν λͺ μ΄ μλ£νλ©΄ λ€μ μ¬λμκ² μλ μ λ¬
- λλ€ μ΄λ: λ€μν μ΄λ μμ μ€ λλ€ μ ν
- λ―Έμ μλ¦Ό: λ―Έμ ν λΉ μ FCM νΈμ μλ¦Ό
- λ μ΄ μλ¦Ό: μ€μ μκ° κ²½κ³Ό μ μλ/μλ λ μ΄ μλ¦Ό
- λͺ©ν λ¬μ± μλ¦Ό: κ·Έλ£Ή λͺ©ν λ¬μ± μ μ 체 μλ¦Ό
- κ°μΈ λνΉ: λ―Έμ μν μλ κΈ°λ° μμ
- μΌκ°/λμ λνΉ: λΉμΌ λ° μ 체 κΈ°κ° λνΉ μ 곡
POST /mvp/auth/sign-up- MVP νμκ°μPOST /mvp/auth/login- MVP λ‘κ·ΈμΈPOST /api/auth/reissue- ν ν° μ¬λ°κΈPOST /api/auth/logout- λ‘κ·Έμμ
POST /groups- κ·Έλ£Ή μμ±GET /groups/{groupId}- κ·Έλ£Ή μ‘°νPATCH /groups/{groupId}- κ·Έλ£Ή μμ POST /groups/join/code- μ½λλ‘ κ·Έλ£Ή μ°Έμ¬PATCH /groups/{groupId}/initiation- κ·Έλ£Ή μμ
GET /groups/{groupId}/missions- λΉμΌ λ―Έμ νν©GET /groups/{groupId}/missions/rank- λ―Έμ λνΉPOST /missions/start- λ―Έμ μμPOST /missions/{missionId}/finish- λ―Έμ μλ£
PATCH /members/notification- FCM ν ν° λ±λ‘PATCH /alarm/reminder- μλ λ μ΄ μλ¦Ό
- GitHub Actions νΈλ¦¬κ±°
- μ ν리μΌμ΄μ λΉλ λ° ν μ€νΈ
- Docker μ΄λ―Έμ§ λΉλ λ° νΈμ
- EC2 μΈμ€ν΄μ€μ μλ λ°°ν¬
- GitHub Actions νΈλ¦¬κ±°
- μ ν리μΌμ΄μ λΉλ λ° ν μ€νΈ
- Docker μ΄λ―Έμ§ λΉλ λ° νΈμ
- AWS CodeDeployλ₯Ό ν΅ν 무μ€λ¨ λ°°ν¬
- Auto Scaling Group μ μ©
- Java 17
- Docker & Docker Compose
- Redis
- MariaDB (or H2 for local)
application-dev.yml- κ°λ° νκ²½ μ€μ application-prod.yml- μ΄μ νκ²½ μ€μ application-jwt.yml- JWT μ€μ application-oauth.yml- OAuth2 μ€μ application-fcm.yml- FCM μ€μ
snack-exercise-server/
βββ .github/ # GitHub Actions μν¬νλ‘μ°
βββ src/
β βββ main/
β β βββ java/com/soma/snackexercise/
β β β βββ auth/ # μΈμ¦/μΈκ° κ΄λ ¨
β β β βββ config/ # μ€μ ν΄λμ€
β β β βββ controller/ # REST 컨νΈλ‘€λ¬
β β β βββ domain/ # μν°ν°
β β β βββ dto/ # DTO
β β β βββ exception/ # μμΈ ν΄λμ€
β β β βββ repository/ # JPA λ ν¬μ§ν 리
β β β βββ service/ # λΉμ¦λμ€ λ‘μ§
β β β βββ util/ # μ νΈλ¦¬ν°
β β βββ resources/
β β βββ application.yml
β β βββ data.sql # μ΄κΈ° λ°μ΄ν°
β βββ test/ # ν
μ€νΈ μ½λ
βββ scripts/ # λ°°ν¬ μ€ν¬λ¦½νΈ
βββ docker-compose.yml # Docker μ€μ
βββ Dockerfile # Docker μ΄λ―Έμ§ μ€μ
βββ build.gradle # Gradle λΉλ μ€μ
- Access Token: μμ² μΈμ¦μ© (μ§§μ μ ν¨κΈ°κ°)
- Refresh Token: ν ν° μ¬λ°κΈμ© (Redis μ μ₯)
- Token Blacklist: λ‘κ·Έμμ ν ν° κ΄λ¦¬
- Kakao λ‘κ·ΈμΈ μ§μ
- μ κ· νμ μλ κ°μ νλ‘μΈμ€
- Health Check:
/healthμλν¬μΈνΈ - Swagger UI: API λ¬Έμ μλ μμ±
- Application Logs: μ€μΌμ€λ¬ λ° λ―Έμ ν λΉ λ‘κ·Έ
- Error Tracking: μμΈ μ²λ¦¬ λ° μλ¬ μλ΅ νμ€ν
- κ·Έλ£Ή λ΄ λ―Έμ μν νμκ° κ°μ₯ μ μ λ©€λ² μ ν
- λμΌν νμμΈ κ²½μ° λλ€ μ ν
- μ΄λ μ’ λ₯λ μ 체 νμμ λλ€ μ ν
- μ€μ λ κ°κ²©(
checkIntervalTime)λ§λ€ μ²΄ν¬ - μ΅λ λ
μ΄ νμ(
checkMaxNum) μ ν - λ―Έμνμμ κ·Έλ£Ήμμκ² κ°κ° λ€λ₯Έ λ©μμ§ μ μ‘
- μ€μ λ κΈ°κ°(
existDays) κ²½κ³Ό μ μλ μ’ λ£ - λͺ©ν λ¬μ± μ¬λΆ νλ¨ λ° μλ¦Ό
- λͺ¨λ
JoinListλΉνμ±ν
main: νλ‘λμ λ°°ν¬ λΈλμΉdevelop: κ°λ° ν΅ν© λΈλμΉfeature/*: κΈ°λ₯ κ°λ° λΈλμΉhotfix/*: κΈ΄κΈ μμ λΈλμΉ
feat: μλ‘μ΄ κΈ°λ₯ μΆκ°fix: λ²κ·Έ μμ docs: λ¬Έμ μμ style: μ½λ ν¬λ§·νrefactor: μ½λ 리ν©ν λ§test: ν μ€νΈ μ½λchore: λΉλ μ 무 μμ
- Bug Report
- Feature Request
- Discussion
- Refactor
- JPA N+1 λ¬Έμ ν΄κ²°: Fetch Join μ¬μ©
- μΊμ± μ λ΅: Redisλ₯Ό νμ©ν ν ν° μΊμ±
- λΉλκΈ° μ²λ¦¬:
@Asyncλ₯Ό νμ©ν μλ¦Ό μ μ‘ - DB μΈλ±μ±: μμ£Ό μ‘°νλλ μ»¬λΌ μΈλ±μ€ μ€μ
ν¨κ» μ΄λνλ©° 건κ°ν μ΅κ΄μ λ§λ€μ΄κ°μ! πͺ