Skip to content

Latest commit

ย 

History

History
152 lines (115 loc) ยท 5.7 KB

File metadata and controls

152 lines (115 loc) ยท 5.7 KB

CoinFlow

CoinFlow๋Š” ๋‹จ์ผ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์—์„œ ์ง€์ •๊ฐ€ ์ฃผ๋ฌธ ์ƒ์„ฑ, ๊ฐ€๊ฒฉ-์‹œ๊ฐ„ ์šฐ์„  ๋งค์นญ, ์ฒด๊ฒฐ, ์ง€๊ฐ‘ ์ •์‚ฐ, ์›์žฅ ๊ธฐ๋ก๊นŒ์ง€ ๊ฒ€์ฆํ•˜๋Š” ์•”ํ˜ธํ™”ํ ๊ฑฐ๋ž˜์†Œ ์ฝ”์–ด ๋ฐฑ์—”๋“œ MVP์ž…๋‹ˆ๋‹ค.

์ด ํ”„๋กœ์ ํŠธ๋Š” ์‹ค์‹œ๊ฐ„ ์‹œ์„ธ๋‚˜ ๋ถ„์‚ฐ ์ธํ”„๋ผ๋ณด๋‹ค ์ฃผ๋ฌธ๊ณผ ์ž์‚ฐ ์ •ํ•ฉ์„ฑ์„ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋ฌธ์ด ์ฒด๊ฒฐ๋  ๋•Œ orders, trades, wallets, wallet_ledgers, domain_events๊ฐ€ ์ผ๊ด€๋˜๊ฒŒ ๊ธฐ๋ก๋˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„ ๋ฒ”์œ„

  • ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, JWT access token ์ธ์ฆ
  • ์‚ฌ์šฉ์ž๋ณ„ ์ง€๊ฐ‘ ์ž๋™ ์ƒ์„ฑ ๋ฐ ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ
  • ์ง€์ •๊ฐ€ BUY / SELL ์ฃผ๋ฌธ ์ƒ์„ฑ
  • ์ฃผ๋ฌธ ์ทจ์†Œ
  • ๊ฐ€๊ฒฉ ์šฐ์„ , ์‹œ๊ฐ„ ์šฐ์„  ๋งค์นญ
  • ๋ถ€๋ถ„ ์ฒด๊ฒฐ, ์™„์ „ ์ฒด๊ฒฐ
  • BUY ์ฃผ๋ฌธ quote asset ์ž ๊ธˆ, SELL ์ฃผ๋ฌธ base asset ์ž ๊ธˆ
  • ์ฒด๊ฒฐ ์‹œ buyer/seller ์ง€๊ฐ‘ ์ •์‚ฐ
  • append-only ์ง€๊ฐ‘ ์›์žฅ ๊ธฐ๋ก
  • ์‹œ์žฅ, ์˜ค๋”๋ถ, ์ตœ๊ทผ ์ฒด๊ฒฐ, ์‚ฌ์šฉ์ž fill, ์ง€๊ฐ‘, ์›์žฅ ์กฐํšŒ
  • ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ DB์˜ ๋ฏธ์ฒด๊ฒฐ ์ฃผ๋ฌธ์œผ๋กœ ์ธ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋”๋ถ ์ดˆ๊ธฐํ™”
  • ์ฃผ๋ฌธ/์ฒด๊ฒฐ/์ •์‚ฐ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋กœ๊ทธ ์ €์žฅ

์ œ์™ธ ๋ฒ”์œ„

  • ์ž…๊ธˆ/์ถœ๊ธˆ
  • ์‹œ์žฅ๊ฐ€ ์ฃผ๋ฌธ
  • IOC/FOK/GTT, post-only, iceberg ์ฃผ๋ฌธ
  • ์ˆ˜์ˆ˜๋ฃŒ
  • refresh token, OAuth/social login, role/permission
  • Kafka ๊ธฐ๋ฐ˜ ์ด๋ฒคํŠธ ๋ฐœํ–‰
  • WebSocket ์‹ค์‹œ๊ฐ„ ์ฒด๊ฒฐ/ํ˜ธ๊ฐ€ push
  • Redis, MQ, ์„œ๋ฒ„ ๋ถ„๋ฆฌ
  • replay, redrive, reconciliation
  • ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€

์ผ๋ถ€ ๋กœ์ปฌ ๊ฐœ๋ฐœ ํŽธ์˜๋ฅผ ์œ„ํ•œ API์™€ ์ธํ”„๋ผ ๊ธฐ๋ฐ˜์€ ์กด์žฌํ•˜์ง€๋งŒ, ์šด์˜ ๊ธฐ๋Šฅ ๋ฒ”์œ„์™€ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด dev/test ์ž…๊ธˆ ๋ณด์กฐ API๋Š” prod ํ”„๋กœํ•„์—์„œ ์ œ์™ธ๋˜๋ฉฐ, Kafka ์ปจํ…Œ์ด๋„ˆ๋Š” ๋กœ์ปฌ ์ธํ”„๋ผ ๊ธฐ๋ฐ˜์ผ ๋ฟ ํ˜„์žฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—๋Š” spring-kafka producer/consumer๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Phase 1 ์™„๋ฃŒ ์ดํ›„ ๋ฆฌ๋ทฐ ๊ณผ์ •์—์„œ zero-quote ์ฒด๊ฒฐ ๋ฐฉ์ง€, dust maker ์ž๋™ ์ทจ์†Œ, ์˜ค๋”๋ถ ์žฌ๋นŒ๋“œ ๊ฐ™์€ ์ •ํ•ฉ์„ฑ ๋ณด๊ฐ•์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์„ค๊ณ„

์ฃผ์ œ ์„ค๊ณ„
Source of truth DB์˜ ์ฃผ๋ฌธ, ์ฒด๊ฒฐ, ์ง€๊ฐ‘, ์›์žฅ์„ ๊ธฐ์ค€ ์ƒํƒœ๋กœ ๋‘ก๋‹ˆ๋‹ค.
์ธ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋”๋ถ ๋งค์นญ ํ›„๋ณด ์กฐํšŒ์™€ ํ˜ธ๊ฐ€ ์กฐํšŒ๋ฅผ ์œ„ํ•œ ํŒŒ์ƒ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.
์˜ค๋”๋ถ ๋ฐ˜์˜ DB commit ์ดํ›„์—๋งŒ ์ธ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋”๋ถ์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
์ˆœ์ฐจ ์ฒ˜๋ฆฌ ๊ฐ™์€ ์‹œ์žฅ์˜ ์ฃผ๋ฌธ ์ƒ์„ฑ/์ทจ์†Œ๋Š” market๋ณ„ ReentrantLock์œผ๋กœ ์ง๋ ฌํ™”ํ•ฉ๋‹ˆ๋‹ค.
DB ๋™์‹œ์„ฑ sequence, wallet, maker order ๊ฐฑ์‹ ์— pessimistic lock์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
์ง€๊ฐ‘ ๋ชจ๋ธ available_balance์™€ locked_balance๋ฅผ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
์›์žฅ ๋ชจ๋“  ์ง€๊ฐ‘ ๋ณ€๊ฒฝ์„ wallet_ledgers์— append-only๋กœ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฒคํŠธ domain_events๋ฅผ ๋‚ด๋ถ€ ์ด๋ฒคํŠธ ๋กœ๊ทธ๋กœ ์ €์žฅํ•˜๊ณ , ์ดํ›„ Outbox ํ™•์žฅ ๊ฒฝ๊ณ„๋ฅผ ๋‚จ๊น๋‹ˆ๋‹ค.

๊ธฐ์ˆ  ์Šคํƒ

  • Java 21
  • Spring Boot 3.5
  • Spring Web MVC
  • Spring Security + OAuth2 Resource Server + JWT
  • Spring Data JPA
  • MySQL 8
  • Flyway
  • JUnit 5, AssertJ
  • Testcontainers MySQL
  • Actuator, Micrometer, Prometheus registry
  • Docker Compose

์‹คํ–‰ ๋ฐฉ๋ฒ•

1. ๋กœ์ปฌ ์ธํ”„๋ผ ์‹คํ–‰

docker compose up -d mysql

docker-compose.yml์—๋Š” Kafka ์ปจํ…Œ์ด๋„ˆ๋„ ํฌํ•จ๋˜์–ด ์žˆ์ง€๋งŒ, ํ˜„์žฌ MVP ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰์—๋Š” MySQL๋งŒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰

./gradlew bootRun

๊ธฐ๋ณธ DB ์ ‘์† ์ •๋ณด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

spring.datasource.url=jdbc:mysql://localhost:3306/coinflow?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
spring.datasource.username=coinflow
spring.datasource.password=coinflow

๋‹ค๋ฅธ ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋ฉด ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.

DB_URL='jdbc:mysql://localhost:3307/coinflow?serverTimezone=Asia/Seoul&characterEncoding=UTF-8' ./gradlew bootRun

3. API ๋ฌธ์„œ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ํ›„ Swagger UI์—์„œ API๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

http://localhost:8080/swagger-ui/index.html

ํ…Œ์ŠคํŠธ

์ „์ฒด ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์Œ ๋ช…๋ น์œผ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

./gradlew test

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋Š” Testcontainers ๊ธฐ๋ฐ˜ MySQL์„ ์‚ฌ์šฉํ•ด decimal, foreign key, transaction ๊ฒฝ๊ณ„์™€ ํ•ต์‹ฌ ์ •ํ•ฉ์„ฑ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์‹ค์ œ MySQL์— ๊ฐ€๊น๊ฒŒ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ์™€ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ๊ฒ€์ฆ ๋ฒ”์œ„:

  • ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, JWT ์ธ์ฆ
  • BUY/SELL ์ฃผ๋ฌธ ์ž์‚ฐ ์ž ๊ธˆ
  • ๊ฐ€๊ฒฉ ์šฐ์„  ๋งค์นญ
  • ๋ถ€๋ถ„ ์ฒด๊ฒฐ, ์™„์ „ ์ฒด๊ฒฐ
  • BUY taker ๊ฐ€๊ฒฉ ์ฐจ์ด ํ™˜๋ถˆ
  • SELL taker ์ •์‚ฐ
  • ๋ถ€๋ถ„ ์ฒด๊ฒฐ ํ›„ ์ทจ์†Œ
  • ์ž๊ธฐ ์ฒด๊ฒฐ ๊ฑฐ์ ˆ
  • ์›์žฅ ๊ธฐ๋ก
  • ์˜ค๋”๋ถ ์กฐํšŒ
  • ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ์ €์žฅ
  • ์ง€๊ฐ‘ ์ž”๊ณ  ์Œ์ˆ˜ ๋ฐฉ์ง€

๋ฌธ์„œ

๋ฌธ์„œ ์„ค๋ช…
PRD MVP ์ œํ’ˆ ๋ฒ”์œ„, ํฌํ•จ/์ œ์™ธ ๊ธฐ์ค€, ์„ฑ๊ณต ๊ธฐ์ค€
Plan MVP ๊ตฌํ˜„ ์ˆœ์„œ์™€ ์„ค๊ณ„ ์›์น™
API REST API ๊ณ„์•ฝ๊ณผ ์—๋Ÿฌ ์ฝ”๋“œ
ERD ํ…Œ์ด๋ธ” ๊ตฌ์กฐ์™€ ๊ด€๊ณ„
Test Plan ํ•ต์‹ฌ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค
Order Flow ์ฃผ๋ฌธ ์ƒ์„ฑ๋ถ€ํ„ฐ ์ฒด๊ฒฐ/์ •์‚ฐ/์˜ค๋”๋ถ ๋ฐ˜์˜๊นŒ์ง€์˜ ๋‚ด๋ถ€ ํ๋ฆ„
Issues Phase 1 ์ดํ›„ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ด์Šˆ์™€ ๋ณด๊ฐ• ๋‚ด์šฉ
Reference ์„ค๊ณ„ ํŒ๋‹จ ๊ทผ๊ฑฐ์™€ ์™ธ๋ถ€ ๊ฑฐ๋ž˜์†Œ API ๋ ˆํผ๋Ÿฐ์Šค

๋‹ค์Œ ๋‹จ๊ณ„

ํ˜„์žฌ ๊ตฌํ˜„ ์™„๋ฃŒ ๋ฒ”์œ„๋Š” Phase 1 MVP์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋Š” ์ด๋ฒคํŠธ ๋ฐœํ–‰๊ณผ ์‹ค์‹œ๊ฐ„ ์ „ํŒŒ๋ฅผ ๋ณ„๋„ ์ด์Šˆ๋กœ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.

  • OutboxPublisher ๊ตฌํ˜„
  • domain_events.published=false ์ด๋ฒคํŠธ Kafka ๋ฐœํ–‰
  • Kafka ๋ฐœํ–‰ ์„ฑ๊ณต/์‹คํŒจ ์ƒํƒœ์™€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ๊ด€๋ฆฌ
  • Kafka Consumer ๊ธฐ๋ฐ˜ WebSocket ์ฒด๊ฒฐ/์˜ค๋”๋ถ broadcast
  • ์ •์‚ฐ Batch์™€ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€

Kafka, WebSocket, Batch ์ •์‚ฐ์€ ์•„์ง ๊ตฌํ˜„ ์™„๋ฃŒ ๊ธฐ๋Šฅ์œผ๋กœ ํ‘œ๊ธฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.