Skip to content

uz2ni/ecommerce-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

134 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

E-Commerce API

Spring Boot ๊ธฐ๋ฐ˜์˜ ์ด์ปค๋จธ์Šค API ์ž…๋‹ˆ๋‹ค. ํšŒ์› ๊ด€๋ฆฌ, ์ƒํ’ˆ ์กฐํšŒ, ์žฅ๋ฐ”๊ตฌ๋‹ˆ, ์ฃผ๋ฌธ/๊ฒฐ์ œ, ์ฟ ํฐ ๋ฐœ๊ธ‰ ๋“ฑ ์ด์ปค๋จธ์Šค์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๋ชฉ์ฐจ

์•„ํ‚คํ…์ฒ˜ ๋ฐ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜ (Layered Architecture)

์ด ํ”„๋กœ์ ํŠธ๋Š” ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ฑ„ํƒํ•˜์—ฌ ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ์™€ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

โ”œโ”€โ”€ presentation    (ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๋ ˆ์ด์–ด)
โ”‚   โ””โ”€โ”€ controller 
โ”‚   โ””โ”€โ”€ dto         // request, response DTO
โ”œโ”€โ”€ application      (์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ์ด์–ด)
โ”‚   โ””โ”€โ”€ service     // ์—ฌ๋Ÿฌ entity, repository ์กฐํ•ฉํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
โ”‚   โ””โ”€โ”€ dto         // application layer DTO
โ”‚   โ””โ”€โ”€ enums       // service ๋‚ด ์‚ฌ์šฉ enum
โ”‚   โ””โ”€โ”€ validator   // ํ•ด๋‹น ๋„๋ฉ”์ธ์˜ ๊ฒ€์ฆ ๋กœ์ง (๋‹ค๋ฅธ ๋„๋ฉ”์ธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ถ„๋ฆฌ)
โ”œโ”€โ”€ domain          (๋„๋ฉ”์ธ ๋ ˆ์ด์–ด)
โ”‚   โ””โ”€โ”€ entity      // ๋„๋ฉ”์ธ ๊ฐ์ฒด, ๋„๋ฉ”์ธ์— ํ•ด๋‹นํ•˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
โ””โ”€โ”€ infrastructure  (์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜ ๋ ˆ์ด์–ด)
    โ””โ”€โ”€ InMemoryXXRepository // ์ธ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ 

๊ฐ ๋ ˆ์ด์–ด์˜ ์—ญํ• 

  • Presentation Layer: HTTP ์š”์ฒญ/์‘๋‹ต ์ฒ˜๋ฆฌ, DTO ๋ณ€ํ™˜
  • Application Layer: ๋น„์ฆˆ๋‹ˆ์Šค ์œ ์Šค์ผ€์ด์Šค ์กฐ์œจ, ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
  • Domain Layer: ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ฐ ๋„๋ฉ”์ธ ๋ชจ๋ธ
  • Infrastructure Layer: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ, ์™ธ๋ถ€ ์‹œ์Šคํ…œ ์—ฐ๋™

์„ ์ • ์ด์œ 

  1. ๋ช…ํ™•ํ•œ ์ฑ…์ž„ ๋ถ„๋ฆฌ: ๊ฐ ๋ ˆ์ด์–ด๊ฐ€ ๋ช…ํ™•ํ•œ ์—ญํ• ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ์ฝ”๋“œ์˜ ์‘์ง‘๋„๊ฐ€ ๋†’๊ณ  ๊ฒฐํ•ฉ๋„๊ฐ€ ๋‚ฎ์Œ
  2. ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ: ๋ ˆ์ด์–ด๋ณ„๋กœ ๋…๋ฆฝ์ ์ธ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋ชจํ‚น์ด ์šฉ์ดํ•จ
  3. ์œ ์ง€๋ณด์ˆ˜์„ฑ: ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ํŠน์ • ๋ ˆ์ด์–ด์— ๊ตญํ•œ๋˜์–ด ์˜ํ–ฅ ๋ฒ”์œ„๊ฐ€ ์ œํ•œ์ ์ž„
  4. ํ™•์žฅ์„ฑ: ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์‹œ ๊ธฐ์กด ๋ ˆ์ด์–ด ๊ตฌ์กฐ๋ฅผ ๋”ฐ๋ผ ์ผ๊ด€๋˜๊ฒŒ ๊ตฌํ˜„ ๊ฐ€๋Šฅ
  5. ํŒ€ ํ˜‘์—…: ๋ ˆ์ด์–ด๋ณ„๋กœ ์ž‘์—…์„ ๋ถ„๋‹ดํ•˜๊ธฐ ์šฉ์ดํ•˜์—ฌ ๋ณ‘๋ ฌ ๊ฐœ๋ฐœ์— ์œ ๋ฆฌํ•จ

๋™์‹œ์„ฑ ์ œ์–ด

์ด์ปค๋จธ์Šค ์‹œ์Šคํ…œ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ปค์Šคํ…€ ๋ฝ(Lock) ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ตฌํ˜„ ๋ฐฉ์‹

@WithLock ์–ด๋…ธํ…Œ์ด์…˜ + AOP (Aspect)

@WithLock(key = "'issueCoupon:' + #command.couponId")
public IssueCouponResult issueCoupon(IssueCouponCommand command) {
    // ์ฟ ํฐ ๋ฐœ๊ธ‰ ๋กœ์ง
}

ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ

  1. @WithLock ์–ด๋…ธํ…Œ์ด์…˜

    • SpEL ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฝ ํ‚ค๋ฅผ ๋™์ ์œผ๋กœ ์ง€์ •
    • timeout: ๋ฝ ํš๋“ ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์„ค์ • (๊ธฐ๋ณธ 3์ดˆ)
    • ignoreIfLocked: ๋ฝ ํš๋“ ์‹คํŒจ ์‹œ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ ์—ฌ๋ถ€ (๊ธฐ๋ณธ false)
  2. LockAspect

    • ConcurrentHashMap<Object, ReentrantLock> ๊ธฐ๋ฐ˜ ๋ฝ ๊ด€๋ฆฌ
    • ๊ณต์ •ํ•œ ๋ฝ ํš๋“์„ ์œ„ํ•ด ReentrantLock(true) ์‚ฌ์šฉ
    • ๋ฝ ํ‚ค๋ณ„๋กœ ๋…๋ฆฝ์ ์ธ ๋ฝ ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ

๊ธฐ์ˆ ์  ํŠน์ง•

  • ์„ธ๋ฐ€ํ•œ ๋ฝ ์ œ์–ด: ์ „์—ญ ๋ฝ์ด ์•„๋‹Œ ํ‚ค ๊ธฐ๋ฐ˜ ๋ฝ์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”
  • ํƒ€์ž„์•„์›ƒ ์„ค์ •: ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€ ๋ฐ ์‘๋‹ต ์‹œ๊ฐ„ ๋ณด์žฅ
  • SpEL ์ง€์›: ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ™œ์šฉํ•œ ๋™์  ๋ฝ ํ‚ค ์ƒ์„ฑ

์ ์šฉ ์‚ฌ๋ก€

1. ์ฟ ํฐ ๋ฐœ๊ธ‰ (CouponService)

๋ฌธ์ œ ์ƒํ™ฉ

  • ๋™์ผ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ™์€ ์ฟ ํฐ์„ ๋™์‹œ์— ์—ฌ๋Ÿฌ ๋ฒˆ ์š”์ฒญ
  • ์ œํ•œ๋œ ์ˆ˜๋Ÿ‰์˜ ์ฟ ํฐ์„ ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ์š”์ฒญ

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

@WithLock(key = "'issueCoupon:' + #command.couponId")
public IssueCouponResult issueCoupon(IssueCouponCommand command)
  • ์ฟ ํฐ ID๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฝ ํš๋“
  • ๋™์ผ ์ฟ ํฐ์— ๋Œ€ํ•œ ๋™์‹œ ๋ฐœ๊ธ‰ ์š”์ฒญ์„ ์ˆœ์ฐจ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ค‘๋ณต ๋ฐœ๊ธ‰ ๋ฐฉ์ง€

๊ฒ€์ฆ

  • 20๊ฐœ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๋™์ผ ์‚ฌ์šฉ์ž๋กœ ๋ฐœ๊ธ‰ ์‹œ๋„ โ†’ 1๊ฐœ๋งŒ ๋ฐœ๊ธ‰ ์„ฑ๊ณต
  • 20๊ฐœ ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์ง€๋ง‰ 1๊ฐœ ์ฟ ํฐ ๋ฐœ๊ธ‰ ์‹œ๋„ โ†’ 1๋ช…๋งŒ ์„ฑ๊ณต

2. ์ฃผ๋ฌธ ๊ฒฐ์ œ (OrderService)

๋ฌธ์ œ ์ƒํ™ฉ

  • ๋™์ผ ์ฃผ๋ฌธ์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ๋ฒˆ ๊ฒฐ์ œ ์‹œ๋„ (์ค‘๋ณต ๊ฒฐ์ œ)
  • ์žฌ๊ณ  ์ฐจ๊ฐ, ํฌ์ธํŠธ ์ฐจ๊ฐ ์‹œ ๊ฒฝ์Ÿ ์กฐ๊ฑด ๋ฐœ์ƒ

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

@WithLock(key = "'processPayment:' + #userId")
public PaymentResult processPayment(Integer orderId, Integer userId)
  • ์‚ฌ์šฉ์ž ID๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฝ ํš๋“
  • ๋™์ผ ์‚ฌ์šฉ์ž์˜ ๋™์‹œ ๊ฒฐ์ œ๋ฅผ ์ˆœ์ฐจ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ค‘๋ณต ๊ฒฐ์ œ ๋ฐฉ์ง€
  • ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜์„ ํ†ตํ•ด ๊ฒฐ์ œ ์‹คํŒจ ์‹œ ์žฌ๊ณ /ํฌ์ธํŠธ ์›์ƒ๋ณต๊ตฌ

๊ฒ€์ฆ

  • 10๊ฐœ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์ผ ์ฃผ๋ฌธ ๊ฒฐ์ œ ์‹œ๋„ โ†’ 1๋ฒˆ๋งŒ ์„ฑ๊ณต, ํฌ์ธํŠธ 1ํšŒ๋งŒ ์ฐจ๊ฐ
  • ์„œ๋กœ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์„œ๋กœ ๋‹ค๋ฅธ ์ฃผ๋ฌธ โ†’ ๋ชจ๋‘ ์ •์ƒ ์ฒ˜๋ฆฌ (๋ฝ ๊ฒฝํ•ฉ ์—†์Œ)

3. ํฌ์ธํŠธ ์ถฉ์ „ (PointService)

๋ฌธ์ œ ์ƒํ™ฉ

  • ๋™์ผ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ์—ฌ๋Ÿฌ ๋ฒˆ ํฌ์ธํŠธ ์ถฉ์ „

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

@WithLock(key = "'chargePoint:' + #userId", ignoreIfLocked = true)
public PointResult chargePoint(Integer userId, Integer amount)
  • ์‚ฌ์šฉ์ž ID๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฝ ํš๋“
  • ignoreIfLocked = true: ๋ฝ ํš๋“ ์‹คํŒจ ์‹œ ์ฆ‰์‹œ null ๋ฐ˜ํ™˜ (ํƒ€์ž„์•„์›ƒ ์—†์Œ)

๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ

๋ชจ๋“  ๋™์‹œ์„ฑ ์ œ์–ด ๋กœ์ง์€ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•

  • ExecutorService + CountDownLatch๋ฅผ ์‚ฌ์šฉํ•œ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  • ์ผ๋ฐ˜์ ์œผ๋กœ 10~20๊ฐœ ์Šค๋ ˆ๋“œ๋กœ ๋™์‹œ ์š”์ฒญ ํ…Œ์ŠคํŠธ
  • AtomicInteger๋กœ ์„ฑ๊ณต/์‹คํŒจ ์นด์šดํŠธ ์ถ”์ 

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค

  • CouponServiceConcurrencyIntegrationTest: ์ฟ ํฐ ์ค‘๋ณต ๋ฐœ๊ธ‰ ๋ฐฉ์ง€, ์„ ์ฐฉ์ˆœ ์ฒ˜๋ฆฌ
  • OrderServiceConcurrencyIntegrationTest: ์ค‘๋ณต ๊ฒฐ์ œ ๋ฐฉ์ง€, ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜
  • PointServiceConcurrencyIntegrationTest: ํฌ์ธํŠธ ์ถฉ์ „ ๋™์‹œ์„ฑ ์ œ์–ด

์„ ํƒ ์ด์œ 

๋‹ค๋ฅธ ๋ฐฉ์‹ ๋Œ€๋น„ ์žฅ์ 

  1. synchronized ๋Œ€๋น„

    • ์„ธ๋ฐ€ํ•œ ๋ฝ ์ œ์–ด ๊ฐ€๋Šฅ (ํ‚ค ๊ธฐ๋ฐ˜)
    • ํƒ€์ž„์•„์›ƒ ์„ค์ • ๊ฐ€๋Šฅ
    • ๊ณต์ •์„ฑ ๋ณด์žฅ (fair lock)
  2. @Transactional + DB ๋ฝ ๋Œ€๋น„

    • ์ธ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ ์‚ฌ์šฉ ์‹œ์—๋„ ๋™์ž‘
    • DB ๋ถ€ํ•˜ ๊ฐ์†Œ
    • ์‘๋‹ต ์†๋„ ํ–ฅ์ƒ
  3. ๋ถ„์‚ฐ ๋ฝ(Redis) ๋Œ€๋น„

    • ๋‹จ์ผ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์— ์ ํ•ฉ
    • ์™ธ๋ถ€ ์˜์กด์„ฑ ์—†์Œ
    • ๊ตฌํ˜„ ๋ฐ ํ…Œ์ŠคํŠธ ์šฉ์ด

ํ•œ๊ณ„์ 

  • ๋‹จ์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค์—์„œ๋งŒ ๋™์ž‘
  • ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์—์„œ๋Š” ๋ถ„์‚ฐ ๋ฝ(Redis, Zookeeper ๋“ฑ) ํ•„์š”

๊ธฐ์ˆ  ์Šคํƒ

Backend

  • Java 17
  • Spring Boot 3.3.1
  • Gradle

Documentation

  • SpringDoc OpenAPI 3 (Swagger UI)

์„ค์น˜ ๋ฐ ์‹คํ–‰

1. ํ”„๋กœ์ ํŠธ ํด๋ก 

git clone <repository-url>
cd ecommerce-api

2. ๋นŒ๋“œ

# Gradle Wrapper ์‚ฌ์šฉ (๊ถŒ์žฅ)
./gradlew build

# ๋˜๋Š” ์‹œ์Šคํ…œ Gradle ์‚ฌ์šฉ
gradle build

3. ์‹คํ–‰

# Gradle Wrapper ์‚ฌ์šฉ
./gradlew bootRun

# ๋˜๋Š” ๋นŒ๋“œ๋œ JAR ํŒŒ์ผ ์ง์ ‘ ์‹คํ–‰
java -jar build/libs/ecommerce-api-0.0.1-SNAPSHOT.jar

4. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ™•์ธ

์„œ๋ฒ„๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์‹œ์ž‘๋˜๋ฉด ๋‹ค์Œ ์ฃผ์†Œ๋กœ ์ ‘์†ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

5. ํ…Œ์ŠคํŠธ ์‹คํ–‰

# ์ „์ฒด ํ…Œ์ŠคํŠธ ์‹คํ–‰
./gradlew test

# ํŠน์ • ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋งŒ ์‹คํ–‰
./gradlew test --tests com.example.ecommerceapi.YourTestClass

# ํŠน์ • ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋งŒ ์‹คํ–‰
./gradlew test --tests com.example.ecommerceapi.YourTestClass.testMethod

API ๋ฌธ์„œ

Swagger UI

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ํ›„ Swagger UI๋ฅผ ํ†ตํ•ด ๋ชจ๋“  API๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

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

API ์‚ฌ์šฉ ์˜ˆ์‹œ

1. ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ƒํ’ˆ ์ถ”๊ฐ€

curl -X POST http://localhost:8080/api/cart \
  -H "Content-Type: application/json" \
  -d '{
    "userId": 1,
    "productId": 1,
    "quantity": 2
  }'

์„ค๊ณ„ & ๋ณด๊ณ ์„œ ๋ฌธ์„œ

์ƒ์„ธํ•œ ์„ค๊ณ„ & ๋ณด๊ณ ์„œ ๋ฌธ์„œ๋Š” docs/ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

๋ผ์ด์„ผ์Šค

This project is licensed under the MIT License.

About

ecommerce API ๐Ÿ›๏ธ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors