🚀AWS ECS Fargate를 활용한 할인 쿠폰 발급 시스템 - 1


오늘 한 작업

본격적으로 개발은 시작해보자. 우선 프로젝트의 규모를 대충 잡아봤다. 그래야 방향성이 잡힐 것 같아서 였다. 우선 기본적인 틀은 로또를 생성하는 API / 로또 발급하는 API / 발급된 쿠폰 조회, 관리 API 이렇게 3부분이다. 가장 고민이 되었던 부분은 Coupon 생성 부분인데 재고 관리형 쿠폰 vs 개별 쿠폰

1안 쿠폰 관리형

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 쿠폰 마스터
class Coupon {
    private Long id;
    private String name;
    private int discountAmount;
    private int stock;  // 재고 수량
    private LocalDateTime validFrom;
    private LocalDateTime validTo;
}

// 발행된 쿠폰
class IssuedCoupon {
    private Long id;
    private Long couponId;
    private String couponCode;  // 유니크한 코드
    private Long userId;
    private LocalDateTime issuedAt;
    private boolean used;
}

쿠폰의 재고를 가지고 있는 쿠폰 엔티티가 있고 발행을 했을때 발행된 Coupon 정보를 보관하는 방식

2안 개별 쿠폰

1
2
3
4
5
6
7
8
9
10
11
class Coupon {
    private Long id;
    private String couponCode;  // 유니크한 코드
    private String name;
    private int discountAmount;
    private Long userId;        // 발급된 사용자 (null이면 미발급)
    private LocalDateTime validFrom;
    private LocalDateTime validTo;
    private LocalDateTime issuedAt;
    private boolean used;
}

엔티티가 유니크한 쿠폰이 되어 처음부터 만들때 각 쿠폰의 정보를 보관하고 있는 방식

나는 1안을 선택했다.

  1. 데이터 효율성
    • 1000개의 쿠폰을 발급한다고 했을 때 2안의 경우 동일한 정보를 가진 쿠폰을 1000개나 만들어야 한다.
  2. 재고 관리 용이성
    • 아무래도 2안은 각각의 쿠폰이 각각 저장되기 때문에 재고를 관리할 때 재고 관리의 많은 cost가 든다.
  3. 재고 확인과 발급을 분리할 수 있다.
  4. 쿠폰의 정책 변경에 용이하다.
    • 1안은 마스터 쿠폰 정보만 수정하면 됨
  5. 조회 성능
    • 아무래도 쿠폰 정보와 발급 정보가 분리 되어있기 때문에 각각을 따로 조회 최적화를 진행 할 수 있어서 어떤 사용자가 발급을 했는지를 따로 관리하기 때문에 조회가 편하다

프로젝트 생성

  • spring 3.xx 버전 + java 21
  • 2024-11-21-170130.png 일단 개발 DB는 H2를 사용하기로 했고 redis는 docker를 활용해서 local redis를 활용했다.

Docker 환경 구성

  • 추후에 AWS Fargate 환경에 프로젝트 배포해야 하기 때문에 docker환경을 구성했다.

Dockerfile

1
2
3
4
5
6
7
FROM openjdk:21-slim  
  
WORKDIR /app  
  
COPY build/libs/*.jar app.jar  
  
ENTRYPOINT ["java", "-jar", "app.jar"]

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3'  
services:  
  app:  
    build: .  
    ports:  
      - "8080:8080"  
    depends_on:  
      - redis  
    environment:  
      - SPRING_PROFILES_ACTIVE=local  
  
  redis:  
    image: redis:latest  
    ports:  
      - "6379:6379"
  • 개발환경에서 편하게 테스트하기 위해 docker-compose 파일 작성
1
2
3
4
5
6
7
8
9
docker-compose down //기존 실행 중인 도커 컨테이너 중지

./gradlew clean build //프로젝트 빌드

docker build -t [이름] . //도커 이미지 빌드

docker-compose up //전체 도커 컴포즈로 실행

docker-compose up redis//도커 컴포즈로 redis만 실행
  • 지금 h2 데이터 베이스를 사용하고 있어서 redis만 docker로 실행하고 애플리케이션은 ide에서 실행하는 방식으로 진행한다.

    Redis 설정 클래스 작성

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    @Configuration  // 스프링의 설정 클래스임을 명시
    public class RedisConfig {
        
      // application.yml의 설정값을 주입
      @Value("${spring.data.redis.host}")
      private String host;
        
      @Value("${spring.data.redis.port}")
      private int port;
        
      // Redis 연결을 위한 Factory 빈 등록
      @Bean
      public RedisConnectionFactory redisConnectionFactory() {
          return new LettuceConnectionFactory(host, port);
      }
        
      // Redis 데이터 조작을 위한 Template 빈 등록
      @Bean
      public RedisTemplate<String, Object> redisTemplate() {
          RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            
          // 앞에서 만든 연결 Factory 설정
          redisTemplate.setConnectionFactory(redisConnectionFactory());
            
          // String 타입의 key, value를 위한 직렬화 설정
          // Redis에 저장될 때 데이터를 어떤 형식으로 변환할지 결정
          redisTemplate.setKeySerializer(new StringRedisSerializer());
          redisTemplate.setValueSerializer(new StringRedisSerializer());
            
          return redisTemplate;
      }
    }
    

직렬화 : 자바 객체를 바이트 스트림으로 변환 역직렬화 : 바이트 스트림을 다시 자바 객체로