JAVA/프로젝트

쿠폰 선착순 발급 이벤트 - 기획, ERD,프로젝트 세팅

whyHbr 2024. 11. 27. 23:34
728x90
반응형

ERD

 


개요

  • 한정 수량의 쿠폰을 먼저 신청한 사용자에게 제공하는 이벤트
  • Mysql 기반 쿠폰 발급 기능을 개발한다.
  • Redis 기반 구판 발급 기능을 개발한다.
  • AWS에 서버를 배포하고 Locust 를 통해 부하테스트를 진행 한다.

 

요구사항

  • 이벤트 기간내에 발급
  • 유저당 1번의 쿠폰 발급
  • 선착순 쿠폰의 최대 쿠폰 발급 수량 설정

 

기능

  • 쿠폰 발급 기능
    • 쿠폰 발급 기간 검증
    • 쿠폰 발급 수량 검증
      • 쿠폰 전체 발급 수량
      • 중복 발급 요청 검증
    • 쿠폰 발급
      • 쿠폰 발급 수량 증가
      • 쿠폰 발급 기록 저장
        • 쿠폰 ID
        • 유저 ID

목표

  • 정확한 발급 수량 제어 (동시성 이슈 처리)
  • 높은 처리량
  • 멀티 모듈 사용
  • gradle - kotlin 경험. 

깃 컨벤션

 

  • build: 빌드, 의존성 추가
  • feat: 새로운 기능 추가
  • fix: 에러 수정
  • docs: 문서 수정
  • style: 코드 포맷팅, 세미콜론 누락 등 코드에 변경이 없는 경우
  • refactor: 코드 리팩토링
  • test: 테스트 코드 작성, 리팩토링
  • rename: 파 이름 변경
  • remove: 파일 삭제
  • conf: 설정 파일

기술 스택

  • Java 17
  • Spring Boot3
  • IntelliJ
  • MySql
  • h2
  • JPA
  • QueryDel
  • Redis
  • Spring Actuator
  • Grafana
  • AWS
  • Locust

 

멀티 모듈 설정

프로젝트 생성(프로젝트 이름 coupon) - gradle Kotlin 선택 후 설정한다.

프로젝트의 src 폴더를 삭제하고, root 폴더에 coupon-api, coupon-core, coupon-consumer 모듈을 추가한다. 설정값은 coupon 프로젝트와 동일하다.


 

각 모듈의 기능

coupon-api

  • 쿠폰에 대한 api 서버이다
  • 유저의 요청을 처리한다. 

coupon-consumer

  • 쿠폰을 비동기적 구조로 발급할 것인데, 발급에 대한 요청이 쌓일 텐데 그 목록을 읽어 실제 쿠폰 발급을 처리 하는 서버이다.

coupon-core

  • api, server 에서 공통적으로 사용하는 기능들을 담을것이다. 
    • entity, repository 접근 등을 구현해 중복되는 코드를 줄인다.

패키지 구조 정리

coupon-api, coupon-consumer, coupon-core 모듈에서 src, .gitognore, build.gradel.kts 파일 제외하고 모두 삭제해준다.


Gradle 구조 정리

기본 구조

coupon 에서 api, core, consumer 를 관리할 것인데 기존 구조는 맞지 않는다.

coupon 제외 모두 삭제해준다.

삭제 후, coupon의 setting.gradle.kts 에 include 를 추가해준다. 

rootProject.name = "coupon"
include("coupon-core", "coupon-api", "coupon-consumer")

결과


build.gradle.kts 의존성 추가

val bootJar: org.springframework.boot.gradle.tasks.bundling.BootJar by tasks

bootJar.enabled = false

plugins {
    java
    id("org.springframework.boot") version "3.4.0"
    id("io.spring.dependency-management") version "1.1.6"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
        extendsFrom(configurations.annotationProcessor.get())
    }
}

repositories {
    mavenCentral()
}

subprojects {
    apply(plugin = "java")
    apply(plugin = "io.spring.dependency-management")
    apply(plugin = "org.springframework.boot")

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation("org.springframework.boot:spring-boot-starter-data-jpa")
        implementation("org.springframework.boot:spring-boot-starter-data-redis")
        compileOnly("org.projectlombok:lombok")
        annotationProcessor("org.projectlombok:lombok")
        runtimeOnly("com.h2database:h2")
        runtimeOnly("com.mysql:mysql-connector-j")
        implementation("org.springframework.boot:spring-boot-starter")
        implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
        implementation("org.springframework.boot:spring-boot-starter-actuator")
        implementation("io.micrometer:micrometer-registry-prometheus")
        annotationProcessor("com.querydsl:querydsl-apt:5.0.0:jakarta")
        annotationProcessor("jakarta.annotation:jakarta.annotation-api")
        annotationProcessor("jakarta.persistence:jakarta.persistence-api")
        testImplementation("org.springframework.boot:spring-boot-starter-test")
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

subprojects:

최상단 프로젝트 coupon 의 하위인 모듈에 대해 위 설정값을 적용하겠다.

 

coupon-core 의 build.gradle

val bootJar: org.springframework.boot.gradle.tasks.bundling.BootJar by tasks

bootJar.enabled = false

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
    implementation("com.fasterxml.jackson.core:jackson-databind")
    implementation("org.redisson:redisson-spring-boot-starter:3.16.4")
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("com.github.ben-manes.caffeine:caffeine")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

 

coupon-api, coupon-consumer 의 build.gradle

dependencies {
    implementation(project(":coupon-core"))
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

api, consumer 둘 다 coupon-core에 의존하게 설정한다.  = core 에 있는 모듈을 사용한다.

  

설정 했다면, 잘됐는지 확인해준다

  • gradle - reload all project 
  • coupon - Tasks-build-build ㅕ

최상단 프로젝트이기 때문에 하위 프로젝트 모두 빌드된다.

BUILD SUCCESSFUL 이게 뜨면 설정 완료.

 

 


application.yml 파일 설정

 

하위 모듈의 applicaiton.properties 파일을

applicaiotn-core.yml

applicaion-api.yml

applicaion-consumer.yml

이런 형식으로 바꿔준다. 

 


Coupon-core 모듈 정리 

coupon-core 는 독립적으로 사용하는 것이 아닌, 다른 프로젝트에 의존해 사용할 것이기 때문에 CouponCoreApplication class를 삭제하고 - CouponCoreConfiguration class 를 생성한다.

package com.example.couponcore;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
public class CouponCoreConfiguration {

}

 

CouponCoreApplicationTest class 또한 삭제해준다.


Coupon-consumer모듈 설정

CouponConsumerApplication 를 수정해준다. 

package com.example.couponconsumer;

import com.example.couponcore.CouponCoreConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

@Import(CouponCoreConfiguration.class)
@SpringBootApplication
public class CouponConsumerApplication {

    public static void main(String[] args) {
       System.setProperty("spring.config.name", "application-core, application-consumer");
       SpringApplication.run(CouponConsumerApplication.class, args);
    }

}

yml파일을 수정해준다.

 

spring:
  application:
    name: coupon-consumer
server:
  port: 8081

 

coupon-api 모듈 설정 

CouponApiApplication class를 수정해준다.


@SpringBootApplication
public class CouponApiApplication {

    public static void main(String[] args) {
       System.setProperty("spring.config.name", "application-core, application-api");
       SpringApplication.run(CouponApiApplication.class, args);
    }

}

 

yml 파일을 수정해준다.

spring:
  application:
    name: coupon-api
server:
  port: 8080

 

 

 


api, consumer Application 을 실행 시켜보고, 8080, 8081 포트가 잘 실행된다면

 

성공~

 

728x90