ν₯μ¬κ³ λ μν€ν μ²(Hexagonal Architecture) ν¨ν΄μ μ μ©νμ¬ μ€κ³λ μ€νΈλ¦¬λ° μλΉμ€ νλ«νΌμ λλ€. μ΄ λ¬Έμλ μ κ· κ°λ°μκ° νλ‘μ νΈμ ꡬ쑰λ₯Ό μ΄ν΄νκ³ , λ‘컬 νκ²½μμ νλ‘μ νΈλ₯Ό μ€νν μ μλλ‘ μ νν κ°μ΄λλ₯Ό μ 곡νλ κ²μ λͺ©νλ‘ ν©λλ€.
λ³Έ νλ‘μ νΈλ μ ν리μΌμ΄μ μ ν΅μ¬ λΉμ¦λμ€ λ‘μ§(Core)κ³Ό μΈλΆ κΈ°μ (Adapters)μ λΆλ¦¬νλ ν₯μ¬κ³ λ μν€ν μ²λ₯Ό λ°λ¦ λλ€. μ΄ κ΅¬μ‘°λ μ μ°νκ³ νμ₯ κ°λ₯νλ©°, μ μ§λ³΄μκ° μ©μ΄ν μμ€ν μ λ§λλ λ° λμμ μ€λλ€.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Applications β
β βββββββββββββββββββ βββββββββββββββββββ β
β β app-api β β app-batch β β
β β (REST API) β β (Batch Job) β β
β βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β²
β μμ‘΄μ± λ°©ν₯
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Core β
β ββββββββββββββββββ βββββββββββββββββ ββββββββββββββββ β
β β core-domain β<-β core-service β->β core-port β β
β β (λλ©μΈ λͺ¨λΈ) β β (λΉμ¦λμ€ λ‘μ§) β β (μΈν°νμ΄μ€) β β
β ββββββββββββββββββ βββββββββββββββββ ββββββββββββββββ β
β β² β
β β β
β ββββββββββββββββ β
β β core-usecase β β
β β (μ μ€μΌμ΄μ€) β β
β ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β²
β μμ‘΄μ± λ°©ν₯
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Adapters β
β ββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββ
β β adapter-http β βadapter-persistenceβ β adapter-redis ββ
β β (μΈλΆ API μ°λ) β β (DB μ°λ) β β (μΊμ μ°λ) ββ
β ββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| κ³μΈ΅ | λͺ¨λ | μ€λͺ |
|---|---|---|
| Applications | app-api |
REST APIλ₯Ό μ 곡νλ Spring Boot μΉ μ ν리μΌμ΄μ μ λλ€. |
app-batch |
λ°μ΄ν° λ§μ΄κ·Έλ μ΄μ λ± λ°°μΉ μμ μ μ²λ¦¬νλ μ ν리μΌμ΄μ μ λλ€. | |
| Core | core-domain |
νλ‘μ νΈμ ν΅μ¬ λλ©μΈ λͺ¨λΈκ³Ό λΉμ¦λμ€ κ·μΉμ μ μν©λλ€. |
core-port |
μΈλΆ μμ€ν (DB, API λ±)κ³Όμ ν΅μ μ μν μΈν°νμ΄μ€(Port)λ₯Ό μ μν©λλ€. | |
core-service |
μ μ€μΌμ΄μ€μ μ€μ λΉμ¦λμ€ λ‘μ§μ ꡬνν©λλ€. | |
core-usecase |
μ ν리μΌμ΄μ μ΄ μμ€ν μ μ΄λ€ κΈ°λ₯μ μ 곡ν΄μΌ νλμ§ μ μνλ μ μ€μΌμ΄μ€μ λλ€. | |
| Adapters | adapter-http |
μΈλΆ API(TMDB, Kakao)μμ ν΅μ μ ꡬννλ μ΄λν°μ λλ€. |
adapter-persistence |
λ°μ΄ν°λ² μ΄μ€(MySQL, JPA) μ°λμ μ²λ¦¬νλ μμμ± μ΄λν°μ λλ€. | |
adapter-redis |
μΊμ± μ²λ¦¬λ₯Ό μν Redis μ°λ μ΄λν°μ λλ€. | |
| Commons | stream-fix-commons |
μμΈ μ²λ¦¬ λ± μ¬λ¬ λͺ¨λμμ 곡ν΅μΌλ‘ μ¬μ©λλ μ½λλ₯Ό ν¬ν¨ν©λλ€. |
| Frontend | stream-fix-frontend |
React κΈ°λ°μ νλ‘ νΈμλ μ ν리μΌμ΄μ μ λλ€. |
| Infra | infra |
Docker Composeλ₯Ό μ΄μ©ν΄ κ°λ°μ νμν μΈνλΌ(MySQL, Redis)λ₯Ό μ€μ ν©λλ€. |
- Java:
21 - Spring Boot:
3.5.0 - Spring Data JPA
- QueryDSL:
5.1.0 - Gradle:
8.13 - DB Migration: Flyway
- Database: MySQL
8.0 - Cache: Redis
- Code Formatting: Spotless (google-java-format)
- React:
19.1.0 - Axios, Bootstrap
- Docker & Docker Compose
μμΈ λ²μ μ 보λ versions.properties λ° κ° λͺ¨λμ build.gradle.kts νμΌ, package.json νμΌμ μ°Έκ³ νμΈμ.
- Java 21
- Node.js 16 μ΄μ
- Docker & Docker Compose
git clone https://github.com/shokoku/stream-fix.git
cd stream-fixνλ‘μ νΈμ νμν MySQL, Redis λ°μ΄ν°λ² μ΄μ€λ₯Ό Dockerλ‘ μ€νν©λλ€.
cd infra
docker-compose up -dπ³ Docker 컨ν μ΄λκ° μ μμ μΌλ‘ μ€νλμλμ§ νμΈνμΈμ. (
docker ps)
νλ‘μ νΈ μ€νμ νμν νκ²½ λ³μλ₯Ό μ€μ ν©λλ€. μν νμΌμ 볡μ¬νμ¬ μ€μ κ°μ μ λ ₯νμΈμ.
1) λ°±μλ νκ²½λ³μ (.env)
λ£¨νΈ κ²½λ‘μ .env.sample νμΌμ .envλ‘ λ³΅μ¬ν ν, μλ νλͺ©λ€μ μμ μ νκ²½μ λ§κ² μμ ν©λλ€.
# 1. .env νμΌ μμ±
cp .env.sample .env
# 2. .env νμΌ μμ
vi .env# .env
KAKAO_CLIENT_ID=your-kakao-client-id
KAKAO_CLIENT_SECRET=your-kakao-client-secret
KAKAO_REDIRECT_URL=http://localhost:3000/login/oauth2/code/kakao
JWT_SECRET=your-jwt-secret-key-that-is-long-enough-to-be-secure2) TMDB API Key μ€μ μν λ°μ΄ν° λ§μ΄κ·Έλ μ΄μ μ νμν TMDB API ν€λ₯Ό μ€μ ν©λλ€.
# 1. adapter-http-property.yml νμΌ μμ±
cp stream-fix-adapters/adapter-http/src/main/resources/adapter-http-property-sample.yml \
stream-fix-adapters/adapter-http/src/main/resources/adapter-http-property.yml
# 2. adapter-http-property.yml νμΌ μμ
vi stream-fix-adapters/adapter-http/src/main/resources/adapter-http-property.yml# adapter-http-property.yml
tmdb:
auth:
access-token: your-tmdb-access-tokenIntelliJ IDEAμ κ°μ IDEμμ StreamFixApplication.javaλ₯Ό μ§μ μ€ννκ±°λ, ν°λ―Έλμμ Gradle λͺ
λ Ήμ΄λ₯Ό μ¬μ©ν©λλ€.
# API μ ν리μΌμ΄μ
μ€ν
./gradlew :stream-fix-apps:app-api:bootRunβ μ΅μ΄ μ€ν μ μμ‘΄μ±μ λ€μ΄λ‘λνλ―λ‘ μκ°μ΄ λ€μ μμλ μ μμ΅λλ€.
λ³λμ ν°λ―Έλμ μ΄μ΄ νλ‘ νΈμλ κ°λ° μλ²λ₯Ό μ€νν©λλ€.
# νλ‘ νΈμλ ν΄λλ‘ μ΄λ
cd stream-fix-frontend
# μμ‘΄μ± μ€μΉ
npm install
# κ°λ° μλ² μ€ν
npm start- νλ‘ νΈμλ μλ²λ
http://localhost:3000μμ μ€νλ©λλ€.
TMDBμμ μν μ 보λ₯Ό κ°μ Έμ λ°μ΄ν°λ² μ΄μ€μ μ μ₯νλ λ°°μΉ μμ μ μ€νν μ μμ΅λλ€.
# app-batch λͺ¨λ μ€ν
./gradlew :stream-fix-apps:app-batch:bootRun| Method | URL | μ€λͺ | μΈμ¦ νμ |
|---|---|---|---|
POST |
/api/v1/user/register |
μΌλ° νμκ°μ μ μ§νν©λλ€. | No |
POST |
/api/v1/user/login |
μ΄λ©μΌκ³Ό λΉλ°λ²νΈλ‘ λ‘κ·ΈμΈν©λλ€. | No |
POST |
/api/v1/user/callback |
μΉ΄μΉ΄μ€ OAuth μΈμ¦ μ½λ°±μ μ²λ¦¬ν©λλ€. | No |
GET |
/api/v1/movie/client/{page} |
μΈλΆ(TMDB)μμ μν λͺ©λ‘μ μ‘°νν©λλ€. | Yes |
POST |
/api/v1/movie/search |
DBμ μ μ₯λ μν λͺ©λ‘μ μ‘°νν©λλ€. | Yes |
POST |
/api/v1/movie/{movieId}/download |
νΉμ μνλ₯Ό λ€μ΄λ‘λν©λλ€. | Yes |
POST |
/api/v1/movie/{movieId}/like |
νΉμ μνμ 'μ’μμ'λ₯Ό νμν©λλ€. | Yes |
GET |
/api/v1/sample |
μν APIλ₯Ό νΈμΆν©λλ€. | Yes |
νλ‘μ νΈμλ Spotless νλ¬κ·ΈμΈμ΄ μ μ©λμ΄ μμ΄, μ»€λ° μ μ μ½λλ₯Ό google-java-format μ€νμΌμ λ§κ² μ 리ν΄μΌ ν©λλ€. μλ λͺ
λ Ήμ μ€ννμ¬ μ½λ μ€νμΌμ μ μ©νμΈμ.
./gradlew spotlessApplyGit-Flow λλ GitHub Flowμ κ°μ λΈλμΉ μ λ΅μ λ°λ₯΄λ κ²μ κΆμ₯ν©λλ€. λΈλμΉ μ΄λ¦μ λͺ©μ μ λͺ
νν λνλ΄λλ‘ μμ±ν©λλ€.
- feature:
feature/κΈ°λ₯-μ΄λ¦(μ:feature/login-api) - bugfix:
bugfix/λ²κ·Έ-μμ -λ΄μ©(μ:bugfix/fix-movie-like-error) - docs:
docs/λ¬Έμ-μ λ°μ΄νΈ-λ΄μ©(μ:docs/update-readme) - refactor:
refactor/리ν©ν λ§-λ΄μ©
-
Q:
bootRunμ€ν μCould not find...μλ¬κ° λ°μν΄μ.- A: Gradle μμ‘΄μ±μ μ λλ‘ λ€μ΄λ‘λνμ§ λͺ»νμ μ μμ΅λλ€. IDEμ Gradle νμμ 'Reload All Gradle Projects'λ₯Ό μ€ννκ±°λ, ν°λ―Έλμμ
./gradlew clean buildλ₯Ό μ€νν΄ λ³΄μΈμ.
- A: Gradle μμ‘΄μ±μ μ λλ‘ λ€μ΄λ‘λνμ§ λͺ»νμ μ μμ΅λλ€. IDEμ Gradle νμμ 'Reload All Gradle Projects'λ₯Ό μ€ννκ±°λ, ν°λ―Έλμμ
-
Q: μΉ΄μΉ΄μ€ λ‘κ·ΈμΈ μ 리λ€μ΄λ νΈ URI μ€λ₯κ° λ°μν΄μ.
- A: μΉ΄μΉ΄μ€ κ°λ°μ μ½μμ λ±λ‘λ
Redirect URIμ.envνμΌμKAKAO_REDIRECT_URLμ΄ μΌμΉνλμ§ νμΈνμΈμ. (κΈ°λ³Έκ°:http://localhost:3000/login/oauth2/code/kakao)
- A: μΉ΄μΉ΄μ€ κ°λ°μ μ½μμ λ±λ‘λ
-
Q: ν¬νΈ μΆ©λ(Port already in use)μ΄ λ°μν΄μ.
- A: λ°±μλ(8080), νλ‘ νΈμλ(3000), MySQL(3306), Redis(6379) ν¬νΈκ° λ€λ₯Έ νλ‘μΈμ€μμ μ¬μ© μ€μΈμ§ νμΈνκ³ , νμ μ ν΄λΉ νλ‘μΈμ€λ₯Ό μ’
λ£νκ±°λ
application.yml,docker-compose.ymlνμΌμμ ν¬νΈλ₯Ό λ³κ²½νμΈμ.
- A: λ°±μλ(8080), νλ‘ νΈμλ(3000), MySQL(3306), Redis(6379) ν¬νΈκ° λ€λ₯Έ νλ‘μΈμ€μμ μ¬μ© μ€μΈμ§ νμΈνκ³ , νμ μ ν΄λΉ νλ‘μΈμ€λ₯Ό μ’
λ£νκ±°λ
-
Q: νλ‘ νΈμλμμ API νΈμΆ μ 403 Forbidden μλ¬κ° λ°μν΄μ.
- A: λ‘κ·ΈμΈ ν λ°μ JWT ν ν°μ΄ HTTP μμ² ν€λμ
AuthorizationμBearer {token}νμμΌλ‘ μ¬λ°λ₯΄κ² ν¬ν¨λμλμ§ νμΈνμΈμ.
- A: λ‘κ·ΈμΈ ν λ°μ JWT ν ν°μ΄ HTTP μμ² ν€λμ