Skip to content

Commit 0195bcc

Browse files
authored
Merge pull request #166 from Kimjipang/main
[volume-6] 외부 시스템 장애 및 지연 대응
2 parents bd8fb7b + 508b3ee commit 0195bcc

71 files changed

Lines changed: 1754 additions & 29 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/commerce-api/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ dependencies {
1616
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
1717
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
1818

19+
// FeignClient
20+
implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
21+
22+
// Resilience4j
23+
implementation("io.github.resilience4j:resilience4j-spring-boot3")
24+
implementation("org.springframework.boot:spring-boot-starter-aop")
25+
1926
// test-fixtures
2027
testImplementation(testFixtures(project(":modules:jpa")))
2128
testImplementation(testFixtures(project(":modules:redis")))

apps/commerce-api/src/main/java/com/loopers/CommerceApiApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import org.springframework.boot.autoconfigure.SpringBootApplication;
66
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
77
import org.springframework.cache.annotation.EnableCaching;
8+
import org.springframework.cloud.openfeign.EnableFeignClients;
89

910
import java.util.TimeZone;
1011

1112
@ConfigurationPropertiesScan
1213
@SpringBootApplication
1314
@EnableCaching
15+
@EnableFeignClients
1416
public class CommerceApiApplication {
1517

1618
@PostConstruct
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.loopers.application.coupon;
2+
3+
import com.loopers.domain.coupon.Coupon;
4+
import com.loopers.domain.coupon.CouponRepository;
5+
import com.loopers.domain.user.UserRepository;
6+
import com.loopers.interfaces.api.coupon.CouponV1Dto;
7+
import com.loopers.support.error.CoreException;
8+
import com.loopers.support.error.ErrorType;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.stereotype.Component;
11+
12+
import java.util.List;
13+
14+
@Component
15+
@RequiredArgsConstructor
16+
public class CouponFacade {
17+
private final CouponRepository couponRepository;
18+
private final UserRepository userRepository;
19+
20+
public CouponInfo issueCoupon(CouponV1Dto.CouponRequest request) {
21+
/*
22+
👨‍💻 쿠폰 발급 로직
23+
- [ ] 사용자 검증
24+
- [ ] 쿠폰 발급
25+
*/
26+
Long userId = request.userId();
27+
userRepository.findById(userId).orElseThrow(
28+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 회원입니다.")
29+
);
30+
31+
Coupon coupon = Coupon.create(request);
32+
Coupon saved = couponRepository.save(coupon);
33+
34+
return CouponInfo.from(saved);
35+
}
36+
37+
public List<CouponInfo> findCoupons(Long userId) {
38+
/*
39+
👨‍💻 사용 가능한 쿠폰 조회 로직
40+
- [ ] 사용자 검증
41+
- [ ] 쿠폰 조회
42+
*/
43+
userRepository.findById(userId).orElseThrow(
44+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 회원입니다.")
45+
);
46+
47+
List<Coupon> coupons = couponRepository.findAllByUserId(userId).orElseThrow(
48+
() -> new CoreException(ErrorType.BAD_REQUEST, "사용 가능한 쿠폰이 존재하지 않습니다.")
49+
);
50+
51+
return coupons.stream()
52+
.map(CouponInfo::from)
53+
.toList();
54+
}
55+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.loopers.application.coupon;
2+
3+
import com.loopers.domain.coupon.Coupon;
4+
import com.loopers.domain.coupon.CouponType;
5+
6+
public record CouponInfo(Long id, Long userId, CouponType couponType, int quantity) {
7+
public static CouponInfo from(Coupon coupon) {
8+
return new CouponInfo(
9+
coupon.getId(),
10+
coupon.getUserId(),
11+
coupon.getCouponType(),
12+
coupon.getQuantity()
13+
);
14+
}
15+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.loopers.application.event.coupon;
2+
3+
import com.loopers.application.order.OrderCreatedEvent;
4+
import com.loopers.domain.coupon.Coupon;
5+
import com.loopers.domain.coupon.CouponRepository;
6+
import com.loopers.support.error.CoreException;
7+
import com.loopers.support.error.ErrorType;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Component;
10+
import org.springframework.transaction.annotation.Propagation;
11+
import org.springframework.transaction.annotation.Transactional;
12+
import org.springframework.transaction.event.TransactionPhase;
13+
import org.springframework.transaction.event.TransactionalEventListener;
14+
15+
@Component
16+
@RequiredArgsConstructor
17+
public class CouponEventHandler {
18+
private final CouponRepository couponRepository;
19+
20+
@Transactional(propagation = Propagation.REQUIRES_NEW)
21+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
22+
public void onOrderCreated(OrderCreatedEvent event) {
23+
Long couponId = event.couponId();
24+
Coupon coupon = couponRepository.findById(couponId).orElseThrow(
25+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 쿠폰입니다.")
26+
);
27+
28+
coupon.useCoupon();
29+
}
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.loopers.application.event.like;
2+
3+
import com.loopers.application.like.LikeCreateEvent;
4+
import com.loopers.domain.product.Product;
5+
import com.loopers.domain.product.ProductRepository;
6+
import com.loopers.support.error.CoreException;
7+
import com.loopers.support.error.ErrorType;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Component;
10+
import org.springframework.transaction.annotation.Propagation;
11+
import org.springframework.transaction.annotation.Transactional;
12+
import org.springframework.transaction.event.TransactionPhase;
13+
import org.springframework.transaction.event.TransactionalEventListener;
14+
15+
@Component
16+
@RequiredArgsConstructor
17+
public class LikeEventHandler {
18+
private final ProductRepository productRepository;
19+
20+
@Transactional(propagation = Propagation.REQUIRES_NEW)
21+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
22+
public void onLikeCreated(LikeCreateEvent event) {
23+
Product product = productRepository.findById(event.productId()).orElseThrow(
24+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 상품입니다.")
25+
);
26+
27+
product.addLikeCount();
28+
}
29+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.loopers.application.event.useraction;
2+
3+
import com.loopers.application.product.UserActionEvent;
4+
import com.loopers.domain.actionlog.UserActionLog;
5+
import com.loopers.infrastructure.actionlog.UserActionLogJpaRepository;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.stereotype.Component;
8+
import org.springframework.transaction.annotation.Propagation;
9+
import org.springframework.transaction.annotation.Transactional;
10+
import org.springframework.transaction.event.TransactionPhase;
11+
import org.springframework.transaction.event.TransactionalEventListener;
12+
13+
@Component
14+
@RequiredArgsConstructor
15+
public class UserActionLogEventHandler {
16+
private final UserActionLogJpaRepository userActionLogJpaRepository;
17+
18+
@Transactional(propagation = Propagation.REQUIRES_NEW)
19+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
20+
public void onUserAction(UserActionEvent event) {
21+
UserActionLog userActionLog = UserActionLog.create(event);
22+
userActionLogJpaRepository.save(userActionLog);
23+
}
24+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.loopers.application.like;
2+
3+
public record LikeCreateEvent(Long userId, Long productId) {
4+
}

apps/commerce-api/src/main/java/com/loopers/application/like/LikeFacade.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package com.loopers.application.like;
22

3+
import com.loopers.application.product.UserActionEvent;
4+
import com.loopers.domain.actionlog.ActionType;
35
import com.loopers.domain.like.Like;
46
import com.loopers.domain.like.LikeRepository;
5-
import com.loopers.domain.product.Product;
67
import com.loopers.domain.product.ProductRepository;
78
import com.loopers.domain.user.UserRepository;
89
import com.loopers.interfaces.api.like.LikeV1Dto;
910
import com.loopers.support.error.CoreException;
1011
import com.loopers.support.error.ErrorType;
1112
import lombok.RequiredArgsConstructor;
13+
import org.springframework.context.ApplicationEventPublisher;
1214
import org.springframework.stereotype.Component;
1315
import org.springframework.transaction.annotation.Transactional;
1416

@@ -18,6 +20,7 @@ public class LikeFacade {
1820
private final LikeRepository likeRepository;
1921
private final UserRepository userRepository;
2022
private final ProductRepository productRepository;
23+
private final ApplicationEventPublisher publisher;
2124

2225
@Transactional
2326
public LikeInfo doLike(LikeV1Dto.LikeRequest request) {
@@ -33,7 +36,7 @@ public LikeInfo doLike(LikeV1Dto.LikeRequest request) {
3336
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 유저입니다.")
3437
);
3538

36-
Product product = productRepository.findById(productId).orElseThrow(
39+
productRepository.findById(productId).orElseThrow(
3740
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 상품입니다.")
3841
);
3942

@@ -43,7 +46,8 @@ public LikeInfo doLike(LikeV1Dto.LikeRequest request) {
4346
Like newLike = request.toEntity();
4447
likeRepository.save(newLike);
4548

46-
product.addLikeCount();
49+
publisher.publishEvent(new LikeCreateEvent(userId, productId));
50+
publisher.publishEvent(new UserActionEvent(userId, productId, ActionType.DO_LIKE));
4751

4852
return LikeInfo.from(newLike);
4953
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.loopers.application.order;
2+
3+
public record OrderCreatedEvent(Long couponId) {
4+
}

0 commit comments

Comments
 (0)