Skip to content

koosco/commerce

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

225 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Commerce - ์ด์ปค๋จธ์Šค ๋ถ„์‚ฐ ์‹œ์Šคํ…œ

Kotlin + Spring Boot ๊ธฐ๋ฐ˜์˜ ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ ํฌํŠธํด๋ฆฌ์˜ค ํ”„๋กœ์ ํŠธ

๐Ÿ“‹ ๋ชฉ์ฐจ

๐ŸŽฏ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์ด ํ”„๋กœ์ ํŠธ๋Š” ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์˜ ํ•ต์‹ฌ ๊ณผ์ œ(ํŠธ๋žœ์žญ์…˜, ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ, ์žฅ์•  ์ฒ˜๋ฆฌ)๋ฅผ ํ•™์Šตํ•˜๊ณ  ์‹คํ—˜ํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“  ํฌํŠธํด๋ฆฌ์˜ค์ž…๋‹ˆ๋‹ค.

์™„๋ฒฝํ•œ ์ œํ’ˆ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ๋ณด๋‹ค, ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์‹ค์ œ ๋ฌธ์ œ๋“ค์„ ์ง์ ‘ ๊ฒฝํ—˜ํ•˜๊ณ  ํ•ด๊ฒฐํ•˜๋Š” ๊ณผ์ •์— ์ง‘์ค‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ

  1. ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜ ๊ตฌํ˜„

    • Kafka๋ฅผ ํ™œ์šฉํ•œ ์„œ๋น„์Šค ๊ฐ„ ๋น„๋™๊ธฐ ํ†ต์‹ 
    • CloudEvents ํ‘œ์ค€ ์ค€์ˆ˜
    • Outbox ํŒจํ„ด + CDC๋ฅผ ํ†ตํ•œ ์ด๋ฒคํŠธ-ํŠธ๋žœ์žญ์…˜ ์›์ž์„ฑ ๋ณด์žฅ
  2. ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ

    • Saga ํŒจํ„ด์„ ํ™œ์šฉํ•œ ์ฃผ๋ฌธ ํ”„๋กœ์„ธ์Šค ๊ตฌํ˜„
    • ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜(Compensating Transaction) ์ฒ˜๋ฆฌ
    • ์ด๋ฒคํŠธ ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ
  3. ์„ฑ๋Šฅ ๋ฐ ํ™•์žฅ์„ฑ

    • Redis๋ฅผ ํ™œ์šฉํ•œ ์žฌ๊ณ  ๊ด€๋ฆฌ ์„ฑ๋Šฅ ์ตœ์ ํ™”
    • Kubernetes ํ™˜๊ฒฝ์—์„œ์˜ ์ˆ˜ํ‰ ํ™•์žฅ
    • k6 ๊ธฐ๋ฐ˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์„ฑ๋Šฅ ์ธก์ •
  4. ๊ด€์ธก์„ฑ(Observability)

    • Prometheus + Grafana๋ฅผ ํ†ตํ•œ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ ๋ฐ ์‹œ๊ฐํ™”
    • ๋ถ„์‚ฐ ์ถ”์ (correlationId, causationId)
    • ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๋ถ„์„

๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ

Backend

  • Language: Kotlin 1.9.25, Java 21
  • Framework: Spring Boot 3.5.8, Spring Cloud 2025.0.0
  • Build: Gradle (Kotlin DSL), Multi-module

Data & Cache

  • Database: MariaDB (JPA, QueryDSL)
  • Cache: Redis (AOF persistence)
  • Migration: JPA DDL Auto (๊ฐœ๋ฐœ), Flyway reference

Messaging

  • Message Broker: Apache Kafka (KRaft mode)
  • CDC: Debezium (Outbox pattern)
  • Event Spec: CloudEvents 1.0

Infrastructure

  • Container: Docker, Docker Compose
  • Orchestration: Kubernetes (k3s/k3d)
  • Monitoring: Prometheus, Grafana, Node Exporter
  • Load Testing: k6 (JavaScript)

Code Quality

  • Formatting: Spotless (ktlint 1.5.0)
  • Architecture: Clean Architecture
  • Testing: JUnit 5, Testcontainers

๐Ÿ— ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

์„œ๋น„์Šค ๊ตฌ์„ฑ๋„

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        API Gateway                          โ”‚
โ”‚                    (Kubernetes Ingress)                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                              โ”‚
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ”‚                     โ”‚                     โ”‚
   โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”          โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”          โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”
   โ”‚  auth   โ”‚          โ”‚  user   โ”‚          โ”‚ catalog โ”‚
   โ”‚ service โ”‚          โ”‚ service โ”‚          โ”‚ service โ”‚
   โ”‚  :8089  โ”‚          โ”‚  :8081  โ”‚          โ”‚  :8084  โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                              โ”‚
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ”‚                     โ”‚                     โ”‚
   โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”          โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”          โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”
   โ”‚  order  โ”‚          โ”‚inventoryโ”‚          โ”‚ payment โ”‚
   โ”‚ service โ”‚          โ”‚ service โ”‚          โ”‚ service โ”‚
   โ”‚  :8085  โ”‚          โ”‚  :8083  โ”‚          โ”‚  :8087  โ”‚
   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜          โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜          โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜
        โ”‚                    โ”‚                     โ”‚
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  Kafka Cluster  โ”‚
                    โ”‚   (KRaft mode)  โ”‚
                    โ”‚      :9092      โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚    Debezium     โ”‚
                    โ”‚  (CDC Outbox)   โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Infrastructure Layer                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  MariaDB   โ”‚   Redis    โ”‚  Prometheus  โ”‚    Grafana        โ”‚
โ”‚   :3306    โ”‚   :6379    โ”‚    :9090     โ”‚     :3000         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Clean Architecture ๊ณ„์ธต ๊ตฌ์กฐ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              api (Presentation)              โ”‚
โ”‚  - Controllers, DTOs, Exception Handlers     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚ depends on
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚          application (Use Cases)             โ”‚
โ”‚  - Commands, Results, Ports (interfaces)     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚ depends on
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚             domain (Entities)                โ”‚
โ”‚  - Entities, Value Objects, Domain Events    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ–ฒ
                   โ”‚ implemented by
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚         infra (Adapters & Ports)             โ”‚
โ”‚  - Repositories, Kafka, Redis, External APIs โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

mono/
โ”œโ”€โ”€ common/                      # ๊ณตํ†ต ๋ชจ๋“ˆ
โ”‚   โ”œโ”€โ”€ common-core/             # ์‘๋‹ต ๋ชจ๋ธ, ์˜ˆ์™ธ, ์ด๋ฒคํŠธ ์Šคํ‚ค๋งˆ
โ”‚   โ”œโ”€โ”€ common-security/         # JWT ์ธ์ฆ, ํ•„ํ„ฐ
โ”‚   โ””โ”€โ”€ common-observability/    # ๋กœ๊น…, MDC
โ”‚
โ”œโ”€โ”€ services/                    # ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค
โ”‚   โ”œโ”€โ”€ auth-service/            # JWT ํ† ํฐ ๋ฐœํ–‰
โ”‚   โ”œโ”€โ”€ user-service/            # ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ
โ”‚   โ”œโ”€โ”€ catalog-service/         # ์ƒํ’ˆ/์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ฆฌ
โ”‚   โ”œโ”€โ”€ inventory-service/       # ์žฌ๊ณ  ๊ด€๋ฆฌ (Redis + MariaDB)
โ”‚   โ”œโ”€โ”€ order-service/           # ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ (Saga ํŒจํ„ด)
โ”‚   โ””โ”€โ”€ payment-service/         # ๊ฒฐ์ œ ์ฒ˜๋ฆฌ (Toss Payments)
โ”‚
โ”œโ”€โ”€ infra/                       # ์ธํ”„๋ผ ์ฝ”๋“œ
โ”‚   โ”œโ”€โ”€ docker-compose.yaml      # ๋กœ์ปฌ ์ธํ”„๋ผ (DB, Redis, Kafka)
โ”‚   โ”œโ”€โ”€ k8s/                     # Kubernetes ๋งค๋‹ˆํŽ˜์ŠคํŠธ
โ”‚   โ”œโ”€โ”€ config/                  # ์„ค์ • ํŒŒ์ผ (Redis, Prometheus, Grafana)
โ”‚   โ””โ”€โ”€ makefiles/               # Makefile ๋ชจ๋“ˆ
โ”‚
โ””โ”€โ”€ load-test/                   # k6 ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ
    โ”œโ”€โ”€ scripts/                 # ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค
    โ””โ”€โ”€ monitoring/              # k6 Grafana ๋Œ€์‹œ๋ณด๋“œ

โœจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ

1. ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜

Outbox ํŒจํ„ด + CDC๋ฅผ ํ†ตํ•œ ์‹ ๋ขฐ์„ฑ ์žˆ๋Š” ์ด๋ฒคํŠธ ๋ฐœํ–‰

@UseCase
class CreateOrderUseCase(
    private val orderRepository: OrderRepository,
    private val outboxRepository: OrderOutboxRepository,
) {
    @Transactional
    fun execute(command: CreateOrderCommand): CreateOrderResult {
        val order = orderRepository.save(order)

        // Outbox ํ…Œ์ด๋ธ”์— ์ €์žฅ (๊ฐ™์€ ํŠธ๋žœ์žญ์…˜)
        outboxRepository.save(OrderOutboxEntry(
            aggregateId = order.id.toString(),
            eventType = "order.placed",
            payload = objectMapper.writeValueAsString(cloudEvent),
            topic = "order.placed",
        ))

        return CreateOrderResult(order.id!!)
    }
    // Debezium CDC๊ฐ€ Outbox ํ…Œ์ด๋ธ”์„ ๊ฐ์‹œํ•˜๊ณ  Kafka๋กœ ๋ฐœํ–‰
}

์ด๋ฒคํŠธ ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ

@Component
class KafkaOrderPlacedConsumer(
    private val useCase: ReserveStockUseCase,
    private val idempotencyChecker: IdempotencyChecker,
) {
    @KafkaListener(topics = ["order.placed"])
    fun onOrderPlaced(event: CloudEvent<OrderPlacedEvent>, ack: Acknowledgment) {
        // 1. ๋ฉฑ๋“ฑ์„ฑ ์ฒดํฌ (Fast-path)
        if (idempotencyChecker.isDuplicate(event.id, "RESERVE_STOCK")) {
            ack.acknowledge()
            return
        }

        // 2. UseCase ์‹คํ–‰
        useCase.execute(command, context)

        // 3. ๋ฉฑ๋“ฑ์„ฑ ๊ธฐ๋ก ์ €์žฅ
        idempotencyRepository.save(
            InventoryEventIdempotency(event.id, "RESERVE_STOCK", referenceId)
        )

        ack.acknowledge()
    }
}

2. Saga ํŒจํ„ด ์ฃผ๋ฌธ ํ”„๋กœ์„ธ์Šค

์ฃผ๋ฌธ ์ƒ์„ฑ โ†’ ์žฌ๊ณ  ์˜ˆ์•ฝ โ†’ ๊ฒฐ์ œ โ†’ ์ฃผ๋ฌธ ํ™•์ • ํ๋ฆ„์„ Saga ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„:

์ฃผ๋ฌธ ์ƒ์„ฑ (ORDER_CREATED)
    โ†“
์žฌ๊ณ  ์˜ˆ์•ฝ (order.placed โ†’ inventory.reserved)
    โ†“
๊ฒฐ์ œ ์ƒ์„ฑ (inventory.confirmed โ†’ payment.created)
    โ†“
๊ฒฐ์ œ ์™„๋ฃŒ (payment.completed โ†’ order.confirmed)

[๋ณด์ƒ ํŠธ๋žœ์žญ์…˜]
๊ฒฐ์ œ ์‹คํŒจ โ†’ ์žฌ๊ณ  ๋ณต๊ตฌ โ†’ ์ฃผ๋ฌธ ์ทจ์†Œ

3. Redis ๊ธฐ๋ฐ˜ ๊ณ ์„ฑ๋Šฅ ์žฌ๊ณ  ๊ด€๋ฆฌ

// Lua ์Šคํฌ๋ฆฝํŠธ๋กœ ์›์ž์  ์žฌ๊ณ  ์ฐจ๊ฐ
val script = """
    local available = redis.call('HGET', KEYS[1], 'availableQuantity')
    if tonumber(available) >= tonumber(ARGV[1]) then
        redis.call('HINCRBY', KEYS[1], 'availableQuantity', -ARGV[1])
        redis.call('HINCRBY', KEYS[1], 'reservedQuantity', ARGV[1])
        return 1
    else
        return 0
    end
""".trimIndent()

redisTemplate.execute(script, keys, args)

4. ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์„ฑ๋Šฅ ์ธก์ •

# k6 ์Šคํฌ๋ฆฝํŠธ๋กœ ์„ฑ๋Šฅ ์ธก์ •
k6 run --out experimental-prometheus-rw scripts/inventory/baseline.test.js

# Grafana์—์„œ ์‹ค์‹œ๊ฐ„ ๋ฉ”ํŠธ๋ฆญ ํ™•์ธ
- Response Time (P50/P95/P99)
- Throughput (RPS)
- Error Rate
- Resource Usage

๐Ÿš€ ์‹œ์ž‘ํ•˜๊ธฐ

์‚ฌ์ „ ์š”๊ตฌ์‚ฌํ•ญ

  • Java 21
  • Docker & Docker Compose
  • Gradle 8.x
  • (์„ ํƒ) k3d (Kubernetes ํ…Œ์ŠคํŠธ์šฉ)

1. ์ธํ”„๋ผ ์‹œ์ž‘

cd infra

# Docker Compose๋กœ DB, Redis, Kafka ์‹œ์ž‘
docker compose --profile full up -d

# ์ƒํƒœ ํ™•์ธ
docker compose ps

2. ์„œ๋น„์Šค ๋นŒ๋“œ ๋ฐ ์‹คํ–‰

# ์ „์ฒด ๋นŒ๋“œ
./gradlew build

# ํŠน์ • ์„œ๋น„์Šค ์‹คํ–‰
./gradlew :services:order-service:bootRun

# ๋˜๋Š” JAR ์‹คํ–‰
java -jar services/order-service/build/libs/order-service-0.0.1-SNAPSHOT.jar

3. ๋™์ž‘ ํ™•์ธ

# ์‚ฌ์šฉ์ž ๋“ฑ๋ก
curl -X POST http://localhost:8081/api/users/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "password123",
    "name": "ํ…Œ์ŠคํŠธ"
  }'

# ์ฃผ๋ฌธ ์ƒ์„ฑ
curl -X POST http://localhost:8085/api/orders \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [{"skuId": 1, "quantity": 2}],
    "shippingAddressId": 1
  }'

4. ๋ชจ๋‹ˆํ„ฐ๋ง

๐Ÿ“ฆ ์„œ๋น„์Šค ์†Œ๊ฐœ

์„œ๋น„์Šค ํฌํŠธ ์„ค๋ช… ์ƒ์„ธ ๋ฌธ์„œ
auth-service 8089 JWT ํ† ํฐ ๋ฐœํ–‰ ๋ฐ ์ธ์ฆ README
user-service 8081 ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ (๋“ฑ๋ก, ํ”„๋กœํ•„, ์ฃผ์†Œ) README
catalog-service 8084 ์ƒํ’ˆ/์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ฆฌ README
inventory-service 8083 ์žฌ๊ณ  ๊ด€๋ฆฌ (Redis + MariaDB) README
order-service 8085 ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ (Saga ํŒจํ„ด) README
payment-service 8087 ๊ฒฐ์ œ ์ฒ˜๋ฆฌ (Toss Payments) README

๐Ÿ“ ์ฃผ์š” ์ž‘์—… ๋‚ด์šฉ

1. Outbox ํŒจํ„ด + CDC ๊ตฌํ˜„ (2025-01)

๋ชฉํ‘œ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ๊ณผ ์ด๋ฒคํŠธ ๋ฐœํ–‰์˜ ์›์ž์„ฑ ๋ณด์žฅ

๊ตฌํ˜„ ๋‚ด์šฉ:

  • ๋ชจ๋“  ์„œ๋น„์Šค์— Outbox ํ…Œ์ด๋ธ” ์ถ”๊ฐ€
  • Debezium CDC Connector ์„ค์ •์œผ๋กœ Outbox โ†’ Kafka ์ž๋™ ๋ฐœํ–‰
  • ๊ธฐ์กด ์ง์ ‘ Kafka ๋ฐœํ–‰ ์ฝ”๋“œ๋ฅผ Outbox ํŒจํ„ด์œผ๋กœ ์ „ํ™˜

๊ฒฐ๊ณผ:

  • DB ํŠธ๋žœ์žญ์…˜๊ณผ ์ด๋ฒคํŠธ ๋ฐœํ–‰์˜ ์›์ž์„ฑ 100% ๋ณด์žฅ
  • ์ด๋ฒคํŠธ ์œ ์‹ค ๋ฐฉ์ง€
  • ์„œ๋น„์Šค๋ณ„ Outbox ํ…Œ์ด๋ธ”: {service}_outbox

2. ์ด๋ฒคํŠธ ๋ฉฑ๋“ฑ์„ฑ ์ฒ˜๋ฆฌ (2025-01)

๋ชฉํ‘œ: ์ค‘๋ณต ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋ฐฉ์ง€

๊ตฌํ˜„ ๋‚ด์šฉ:

  • ์„œ๋น„์Šค๋ณ„ IdempotencyEntry ํ…Œ์ด๋ธ” ์ถ”๊ฐ€
  • Consumer์—์„œ (eventId, action) ์œ ๋‹ˆํฌ ์ œ์•ฝ์œผ๋กœ ์ค‘๋ณต ๊ฒ€์ฆ
  • Fast-path ๋ฉฑ๋“ฑ์„ฑ ์ฒดํฌ (DB ์กฐํšŒ ์ตœ์†Œํ™”)

๊ฒฐ๊ณผ:

  • order-service: 5๊ฐœ Consumer์— ๋ฉฑ๋“ฑ์„ฑ ์ ์šฉ
  • inventory-service: 4๊ฐœ Consumer์— ๋ฉฑ๋“ฑ์„ฑ ์ ์šฉ
  • payment-service: ๊ธฐ์กด ๋ฉฑ๋“ฑ์„ฑ ํ…Œ์ด๋ธ” ์œ ์ง€

3. Kafka Consumer ํŒจํ„ด ํ†ต์ผ (2025-01)

๋ชฉํ‘œ: ์ผ๊ด€๋œ Consumer ๊ตฌํ˜„ ํŒจํ„ด ํ™•๋ฆฝ

๊ตฌํ˜„ ๋‚ด์šฉ:

  • @Validated + @Valid ์กฐํ•ฉ์œผ๋กœ CloudEvent ๊ฒ€์ฆ
  • MessageContext๋กœ correlationId, causationId ์ „๋‹ฌ
  • poison message ์ฒ˜๋ฆฌ ์ „๋žต ํ†ต์ผ
  • ์ˆ˜๋™ ack ๋ชจ๋“œ (MANUAL_IMMEDIATE)

๊ฒฐ๊ณผ:

  • ๋ชจ๋“  Consumer๊ฐ€ ๋™์ผํ•œ ํŒจํ„ด ์ค€์ˆ˜
  • ์ด๋ฒคํŠธ ์ถ”์  ๋ฐ ๋””๋ฒ„๊น… ์šฉ์ด

4. Integration Event ์ง์ ‘ ๋ฐœํ–‰ ํŒจํ„ด (2025-01)

๋ชฉํ‘œ: ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ์ถ”์ถœ ํŒจํ„ด ์ œ๊ฑฐ, ๋‹จ์ˆœํ™”

๊ตฌํ˜„ ๋‚ด์šฉ:

  • pullDomainEvents() ํŒจํ„ด ์ œ๊ฑฐ
  • UseCase์—์„œ Integration Event ์ง์ ‘ ์ƒ์„ฑ ๋ฐ ๋ฐœํ–‰
  • Port/Adapter ๊ตฌ์กฐ ์œ ์ง€ (IntegrationEventPublisher)

๊ฒฐ๊ณผ:

  • ์ฝ”๋“œ ๋ณต์žก๋„ ๊ฐ์†Œ
  • ์ด๋ฒคํŠธ ๋ฐœํ–‰ ๋กœ์ง ๋ช…ํ™•ํ™”
  • ๋ถˆํ•„์š”ํ•œ ์ถ”์ƒํ™” ์ œ๊ฑฐ

5. Gradle Multi-module ์ „ํ™˜ (2024-12)

๋ชฉํ‘œ: ์—ฌ๋Ÿฌ ์ €์žฅ์†Œ๋ฅผ ํ•˜๋‚˜์˜ ๋ชจ๋…ธ๋ ˆํฌ๋กœ ํ†ตํ•ฉ

๊ตฌํ˜„ ๋‚ด์šฉ:

  • common ๋ชจ๋“ˆ์„ GitHub Packages์—์„œ project dependency๋กœ ๋ณ€๊ฒฝ
  • ์„œ๋น„์Šค๋ณ„ ๋…๋ฆฝ ๋นŒ๋“œ ๋ฐ ๊ณตํ†ต ๋นŒ๋“œ ์Šคํฌ๋ฆฝํŠธ
  • Spotless ์ฝ”๋“œ ํฌ๋งทํŒ… ํ†ตํ•ฉ

๊ฒฐ๊ณผ:

  • ๋นŒ๋“œ ์‹œ๊ฐ„ ๋‹จ์ถ• (์บ์‹œ ํ™œ์šฉ)
  • ์˜์กด์„ฑ ๊ด€๋ฆฌ ๋‹จ์ˆœํ™”
  • GH_USER/GH_TOKEN ๋ถˆํ•„์š”

6. Kafka Integration Test ์ธํ”„๋ผ (2024-12)

๋ชฉํ‘œ: Kafka ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž๋™ํ™”

๊ตฌํ˜„ ๋‚ด์šฉ:

  • Testcontainers๋กœ Kafka ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰
  • Producer/Consumer ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
  • ์ด๋ฒคํŠธ ๋ฐœํ–‰/์†Œ๋น„ E2E ๊ฒ€์ฆ

๊ฒฐ๊ณผ:

  • CI/CD ํŒŒ์ดํ”„๋ผ์ธ์—์„œ Kafka ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • ์ด๋ฒคํŠธ ๊ณ„์•ฝ ๋ณ€๊ฒฝ ๊ฐ์ง€

7. k6 ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๊ตฌ์ถ• (2024-11)

๋ชฉํ‘œ: ์„ฑ๋Šฅ ๊ธฐ์ค€์„  ์ˆ˜๋ฆฝ ๋ฐ ๋ณ‘๋ชฉ ์ง€์  ํŒŒ์•…

๊ตฌํ˜„ ๋‚ด์šฉ:

  • Smoke โ†’ Baseline โ†’ Stress 3๋‹จ๊ณ„ ํ…Œ์ŠคํŠธ
  • Prometheus Remote Write๋กœ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘
  • Grafana ๋Œ€์‹œ๋ณด๋“œ๋กœ ์‹ค์‹œ๊ฐ„ ์‹œ๊ฐํ™”

๊ฒฐ๊ณผ:

  • ์žฌ๊ณ  ์ฐจ๊ฐ API: P95 ์‘๋‹ต์‹œ๊ฐ„ 50ms ์ดํ•˜
  • Redis ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ๊ฒ€์ฆ
  • ์‹œ์Šคํ…œ ํ•œ๊ณ„์  ํŒŒ์•… (RPS 1000+)

๐Ÿ“š ์ฐธ๊ณ  ๋ฌธ์„œ

๐Ÿ“„ ๋ผ์ด์„ ์Šค

์ด ํ”„๋กœ์ ํŠธ๋Š” ํฌํŠธํด๋ฆฌ์˜ค ๋ชฉ์ ์œผ๋กœ ์ œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


Contact: GitHub

About

individual commerce project learning for distributed system

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors