https://github.com/GroovyArea/MyChickenBreastShop/wiki/Version-1
GitHub - GroovyArea/MyChickenBreastShop: ChikenBreastShop API with Spring boot
ChikenBreastShop API with Spring boot. Contribute to GroovyArea/MyChickenBreastShop development by creating an account on GitHub.
github.com
ํ๋ก์ ํธ ์ด๊ธฐ ์์ฑํ Wiki ๋ฌธ์์์ ๊ณํํ ๊ธฐ๋ฅ์ ๋ค ๊ตฌํ์ด ๋์๋ค.
๊ฒ์ํ, ๋ฐฐ์ก, ์ฑํ ๊ธฐ๋ฅ ๊ฐ์ ๊ฒฝ์ฐ๋ ๋ถ์์ ์ด๋ฏ๋ก ๋ค์ํ ๊ธฐ๋ฅ์ ์๊ฒ ๊ตฌํํ๋ ๊ฒ๋ณด๋ค ๊ธฐ๋ฅ ํ๋๋ฅผ ๊ตฌ์ฒด์ ์ผ๋ก ๊ณ ๋ คํ๋ฉฐ ๊ตฌํํ๋ ๊ฒ์ด ๋ ์๋ฏธ ์๊ฒ ๋ค๋ ํ๋จํ์ ๊ธฐ๋ฅ ๊ตฌํ์ ์ฌ๊ธฐ์ ์ข ๋ฃํ๊ฒ ๋์๋ค.
์ด๋ฒ 3์ผ ๊ฐ ๋ฆฌํฉํฐ๋ง ํ๋ฉฐ ๊ตฌํํ API๋ ์ด ๋ ๊ฐ์ง์ธ๋ฐ ์ด๋ฉ์ผ ์ธ์ฆ ๋ฒํธ ์ ์ก๊ณผ, ์ฃผ๋ฌธ API์ด๋ค. ๋ ๊ฐ ๋ชจ๋ ๋์์ฑ๊ณผ ์ฑ๋ฅ์ ๊ณ ๋ คํ์ฌ ๋ฆฌํฉํฐ๋ง์ ์งํํ๋ค.
๋ด๊ฐ ์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋๋ ์ ์ ๋จ์ํ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ๊ฑด ์ ๋ง ์ฝ๋ค.. ์๋ฌด๊ฒ๋ ์๋๋ฐ.
๊ธฐ๋ฅ์ด ์ ๋๋ผ๋ ์ฌ๋ฌ ๊ฐ์ง ๋ณ์๋ฅผ ๊ณ ๋ คํ๋ฉฐ ํ์คํ๊ฒ ๊ตฌํํ๋ ๊ฒ ๋ ์๋ฏธ๊ฐ ์์ง ์๋ ์ถ๋ค. ๋๊ตฐ๊ฐ๋ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ํ๋ก์ ํธ๋ก ํ๋๋ฐ ์ฌ๋ฌ ๋ฌธ์ ๋ค์ ๊ณ ๋ คํ๋ฉฐ ๋ง๋ค์๋ ๋๊ฒ ์ด๋ ต๋ค๋ ์๊ธฐ๋ค์ ํ๋ค. ๊ทธ๋งํผ ๊ฐ๋ฐ ๊ณต๋ถ์ ๋จ๋ ๊ฒ ์์ ๊ฒ์ด๊ณ , ์ฌ๋ฌ ๊ฐ์ง ๋ณ์๋ฅผ ๊ณ ๋ คํ๋ฉฐ ์์ผ๊ฐ ๋์ด์ง ๊ฒ์ด๋ค.
์์ผ๋ก๋ ๋ค์ํ ์๊ฐ์ผ๋ก ๋ฐ๋ผ๋ณด๋ ์ฐ์ต์ ํด์ผ๊ฒ ๋ค.
๋์์ฑ ๋ฌธ์
1. ์ด๋ฉ์ผ ์ธ์ฆ ๋ฒํธ ์ ์ก
=> ํ์ ๊ฐ์ ์ ์ด๋ฉ์ผ๋ก ์ธ์ฆ ๋ฒํธ๋ฅผ ์ ์กํด์ค๋ค. ํ์ง๋ง ๋ณธ ์๋ฒ์ ์ฅ์ ๊ฐ ์๊ธธ ์ ์ด๋ฉ์ผ ์ ์ก ์๋น์ค๋ ๋ง๋น๊ฐ ์๊ธธ ์ ์๋ค. ๊ทธ๋ฌ๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น?
MSA ํ๊ฒฝ์ ๊ณ ๋ คํด ๋ณด์๋ค
๋ฐ์ดํฐ ๋ถ์ฐ ์ฒ๋ฆฌ [Micro Service Architecture]
ํ๋ก์ ํธ๋ฅผ ์งํ ์ค์ด๋ค. ํ๋ก์ ํธ์ ๊ท๋ชจ๊ฐ ์ปค์ง ์๋ก ๊ณ์ธต ๊ฐ DTO ๊ฐ์ฒด๋ฅผ ์ด์ฉํ๋ ์ผ์ด ๋ง์์ก๋ค. ๋ถ๋ณ ๊ฐ์ฒด๋ฅผ ์ ์ ํ ์ค๊ณํด์ผ ํ ํ์๋ฅผ ๋๋ผ๋ฉฐ ์ต๋ํ ํด๋์ค ์ค๊ณ๋ฅผ ์ํ๋ค. ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ๋
sweeeetgoguma.tistory.com
๋ด๊ฐ ์งํํ๋ ๋ฐฉ์์ ๋ชจ๋๋ฆฌ์ ์ํคํ ์ณ์ด๋ค. msa๋ก์ ๋ณํ์ ์ด๋ ต์ง๋ง ํ๊ฒฝ ์์ฒด๋ฅผ ์ถฉ๋ถํ ๊ณ ๋ คํ ์ ์๋ค.
์ฐพ์๋ณด๋ ๋ฉํฐ ๋ชจ๋ ์ด๋ ๊ฐ๋ ์ด ์์๋ค. ๊ทธ๊ฒ์ ์ ์ฉ์์ผ ๋ณด๊ณ ์ ํ๋ค.
์ด๋ฉ์ผ ์ ์ก ์๋น์ค๋ฅผ ๋ค๋ฅธ ๋ชจ๋๋ก ์ด์ ํ ๊ฒฝ์ฐ ๋ณธ ์๋ฒ์ ๋ณ๊ฐ๋ก ์๋น์ค๋ฅผ ์ ๊ณตํ ์ ์๋ค.
=> ๋ณธ ์๋ฒ์ ๋ฉ์ผ ์๋ฒ๋ฅผ ๊ฐ๊ฐ ๋ชจ๋๋ก ๋ถ๋ฆฌํ๋ค
๊ทธ๋ผ ํ์๊ฐ์ ์์ฒญ์ ์ด๋ป๊ฒ ์๊ณ ์ด๋ฉ์ผ์ ๋ณด๋ด๋ ๊ฑธ๊น?
=> OutBox ํจํด์ ๋ํด์ ์์๋ณด์๋ค.
=> ์ฐธ์กฐ : https://daddyprogrammer.org/post/14068/database-migration-by-transactional-outbox/
Database Migration by Transactional Outbox Pattern
์ด์ ์ค์ต๊น์ง๋ ๋ฐ์ดํฐ ๋ง์ด๊ทธ๋ ์ด์ ์ ์ํด Database์์ ์ ๊ณตํ๋ binlog๋ DynamoDB/MongoDB์์ ์ ๊ณตํ๋ Change Stream์ ํตํด ๋ณ๊ฒฝ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ์ ์์์ต๋๋ค. ํ์ง๋ง ์ด๋ ๊ฒ ์์คํ ์ ์ผ๋ก ์ง์
daddyprogrammer.org
์ฝ๊ฒ ์์ฝํ์๋ฉด ๋ฉ์์ง ํ์ ๋น์ทํ ๊ฐ๋ ์ด๋ค.
์ด๋ค ์์ฒญ -> API ์๋ต -> ์์ฒญ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ OutBox ํ ์ด๋ธ์ ๊ด๋ จ ๋ฐ์ดํฐ ์ ์ฅ -> ํ ์ด๋ธ์ ๋ฐ๋ผ๋ณด๋ ๋ชจ๋์์ ์์ฒญ ๊ฑด ์์ฐจ์ฒ๋ฆฌ
=> ์ด๋ ๊ฒ ํด์ ํ๋์ ํธ๋์ญ์ ๋จ์๋ก ๋ฌถ์ผ๋ฉด ๋ถ์ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ํ๊ฒฝ์์ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ ๋ํ ๋ถ๋ถ์ด ํด์๊ฐ ๋๋ค.
=> ์ค์ผ์ฅด๋ฌ๋ฅผ ์ด์ฉ ์ ALO (At Least Once) ๋ ์ ์ฉ์ํฌ ์ ์๋ค.
์ฝ๋
/**
* ์ด๋ฉ์ผ์ ๋ฐ๊ณ ๊ด๋ จ ์ ๋ณด๋ฅผ DB์ ์ ์ฅ ํ ์์๋ฐ์ค ํจํด์ ์ํด ์ด๋ฒคํธ ๋ฐ์
* @param emailRequestDTO ์ด๋ฉ์ผ DTO
*/
@Transactional
public void saveEmailKey(UserEmailRequestDTO emailRequestDTO) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime expiredTime = now.plusMinutes(5);
EmailKey emailKey = toEmailKeyVO(expiredTime, emailRequestDTO.getUserEmail());
emailKeyMapper.insertEmailKey(emailKey);
EmailKey saved = emailKeyMapper.selectEmailKey(emailKey.getId());
applicationEventPublisher.publishEvent(
outBoxEventBuilder.createOutBoxEvent(EmailKeyCreated.builder()
.emailKeyId(saved.getId())
.emailKey(saved.getEmailKey())
.email(saved.getEmail())
.build())
);
}
์ด๋ฉ์ผ ์ธ์ฆ ์์ฒญ์ด ์๋ค. ๋งค๊ฐ๋ณ์๋ก DTO๋ฅผ ๋ฐ๋๋ค.
์ด๋ฉ์ผ์ ์ ์กํด์ผ ํ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํจ๋ค. (ApplicationEventPublisher ์ด์ฉ)
/**
* ์ด๋ฒคํธ ๋น๋ <br>
* ๋ฐ์๋ ์ด๋ฒคํธ๋ฅผ ๊ฐ๊ณต, payload๋ฅผ json ํํ๋ก ๋ณํ ํ ๊ฐ์ฒด๋ก ๋ฐํ
*/
@Component
@Slf4j
public class EmailEventBuilder implements OutBoxEventBuilder<EmailKeyCreated> {
private static final String EVENT_ACTION = "์ด๋ฉ์ผ";
@Override
public OutBoxEvent createOutBoxEvent(EmailKeyCreated domainEvent) {
JsonNode jsonNode = ObjectMapperUtil.getMapper().convertValue(domainEvent, JsonNode.class);
return new OutBoxEvent.OutBoxEventBuilder()
.aggregateId(domainEvent.getEmailKeyId())
.aggregateType(EmailKey.class.getSimpleName())
.eventType(domainEvent.getClass().getSimpleName())
.eventAction(EVENT_ACTION)
.payload(jsonNode.toString())
.build();
}
}
=> ์ด๋ฉ์ผ ๊ด๋ จ๋ OutBoxEmail ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ค๋ค.
/**
* ์์๋ฐ์ค ๊ฐ์ฒด DB์ ์ ์ฅ
*/
@Component
@RequiredArgsConstructor
public class OutBoxEventHandler {
private static final String ORDER = "์ฃผ๋ฌธ";
private static final String EMAIL = "์ด๋ฉ์ผ";
private static final String CART = "์ฅ๋ฐ๊ตฌ๋ ์ฃผ๋ฌธ";
private final OutBoxMapper outBoxMapper;
@EventListener
public void doOutBoxEvent(OutBoxEvent outBoxEvent) {
switch (outBoxEvent.getEventAction()) {
case EMAIL:
outBoxMapper.insertEmailOutBox(OutBox.builder()
.aggregateId(outBoxEvent.getAggregateId())
.aggregateType(outBoxEvent.getAggregateType())
.eventType(outBoxEvent.getEventType())
.payload(outBoxEvent.getPayload())
.build());
break;
=> ๋ง๋ค์ด์ง ๊ฐ์ฒด๋ฅผ ํ ๋๋ก OutBox ํ ์ด๋ธ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค.
/**
* ์ด๋ฉ์ผ ์ค์ผ์ฅด๋ฌ <br>
* 10์ด ๊ฐ๊ฒฉ์ผ๋ก outbox ํ
์ด๋ธ ๋ฐ์ดํฐ ์กฐํ ํ ๋ฉ์ผ ๋ฐ์ก
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class EmailSendScheduler {
private static final long EXPIRE_DURATION = 60 * 5L;
private final RedisService redisService;
private final OutBoxEmailMapper outBoxMapper;
private final SendMailService sendMailService;
private final MailContentService mailContentService;
@Transactional
@Scheduled(cron = "0/10 * * * * ?")
public void schedulingValidNumberEmail() {
ObjectMapper objectMapper = new ObjectMapper();
log.info("์ด๋ฉ์ผ ์ ์ก ์ค...");
List<OutBox> outBoxList = outBoxMapper.selectAllOutBox();
if (!outBoxList.isEmpty()) {
List<Long> completedList = new LinkedList<>();
outBoxList.forEach(outBox -> {
String payload = outBox.getPayload();
try {
JsonNode jsonNode = objectMapper.readTree(payload);
String userEmail = jsonNode.get("email").asText();
String authKey = jsonNode.get("email_key").asText();
MailDTO content = mailContentService.createMailContent(payload);
sendMailService.sendEmail(content);
completedList.add(outBox.getId());
redisService.setDataExpire(userEmail, authKey, EXPIRE_DURATION);
} catch (MailException e) {
log.error("๋ฉ์ผ ๋ฐ์ก ์ค ์ค๋ฅ ๋ฐ์ . . .");
} catch (FailedPayloadConvertException | JsonProcessingException e) {
log.error(e.getMessage());
}
});
if (!completedList.isEmpty()) {
outBoxMapper.deleteAllById(completedList);
}
}
}
=> ์ค์ผ์ฅด๋ฌ๊ฐ OutBox ํ ์ด๋ธ์ ํญ์ ๋ฐ๋ผ๋ณด๊ณ ์๋ค. ๋ฐ์ดํฐ๊ฐ ์์ ๋ ์ ์ฅ๋ ์ ๋ณด๋ฅผ ํตํด ์ด๋ฉ์ผ์ ์ ์ก์ํจ๋ค. ๊ทธ ํ Redis์ ์ธ์ฆ๋ฒํธ๋ฅผ ์ ์ฅํ๋ค. -> ์ธ์ฆ์ ์ํจ
์ด๋ ๊ฒ ํ๋ฉด ์ธ์ฆ๋ฒํธ ๋ฐ์ดํฐ์ ๋ํ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ ๋ถ์ฐ ์ฒ๋ฆฌ ํ๊ฒฝ์์๋ ๋ณด์ฅ๋ฐ์ ์ ์๋ค. ํ๋์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ฌ์๊ธฐ ๋๋ฌธ์ด๋ค.
2. ์ฃผ๋ฌธ ๊ฑด์ ๋ํ ์ํ ์ฌ๊ณ ์กฐํ
=> ์ํ ์ฃผ๋ฌธ ์ ์ฌ๊ณ ํ์ ์ ๋จผ์ ํด์ผ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ฒฐ์ ๊ฐ ๊ฐ๋ฅํ๊ฒ ํด์ผ ํ๋ค. ๋ฉํฐ ์ค๋ ๋ ํ๊ฒฝ์ ์๊ฐํด๋ณด์. ๋์์ ์ ์ํ ์ ์ ๋ค์ด ๋๋ ๋๋ ์ธ๊ธฐ๊ฐ ๋ง์ ์ํ์ ์ฃผ๋ฌธํ ์ ์๋ค. ํ์ง๋ง ์ด ์ํ์ ์ฌ๊ณ ๊ฐ ํ์ ์ ์ด๋ค. ์ฌ์ง์ด 1๊ฐ๊ฐ ๋จ์์์ ๋ 2๋ช ์ด ๋์์ ์ฃผ๋ฌธ์ ํ ์ ์๋ค. ๋ ๊ฐ์ ์ค๋ ๋๊ฐ ๋์์ ์กฐํ๋ฅผ ํ๋ ๊ฒ์ด๋ค.
2022.06.02 ใDB ๋์์ฑ ๋ฌธ์ ใ
์๋ฐ ๊ธฐ๋ฐ ์น ํ๋ก๊ทธ๋จ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฉํฐ์ค๋ ๋ฉ์ ๊ธฐ๋ฐ์ผ๋ก ํ๊ธฐ ๋๋ฌธ์, ๋์์ฑ ๊ด๋ จ ๋ฌธ์ ๋ฅผ ์ ํด๊ฒฐํด์ผ ํ๋ค๊ณ ๋ค์๋ค. ์ด๋ฒ์ ๋ด๊ฐ ํ๋ ์ผํ๋ชฐ ํ๋ก์ ํธ์์๋ ๊ทธ ์ด์๊ฐ ๋ฑ ํฐ์ก๋ค. ์๋ฅผ
sweeeetgoguma.tistory.com
๋ด DBMS : Mysql
=> ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฅ์คํธ ํค๋ฝ์ ์ง์ํ๊ณ Repeatable Read ๊ฒฉ๋ฆฌ ์์ค์ด๋ฏ๋ก ๋์์ฑ ๋ฌธ์ ๋ฅผ ์ผ์ผํค์ง ์์ง๋ง,
๊ธฐ์กด์ ๋ ์ฝ๋ ๋ฝ์ ์ถ๊ฐ๋ก ๊ฑธ์ด์ ํด๊ฒฐํ์ง๋ง, ํธ๋ํฝ์ด ๋ง์ด ๋ชฐ๋ฆด ๊ฒฝ์ฐ ์ฑ๋ฅ ์ ์๋๊ฐ ๋๋ฌด ๋๋ ค์ง ์ ์๋ค.
๊ทธ๋์ ์ญ์ OubBox ํจํด์ ์ ์ฉ์์ผฐ๋ค.
์ฝ๋
List<OrderCreated> orderCartList =
getOrderCreatedList(productNoArr, productNameArr, productStockArr, totalAmount);
/* ์ฌ๊ณ ํ์ธ ์ด๋ฒคํธ ๋ฐ์ */
applicationEventPublisher.publishEvent(
outBoxEventCartBuilder.createOutBoxEvent(orderCartList)
);
์ฅ๋ฐ๊ตฌ๋๋ก ์ค๋ช ํ๊ฒ ๋ค. ์์ ๋์ผ
์ด๋ฒคํธ ๋ฐ์
/**
* ์ฅ๋ฐ๊ตฌ๋ ์ฃผ๋ฌธ ์์๋ฐ์ค ์ด๋ฒคํธ ๊ฐ์ฒด ๋น๋
*/
@Component
@Slf4j
public class CartOrderEventBuilder implements OutBoxEventBuilder<List<OrderCreated>> {
private final static String EVENT_ACTION = "์ฅ๋ฐ๊ตฌ๋ ์ฃผ๋ฌธ";
@Override
public OutBoxEvent createOutBoxEvent(List<OrderCreated> domainEvent) {
long firstId = domainEvent.get(0).getItemNumber();
return new OutBoxEvent.OutBoxEventBuilder()
.aggregateId(firstId)
.aggregateType(List.class.getSimpleName())
.eventType(domainEvent.getClass().getSimpleName())
.eventAction(EVENT_ACTION)
.cartList(domainEvent)
.build();
}
}
=> OutBox ์ด๋ฒคํธ ๊ฐ์ฒด ์์ฑ (List ์ ๋ณด ๋ด์์) ๋ฐ ๋ฐํ
/**
* ์ฃผ๋ฌธ ์์ ๋ฐ์ค ์กฐํ ์ค์ผ์ค๋ฌ <br>
* 10์ด๋ง๋ค ์ฃผ๋ฌธ ๊ฑด์ ๋ํ ์ฌ๊ณ ํ์
์ ํ๋ค.
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class CheckStockScheduler {
private final OutBoxMapper outBoxMapper;
private final ProductMapper productMapper;
private final KakaoPayService kakaoPayService;
@Transactional
@Scheduled(cron = "0/10 * * * * ?")
public void schedulingCheckStock() {
ObjectMapper objectMapper = new ObjectMapper();
log.info("์ฌ๊ณ ํ์ธ ์ค . . .");
List<OutBox> outBoxList = outBoxMapper.selectAllOrderOutBox();
if (!outBoxList.isEmpty()) {
List<Long> completedList = new LinkedList<>();
outBoxList.forEach(outBox -> {
String payload = outBox.getPayload();
try {
JsonNode jsonNode = objectMapper.readTree(payload);
String itemName = jsonNode.get("item_name").asText();
if (productMapper.selectStockOfProduct(itemName) < Integer.parseInt(jsonNode.get("quantity").asText())) {
kakaoPayService.changeStockFlag(false);
}
completedList.add(outBox.getId());
} catch (JsonProcessingException e) {
log.error(e.getMessage());
}
});
if (!completedList.isEmpty()) {
outBoxMapper.deleteAllById(completedList);
}
}
}
}
=> ์ค์ผ์ฅด๋ฌ๋ฅผ ํตํด ์ฃผ๊ธฐ์ ์ผ๋ก ์กฐํ ๋ฐ ์ฌ๊ณ ํ์
์ฌ๊ณ ํ์ ์ ๋ฌธ์ ๊ฐ ์๊ธธ ์ flag ๋ ผ๋ฆฌ ๋ณ์๋ฅผ false๋ก ๋ฐ๊พผ๋ค.
public void changeStockFlag(boolean flag) {
this.exceptionFlag = flag;
}
=> ํ๋๊ทธ ๋ณ๊ฒฝ ๋ฉ์๋
/* ์ฌ๊ณ ํ์ ์์ธ ๋ฐ์ */
if (!this.exceptionFlag) {
throw new RunOutOfStockException();
}
ํธ๋์ญ์ ์๋น์ค ๋ด์ ์กฐ๊ฑด๋ฌธ (์๋ฌ ๋ฐ์ ์ ๋ชจ๋ ๋กค๋ฐฑ๋๋ค)
@ExceptionHandler(RunOutOfStockException.class)
public ResponseEntity<String> runOutOfException(RunOutOfStockException e) {
return ResponseEntity.ok().body(e.getMessage());
}
=> ์์ธ๋ฅผ ๋์ ธ ExceptionHandler๋ก ์ฒ๋ฆฌ
๊ฒฐ๋ก
์์ ๋ฐ์ค ํจํด์ ํ์ฉํด์ ๋ฐ์ดํฐ ๋ถ์ฐ ์ฒ๋ฆฌ ํ๊ฒฝ์์ ์ ํฉ์ฑ ๋ฌธ์ ์ ALO๋ฅผ ์ ์ฉ์์ผ ๋ฆฌํฉํฐ๋ง์ ์งํํ๋ค. ์ฒ์์์ผ ์ด๋ ต์ง ํ๋ค ๋ณด๋ ๋๋ฆ ๊ด์ฐฎ๊ฒ ์ดํดํ ๊ฒ ๊ฐ๋ค.
ํ์คํ ๋ค์ํ ํ๊ฒฝ์ ๊ณ ๋ คํ๋ฉฐ ์ฝ๋๋ฅผ ์์ฑํด์ผ ๋ ๊ฒ ๊ฐ๋ค. ๋ ํ์ค์ ๊ฐ๊น์ด ์ฝ๋๋ฅผ ์ง๋ ค๊ณ ๋ ธ๋ ฅํด๋ณด์!
์์ผ๋ก์ ๋ฆฌํฉํฐ๋ง๋ ์์ค ๋๊ฒ ํด๋ณด์์!!!!
๊นํ : https://github.com/GroovyArea/MyChickenBreastShop
'๐ Spring Framework > Spring Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
ใํ ์คํธ ์ฝ๋ & Spring REST Docsใ (0) | 2022.06.20 |
---|---|
OutBox Pattern & Saga Pattern & Transaction (0) | 2022.06.13 |
2022.06.07 ใํ๋ก์ ํธ ์ค๊ฐ ์ ๊ฒใ (0) | 2022.06.07 |
2022.06.02 ใDB ๋์์ฑ ๋ฌธ์ ใ (0) | 2022.06.02 |
2022.06.01 ใ๊ฒฐ์ API - Ver.2ใ (2) | 2022.06.01 |