Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 포인트 사용 [유저 데이트 코스 열람] API - #65 #70

Merged
merged 11 commits into from
Jul 11, 2024

Conversation

rlarlgnszx
Copy link
Member

🔥Pull requests

⛳️ 작업한 브랜치

👷 작업한 내용

  • 포인트 사용 [유저 데이트 코스 열람] API - [FEAT] 포인트 사용 [유저 데이트 코스 열람] API  #65
  • RedisStream Config 구현
  • @Configuration
    @RequiredArgsConstructor
    public class RedisStreamConfig {
    private final pointEventListener pointEventListener;
    private final FreeEventListener freeEventListener;
    @Value("${spring.data.redis.host}")
    private String host;
    @Value("${spring.data.redis.port}")
    private int port;
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory());
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    return redisTemplate;
    }
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
    return new LettuceConnectionFactory(host, port);
    }
    public void createStreamConsumerGroup(final String streamKey, final String consumerGroupName) {
    // Stream이 존재 하지 않으면, MKSTREAM 옵션을 통해 만들고, ConsumerGroup또한 생성한다
    System.out.println(streamKey + consumerGroupName);
    // Stream이 존재하지 않으면, MKSTREAM 옵션을 통해 스트림과 소비자 그룹을 생성
    if (Boolean.FALSE.equals(redisTemplate().hasKey(streamKey))) {
    RedisAsyncCommands<String, String> commands = (RedisAsyncCommands<String, String>) Objects.requireNonNull(
    redisTemplate()
    .getConnectionFactory())
    .getConnection()
    .getNativeConnection();
    CommandArgs<String, String> args = new CommandArgs<>(StringCodec.UTF8)
    .add(CommandKeyword.CREATE)
    .add(streamKey)
    .add(consumerGroupName)
    .add("0")
    .add("MKSTREAM");
    // MKSTREAM 옵션을 사용하여 스트림과 그룹을 생성
    commands.dispatch(CommandType.XGROUP, new StatusOutput<>(StringCodec.UTF8), args).toCompletableFuture()
    .join();
    }
    // Stream 존재 시, ConsumerGroup 존재 여부 확인 후 ConsumerGroup을 생성
    else {
    if (!isStreamConsumerGroupExist(streamKey, consumerGroupName)) {
    redisTemplate().opsForStream().createGroup(streamKey, ReadOffset.from("0"), consumerGroupName);
    }
    }
    }
    // ConsumerGroup 존재 여부 확인
    public boolean isStreamConsumerGroupExist(final String streamKey, final String consumerGroupName) {
    Iterator<StreamInfo.XInfoGroup> iterator = redisTemplate()
    .opsForStream().groups(streamKey).stream().iterator();
    while (iterator.hasNext()) {
    StreamInfo.XInfoGroup xInfoGroup = iterator.next();
    if (xInfoGroup.groupName().equals(consumerGroupName)) {
    return true;
    }
    }
    return false;
    }
    @Bean
    public Subscription PointSubscription() {
    createStreamConsumerGroup("coursePoint", "courseGroup");
    StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> containerOptions = StreamMessageListenerContainer.StreamMessageListenerContainerOptions
    .builder().pollTimeout(Duration.ofMillis(100)).build();
    StreamMessageListenerContainer<String, MapRecord<String, String, String>> container = StreamMessageListenerContainer.create(
    redisConnectionFactory(),
    containerOptions);
    Subscription subscription = container.receiveAutoAck(Consumer.from("courseGroup", "instance-1"),
    StreamOffset.create("coursePoint", ReadOffset.lastConsumed()), pointEventListener);
    container.start();
    return subscription;
    }
    @Bean
    public Subscription FreeSubscription() {
    createStreamConsumerGroup("courseFree", "courseGroup");
    StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> containerOptions = StreamMessageListenerContainer.StreamMessageListenerContainerOptions
    .builder().pollTimeout(Duration.ofMillis(100)).build();
    StreamMessageListenerContainer<String, MapRecord<String, String, String>> container = StreamMessageListenerContainer.create(
    redisConnectionFactory(),
    containerOptions);
    Subscription subscription = container.receiveAutoAck(Consumer.from("courseGroup", "instance-2"),
    StreamOffset.create("courseFree", ReadOffset.lastConsumed()), freeEventListener);
    container.start();
    return subscription;
    }
    }
  • User 검증후 Point 사용 및 Free 검증
  • private CoursePaymentType validateUserFreeOrPoint(final User user, final int requiredPoints) {
    if (user.getFree() > 0) {
    return CoursePaymentType.FREE; // User가 free를 갖고 있으면 true를 반환
    } else if (user.getTotalPoint() < requiredPoints) {
    throw new DateRoadException(FailureCode.INSUFFICIENT_USER_POINTS);
    }
    return CoursePaymentType.POINT;
    }
    public void processCoursePayment(final CoursePaymentType coursePaymentType, User user, final Point point,
    final PointUseReq pointUseReq) {
    switch (coursePaymentType) {
    case FREE -> {
    asyncService.publishEventUserFree(user);
    }
    case POINT -> {
    pointRepository.save(point);
    asyncService.publishEvenUserPoint(user, pointUseReq);
    }
  • Point 사용시 PointEventListner로 User의 point를 변경하는 이벤트 발송
  • Free 사용시 FreeEventListner로 User의 Free를 변경하는 이벤트 발송
  • case FREE -> {
    asyncService.publishEventUserFree(user);
    }
    case POINT -> {
    pointRepository.save(point);
    asyncService.publishEvenUserPoint(user, pointUseReq);

🚨 참고 사항

  • 아마 레디스 서버에 아직 없어서 빌드 에러날거에용
  • 얼른 설치하겠습니다

📟 관련 이슈

@rlarlgnszx rlarlgnszx self-assigned this Jul 11, 2024
Copy link
Contributor

@gardening-y gardening-y left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

진짜 기훈 멋지다.. 저좀 알려주세요 선생님!! 기훈 최고..🙌

Comment on lines 25 to 27
User user = userRepository.findById(userId).orElseThrow(
() -> new DateRoadException(FailureCode.USER_NOT_FOUND)
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드 빼서 관리하면 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 빠질거같아요 당신한테

@Transactional
public void openCourse(final Long userId, final Long courseId, final PointUseReq pointUseReq) {
User user = userRepository.findById(userId).orElseThrow(
() -> new DateRoadException(FailureCode.USER_NOT_FOUND)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EntityNotFoundException으로 예외처리하면 좋을 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 특별함 처리 해주세요

Comment on lines 110 to 116
public void openCourse(final Long userId, final Long courseId, final PointUseReq pointUseReq) {
User user = userRepository.findById(userId).orElseThrow(
() -> new DateRoadException(FailureCode.USER_NOT_FOUND)
);
Course course = courseRepository.findById(courseId).orElseThrow(
() -> new DateRoadException(FailureCode.COURSE_NOT_FOUND)
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자주 쓰이는 로직들은 메서드로 분리해서 관리해도 좋을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 알겠습니다!

return CoursePaymentType.POINT;
}

public void processCoursePayment(final CoursePaymentType coursePaymentType, User user, final Point point,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

통일하려면 final 선언해주면 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다!

Comment on lines +134 to +141
switch (coursePaymentType) {
case FREE -> {
asyncService.publishEventUserFree(user);
}
case POINT -> {
pointRepository.save(point);
asyncService.publishEvenUserPoint(user, pointUseReq);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

성능측면으로 비교군이 적을 경우 switch보다 if가 낫다고 합니다!ㅎㅎ

https://thinkpro.tistory.com/132

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 예전에는 if-else 가 성능적으로 좋았지만 현재는 그렇게 차이가 나지 않고 switch를 쓰는게 더 효과적이라고 합니다!
저도 보고 찾아보다가 알게되어서 공유합니다!!
https://backendcode.tistory.com/212

Comment on lines 25 to 27
User user = userRepository.findById(userId).orElseThrow(
() -> new DateRoadException(FailureCode.USER_NOT_FOUND)
);
Copy link
Contributor

@gardening-y gardening-y Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외처리 클래스 변경이랑 메서드 분리하면 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알겠씁니다!

@PathVariable final Long courseId,
@RequestBody final PointUseReq pointUseReq
) {
courseService.openCourse(userId,courseId,pointUseReq);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

띄어쓰기요..

rlarlgnszx and others added 3 commits July 12, 2024 05:14
# Conflicts:
#	dateroad-api/src/main/java/org/dateroad/course/api/CourseController.java
#	dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java
@rlarlgnszx rlarlgnszx merged commit d7c8410 into develop Jul 11, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEAT] 포인트 사용 [유저 데이트 코스 열람] API
3 participants