Skip to content

Commit

Permalink
Merge pull request #34 from lotteon2/sqs
Browse files Browse the repository at this point in the history
✨ SQS publisher 작성
  • Loading branch information
qwerty1434 authored Dec 26, 2023
2 parents d03e25c + 9960a7f commit bdc74b1
Show file tree
Hide file tree
Showing 16 changed files with 385 additions and 37 deletions.
9 changes: 8 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'

implementation group: 'io.github.lotteon-maven', name: 'blooming-blooms-utils', version: '202312220343'
// sqs
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.4.RELEASE'
implementation 'org.springframework.cloud:spring-cloud-aws-messaging:2.2.4.RELEASE'
implementation 'software.amazon.awssdk:sns:2.21.37'

implementation group: 'io.github.lotteon-maven', name: 'blooming-blooms-utils', version: '202312260338'
}

dependencyManagement {
Expand Down Expand Up @@ -144,6 +149,7 @@ jacocoTestCoverageVerification {
"**/*Response.class",
"**/*Id.class",
"**/*Facade.class",
"**/*Publisher.class",
"**/*Application*"
]+Qdomains)
})
Expand Down Expand Up @@ -180,6 +186,7 @@ jacocoTestReport {
"**/*Response.class",
"**/*Id.class",
"**/*Facade.class",
"**/*Publisher.class",
"**/*Application*"
]+Qdomains)
})
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/kr/bb/store/client/UserClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kr.bb.store.client;


import bloomingblooms.response.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "user-service", url="${endpoint.user-service}")
public interface UserClient {
@GetMapping("/client/users/{userId}/phone-number")
CommonResponse<String> getPhoneNumber(@PathVariable(name = "userId") Long userId);

}
41 changes: 41 additions & 0 deletions src/main/java/kr/bb/store/config/AWSConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package kr.bb.store.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;

@Configuration
public class AWSConfiguration {
@Value("${cloud.aws.credentials.ACCESS_KEY_ID}")
private String accessKeyId;

@Value("${cloud.aws.credentials.SECRET_ACCESS_KEY}")
private String secretAccessKey;

@Value("${cloud.aws.region.static}")
private String region;

public AwsCredentialsProvider getAwsCredentials() {
AwsBasicCredentials awsBasicCredentials =
AwsBasicCredentials.create(accessKeyId, secretAccessKey);
return () -> awsBasicCredentials;
}

@Primary
@Bean
public AmazonSQSAsync amazonSQSAsync() {
BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKeyId, secretAccessKey);
return AmazonSQSAsyncClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
.build();
}

}
26 changes: 20 additions & 6 deletions src/main/java/kr/bb/store/domain/cargo/facade/CargoFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import kr.bb.store.domain.cargo.exception.LockInterruptedException;
import kr.bb.store.domain.cargo.exception.StockChangeFailedException;
import kr.bb.store.domain.cargo.service.CargoService;
import kr.bb.store.message.OutOfStockSQSPublisher;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
Expand All @@ -19,10 +20,13 @@
public class CargoFacade {
private final CargoService cargoService;
private final RedissonClient redissonClient;
private final OutOfStockSQSPublisher outOfStockSQSPublisher;
// TODO : Environment로 교체
private static final Long STOCK_ALERT_COUNT = 50L;

public void modifyAllStocksWithLock(Long storeId, List<StockModifyDto> stockModifyDtos) {
stockModifyDtos.forEach(stockModifyDto -> {
FlowerCargoId flowerCargoId = makeKeys(storeId, stockModifyDto.getFlowerId());
FlowerCargoId flowerCargoId = makeId(storeId, stockModifyDto.getFlowerId());
RLock lock = redissonClient.getLock(makeRedissonKey(storeId, stockModifyDto.getFlowerId()));
try {
boolean available = lock.tryLock(5,1, TimeUnit.SECONDS);
Expand All @@ -43,15 +47,18 @@ public void modifyAllStocksWithLock(Long storeId, List<StockModifyDto> stockModi
}

public void plusStockCountWithLock(Long storeId, Long flowerId, Long stock) {
FlowerCargoId flowerCargoId = makeKeys(storeId,flowerId);
FlowerCargoId flowerCargoId = makeId(storeId,flowerId);
RLock lock = redissonClient.getLock(makeRedissonKey(storeId, flowerId));
try {
boolean available = lock.tryLock(5,1, TimeUnit.SECONDS);
if(!available) {
throw new StockChangeFailedException();
}

cargoService.plusStockCount(flowerCargoId, stock);
Long stockCount = cargoService.plusStockCount(flowerCargoId, stock);
if(isInsufficientCondition(stockCount)) {
outOfStockSQSPublisher.publish(storeId);
}

} catch (InterruptedException e){
throw new LockInterruptedException();
Expand All @@ -63,15 +70,18 @@ public void plusStockCountWithLock(Long storeId, Long flowerId, Long stock) {
}

public void minusStockCountWithLock(Long storeId, Long flowerId, Long stock) {
FlowerCargoId flowerCargoId = makeKeys(storeId,flowerId);
FlowerCargoId flowerCargoId = makeId(storeId,flowerId);
RLock lock = redissonClient.getLock(makeRedissonKey(storeId, flowerId));
try {
boolean available = lock.tryLock(5,1, TimeUnit.SECONDS);
if(!available) {
throw new StockChangeFailedException();
}

cargoService.minusStockCount(flowerCargoId, stock);
Long stockCount = cargoService.minusStockCount(flowerCargoId, stock);
if(isInsufficientCondition(stockCount)) {
outOfStockSQSPublisher.publish(storeId);
}

} catch (InterruptedException e){
throw new LockInterruptedException();
Expand All @@ -86,7 +96,7 @@ public RemainingStocksResponse getAllStocks(Long storeId) {
return cargoService.getAllStocks(storeId);
}

private FlowerCargoId makeKeys(Long storeId, Long flowerId) {
private FlowerCargoId makeId(Long storeId, Long flowerId) {
return FlowerCargoId.builder()
.storeId(storeId)
.flowerId(flowerId)
Expand All @@ -97,4 +107,8 @@ private String makeRedissonKey(Long storeId, Long flowerId) {
return storeId + ":" + flowerId;
}

private boolean isInsufficientCondition(Long stockCount) {
return stockCount < STOCK_ALERT_COUNT;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@
@RequiredArgsConstructor
public class CargoService {
private final FlowerCargoRepository flowerCargoRepository;
private static final Long EMPTY_COUNT = 0L;

@Transactional
public void modifyAllStocks(StockModifyDto stockModifyDto, FlowerCargoId flowerCargoId) {
if(stockModifyDto.getStock() < 0) {
if(stockModifyDto.getStock() < EMPTY_COUNT) {
throw new StockCannotBeNegativeException();
}
flowerCargoRepository.modifyStock(flowerCargoId.getStoreId(), flowerCargoId.getFlowerId(), stockModifyDto.getStock());
}

@Transactional
public void plusStockCount(FlowerCargoId flowerCargoId, Long stock) {
public Long plusStockCount(FlowerCargoId flowerCargoId, Long stock) {
FlowerCargo flowerCargo = flowerCargoRepository.findById(flowerCargoId)
.orElseThrow(FlowerCargoNotFoundException::new);

Expand All @@ -48,10 +49,11 @@ public void plusStockCount(FlowerCargoId flowerCargoId, Long stock) {
}
flowerCargoRepository.plusStock(flowerCargoId.getStoreId(),flowerCargoId.getFlowerId(),stock);

return flowerCargo.getStock() + stock;
}

@Transactional
public void minusStockCount(FlowerCargoId flowerCargoId, Long stock) {
public Long minusStockCount(FlowerCargoId flowerCargoId, Long stock) {
FlowerCargo flowerCargo = flowerCargoRepository.findById(flowerCargoId)
.orElseThrow(FlowerCargoNotFoundException::new);

Expand All @@ -60,6 +62,7 @@ public void minusStockCount(FlowerCargoId flowerCargoId, Long stock) {
}
flowerCargoRepository.minusStock(flowerCargoId.getStoreId(),flowerCargoId.getFlowerId(),stock);

return flowerCargo.getStock() - stock;
}

@Transactional(readOnly = true)
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/kr/bb/store/domain/coupon/facade/CouponFacade.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package kr.bb.store.domain.coupon.facade;

import bloomingblooms.domain.notification.NotificationKind;
import bloomingblooms.domain.order.ProcessOrderDto;
import kr.bb.store.client.UserClient;
import kr.bb.store.domain.coupon.service.CouponService;
import kr.bb.store.message.OrderStatusSQSPublisher;
import lombok.RequiredArgsConstructor;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
Expand All @@ -12,10 +15,18 @@
@RequiredArgsConstructor
public class CouponFacade {
private final CouponService couponService;
private final UserClient userClient;
private final OrderStatusSQSPublisher orderStatusSQSPublisher;

@KafkaListener(topics = "coupon-use", groupId = "use-coupon")
public void useCoupons(ProcessOrderDto processOrderDto) {
LocalDate useDate = LocalDate.now();
couponService.useAllCoupons(processOrderDto.getCouponIds(), processOrderDto.getUserId(), useDate);
try {
LocalDate useDate = LocalDate.now();
couponService.useAllCoupons(processOrderDto.getCouponIds(), processOrderDto.getUserId(), useDate);
} catch (Exception e) {
Long userId = processOrderDto.getUserId();
String phoneNumber = userClient.getPhoneNumber(userId).getData();
orderStatusSQSPublisher.publish(userId, phoneNumber, NotificationKind.INVALID_COUPON);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import kr.bb.store.domain.question.controller.request.AnswerCreateRequest;
import kr.bb.store.domain.question.controller.request.QuestionCreateRequest;
import kr.bb.store.domain.question.controller.response.*;
import kr.bb.store.domain.question.service.QuestionService;
import kr.bb.store.domain.question.facade.QuestionFacade;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;
Expand All @@ -13,53 +13,49 @@
@RestController
@RequiredArgsConstructor
public class QuestionController {
private final QuestionService questionService;
private final QuestionFacade questionFacade;

@PostMapping("/questions")
public void createQuestion(@RequestBody QuestionCreateRequest questionCreateRequest,
@RequestHeader(value = "userId") Long userId) {
questionService.createQuestion(userId, questionCreateRequest);
@RequestHeader(value = "userId") Long userId) {
questionFacade.createQuestion(userId, questionCreateRequest);
}

@GetMapping("/questions/{questionId}")
public CommonResponse<QuestionDetailInfoResponse> getQuestionDetail(@PathVariable Long questionId) {
return CommonResponse.success(questionService.getQuestionInfo(questionId));
return CommonResponse.success(questionFacade.getQuestionInfo(questionId));
}

@PostMapping("/questions/{questionId}/answers")
public void createAnswer(@PathVariable Long questionId,
@RequestBody AnswerCreateRequest answerCreateRequest) {
questionService.createAnswer(questionId, answerCreateRequest.getContent());
@RequestBody AnswerCreateRequest answerCreateRequest) {
questionFacade.createAnswer(questionId, answerCreateRequest.getContent());
}

@GetMapping("/{storeId}/questions")
public CommonResponse<QuestionsForOwnerPagingResponse> storeQuestions(@PathVariable Long storeId,
@RequestParam(required = false) Boolean isReplied,
Pageable pageable) {
return CommonResponse.success(questionService.getQuestionsForStoreOwner(storeId,isReplied,pageable));
@RequestParam(required = false) Boolean isReplied, Pageable pageable) {
return CommonResponse.success(questionFacade.getQuestionsForStoreOwner(storeId,isReplied,pageable));
}

@GetMapping("/questions/product/{productId}")
public CommonResponse<QuestionsInProductPagingResponse> productQuestions(@PathVariable String productId,
@RequestParam(required = false) Boolean isReplied,
Pageable pageable,
@RequestHeader(value = "userId", required = false) Long userId) {
return CommonResponse.success(questionService.getQuestionsInProduct(userId, productId, isReplied, pageable));
public CommonResponse<QuestionsInProductPagingResponse> productQuestions(
@PathVariable String productId,@RequestParam(required = false) Boolean isReplied,
Pageable pageable, @RequestHeader(value = "userId", required = false) Long userId) {
return CommonResponse.success(questionFacade.getQuestionsInProduct(userId, productId, isReplied, pageable));
}

@GetMapping("/questions/product/{productId}/my")
public CommonResponse<MyQuestionsInProductPagingResponse> myQuestionsInProduct(@PathVariable String productId,
@RequestParam(required = false) Boolean isReplied,
Pageable pageable,
@RequestHeader(value = "userId") Long userId) {
return CommonResponse.success(questionService.getMyQuestionsInProduct(userId, productId, isReplied, pageable));
public CommonResponse<MyQuestionsInProductPagingResponse> myQuestionsInProduct(
@PathVariable String productId, @RequestParam(required = false) Boolean isReplied,
Pageable pageable, @RequestHeader(value = "userId") Long userId) {
return CommonResponse.success(questionFacade.getMyQuestionsInProduct(userId, productId, isReplied, pageable));
}

@GetMapping("/questions/my-page")
public CommonResponse<MyQuestionsInMypagePagingResponse> myQuestions(@RequestParam(required = false) Boolean isReplied,
Pageable pageable,
@RequestHeader(value = "userId") Long userId) {
return CommonResponse.success(questionService.getMyQuestions(userId, isReplied, pageable));
Pageable pageable, @RequestHeader(value = "userId") Long userId) {
return CommonResponse.success(questionFacade.getMyQuestions(userId, isReplied, pageable));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package kr.bb.store.domain.question.facade;

import kr.bb.store.client.UserClient;
import kr.bb.store.domain.question.controller.request.QuestionCreateRequest;
import kr.bb.store.domain.question.controller.response.*;
import kr.bb.store.domain.question.entity.Question;
import kr.bb.store.domain.question.service.QuestionService;
import kr.bb.store.message.AnswerSQSPublisher;
import kr.bb.store.message.QuestionSQSPublisher;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class QuestionFacade {
private final QuestionService questionService;
private final QuestionSQSPublisher questionSQSPublisher;
private final AnswerSQSPublisher answerSQSPublisher;
private final UserClient userClient;

public void createQuestion(Long userId, QuestionCreateRequest questionCreateRequest) {
questionService.createQuestion(userId, questionCreateRequest);
Long storeId = questionCreateRequest.getStoreId();
questionSQSPublisher.publish(storeId);
}

public QuestionDetailInfoResponse getQuestionInfo(Long questionId) {
return questionService.getQuestionInfo(questionId);
}

public void createAnswer(Long questionId, String content) {
questionService.createAnswer(questionId, content);
Question question = questionService.getQuestionById(questionId);
Long userId = question.getUserId();
String phoneNumber = userClient.getPhoneNumber(userId).getData();
answerSQSPublisher.publish(userId, phoneNumber);
}

public QuestionsForOwnerPagingResponse getQuestionsForStoreOwner(Long storeId, Boolean isReplied, Pageable pageable) {
return questionService.getQuestionsForStoreOwner(storeId, isReplied, pageable);
}

public QuestionsInProductPagingResponse getQuestionsInProduct(Long userId, String productId, Boolean isReplied, Pageable pageable) {
return questionService.getQuestionsInProduct(userId, productId, isReplied, pageable);
}

public MyQuestionsInProductPagingResponse getMyQuestionsInProduct(Long userId, String productId, Boolean isReplied, Pageable pageable) {
return questionService.getMyQuestionsInProduct(userId, productId, isReplied, pageable);
}

public MyQuestionsInMypagePagingResponse getMyQuestions(Long userId, Boolean isReplied, Pageable pageable) {
return questionService.getMyQuestions(userId, isReplied, pageable);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,9 @@ public Page<MyQuestionInMypageDto> readMyQuestionsInProduct(Long userId, String
public Page<MyQuestionInMypageDto> readQuestionsForMypage(Long userId, Boolean isReplied, Pageable pageable) {
return questionRepository.getMyQuestionsWithPaging(userId, isReplied, pageable);
}

public Question read(Long questionId) {
return questionRepository.findById(questionId)
.orElseThrow(QuestionNotFoundException::new);
}
}
Loading

0 comments on commit bdc74b1

Please sign in to comment.