Git Product home page Git Product logo

coupon-event-example's Introduction

Environment

  • Java 17
  • Gradle 8.2
  • Spring Boot 3.2.0
  • MySQL 8.0.27
  • Docker
  • Kafka

Setting

cd docker

docker-compose -f docker-compose-mysql.yaml up -d
docker-compose -f docker-compose-kafka.yaml up -d

cd ..

./gradlew :consumer:bootRun

Kafka

How to create a topic

docker exec -it kafka kafka-topics.sh --create --topic coupon-create --bootstrap-server localhost:9092

How to produce a message

docker exec -it kafka kafka-console-producer.sh --topic coupon-create --bootstrap-server localhost:9092

How to consume a message

docker exec -it kafka kafka-console-consumer.sh --topic coupon-create --from-beginning --bootstrap-server localhost:9092 --key-deserializer org.apache.kafka.common.serialization.StringDeserializer --value-deserializer org.apache.kafka.common.serialization.LongDeserializer

선착순 쿠폰 발급 이벤트

1. 오직 DB만 사용하여 구현

구현 코드

코드 예시

@Service
public class CouponService implements CouponCommand {

    private final CouponRepository couponRepository;

    public CouponService(
        CouponRepository couponRepository
    ) {
        this.couponRepository = couponRepository;
    }

    @Override
    public void issueCoupon(Long userId) {
        long count = couponRepository.count();

        if (count > MAX_COUPON_COUNT) {
            return;
        }

        couponRepository.save(new Coupon(userId));
    }
}

2. Redis를 사용하여 구현

구현 코드

코드 예시

@Service
public class CouponServiceWithRedis implements CouponCommand {
    private final CouponRepository couponRepository;

    private final CouponCountRepository couponCountRepository;

    public CouponServiceWithRedis(
        CouponRepository couponRepository,
        CouponCountRepository couponCountRepository
    ) {
        this.couponRepository = couponRepository;
        this.couponCountRepository = couponCountRepository;
    }

    @Override
    public void issueCoupon(Long userId) {
        long count = couponCountRepository.increment();

        if (count > MAX_COUPON_COUNT) {
            return;
        }

        couponRepository.save(new Coupon(userId));
    }
}

3. Redis + Kafka를 사용하여 구현

구현 코드

코드 예시

@Service
public class CouponServiceWithRedisAndKafka implements CouponCommand {
    private final CouponCountRepository couponCountRepository;

    private final CouponCreateProducer couponCreateProducer;

    public CouponServiceWithRedisAndKafka(
        CouponCountRepository couponCountRepository,
        CouponCreateProducer couponCreateProducer) {
        this.couponCountRepository = couponCountRepository;
        this.couponCreateProducer = couponCreateProducer;
    }

    @Override
    public void issueCoupon(Long userId) {
        long count = couponCountRepository.increment();

        if (count > MAX_COUPON_COUNT) {
            return;
        }

        couponCreateProducer.createCoupon(userId);
    }
}

Producer

@Component
public class CouponCreateProducer {

    public static final String COUPON_CREATE_TOPIC = "coupon-create";

    private final KafkaTemplate<String, Long> kafkaTemplate;

    public CouponCreateProducer(KafkaTemplate<String, Long> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void createCoupon(Long userId) {
        kafkaTemplate.send(COUPON_CREATE_TOPIC, userId);
    }
}

Consumer

@Component
@Slf4j
public class CouponCreateConsumer {

    private static final String COUPON_CREATE_TOPIC = "coupon-create";

    private final CouponRepository couponRepository;

    private final FailedEventRepository failedEventRepository;

    public CouponCreateConsumer(CouponRepository couponRepository,
        FailedEventRepository failedEventRepository) {
        this.couponRepository = couponRepository;
        this.failedEventRepository = failedEventRepository;
    }

    @KafkaListener(topics = CouponCreateConsumer.COUPON_CREATE_TOPIC, groupId = "coupon-consumer-group-1")
    public void consume(Long userId) {
        try {
            log.info("Consumed message: {}", userId);
            couponRepository.save(new Coupon(userId));
        } catch (Exception e) {
            log.error("Failed to consume message: {}", userId);
            failedEventRepository.save(new FailedEvent(userId));
        }
    }
}

4. 추가적으로 1인당 1개의 쿠폰만 발급되도록 구현

구현 코드

코드 예시

@Service
public class CouponLimitedServiceWithRedisAndKafka implements CouponCommand {
    private final CouponCountRepository couponCountRepository;

    private final CouponCreateProducer couponCreateProducer;

    private final CouponAppliedUserRepository couponAppliedUserRepository;

    public CouponLimitedServiceWithRedisAndKafka(
        CouponCountRepository couponCountRepository,
        CouponCreateProducer couponCreateProducer,
        CouponAppliedUserRepository couponAppliedUserRepository) {
        this.couponCountRepository = couponCountRepository;
        this.couponCreateProducer = couponCreateProducer;
        this.couponAppliedUserRepository = couponAppliedUserRepository;
    }

    @Override
    public void issueCoupon(Long userId) {
        couponAppliedUserRepository.add(userId);

        long count = couponCountRepository.increment();

        if (count > MAX_COUPON_COUNT) {
            return;
        }

        couponCreateProducer.createCoupon(userId);
    }
}

Redis에서 제공하는 타입 Set을 사용하여 구현

@Repository
public class CouponAppliedUserRepository {
    private final RedisTemplate<String, String> redisTemplate;

    public CouponAppliedUserRepository(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void add(Long userId) throws IllegalStateException {
        Long result = redisTemplate
            .opsForSet()
            .add("coupon-applied-user", userId.toString());

        if (result == null || result != 1) {
            throw new IllegalStateException("이미 쿠폰을 받은 사용자입니다.");
        }
    }
}

coupon-event-example's People

Contributors

rolroralra avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.