๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“• Spring Framework/Spring Project

OutBox Pattern & Saga Pattern & Transaction

by GroovyArea 2022. 6. 13.
์ง€๋‚œ๋ฒˆ ํฌ์ŠคํŒ…์„ ์ดํ›„๋กœ 3์ผ๊ฐ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ๊ด€ํ•œ ๊ณต๋ถ€๋ฅผ ํ•˜๋ฉฐ ๋ฆฌํŒฉํ„ฐ๋ง์„ ์ง„ํ–‰ํ–ˆ๋‹ค. 
๋ฐ์ดํ„ฐ ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ์˜ ํŠธ๋žœ์žญ์…˜์˜ ๊ณ ๋ ค๋„ ์ถฉ๋ถ„ํžˆ ์ค‘์š”ํ•œ ์„ค๊ณ„ ๊ฐ™๋‹ค. ๊ทธ ๋ฆฌํŒฉํ„ฐ๋ง ๊ณผ์ •์„ ์ •๋ฆฌํ•ด๋ณด๊ฒ ๋‹ค.

 

https://sweeeetgoguma.tistory.com/entry/%E3%80%8COutBox-Pattern%E3%80%8D-%ED%99%9C%EC%9A%A9

 

ใ€ŒOutBox Patternใ€ ํ™œ์šฉ

https://github.com/GroovyArea/MyChickenBreastShop/wiki/Version-1 GitHub - GroovyArea/MyChickenBreastShop: ChikenBreastShop API with Spring boot ChikenBreastShop API with Spring boot. Contribute to G..

sweeeetgoguma.tistory.com

> ์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ ์ •๋ฆฌํ–ˆ๋‹ค์‹œํ”ผ ๊ธฐ๋Šฅ์€ ์™„๋ฃŒ๊ฐ€ ๋˜์—ˆ๋‹ค

 

๋ฌธ์ œ์ ์„ ์ •๋ฆฌํ•ด๋ณด๊ฒ ๋‹ค. 

 

1. ์•„์›ƒ ๋ฐ•์Šค ํŒจํ„ด์„ ์ด์šฉ ํ›„ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ์˜ ๋ฌธ์ œ

 

์–ผํ• ๋ณด๋ฉด ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

๋ฐ”๋กœ ์•„์›ƒ๋ฐ•์Šค ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ์˜ ์ฒ˜๋ฆฌ๊ฐ€ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ๋‹จ์œ„๋กœ ๋ฌถ์—ฌ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. 

๋ฆฌ์ŠคํŠธ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์žˆ์–ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ๋ชจ๋‘ ๋กค๋ฐฑ ํ›„ ๋‹ค์‹œ ๋ฌดํ•œ ๋ฐ˜๋ณต์„ ํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค. 

 

์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?

 

@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 -> {
            outBoxStockCheck(objectMapper, completedList, outBox);
        });
        if (!completedList.isEmpty()) {
            outBoxMapper.deleteAllById(completedList);
        }
    }
}

@Transactional
void outBoxStockCheck(ObjectMapper objectMapper, List<Long> completedList, OutBox 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());
        outBoxMapper.insertOrderOutBox(outBox);
    }
}
@Scheduled(cron = "0/10 * * * * ?")
public void schedulingValidNumberEmail() {
    ObjectMapper objectMapper = new ObjectMapper();

    log.info("์ด๋ฉ”์ผ ์ „์†ก ์ค‘...");

    List<OutBox> outBoxList = outBoxMapper.selectAllEmailOutBox();
    if (!outBoxList.isEmpty()) {
        List<Long> completedList = new LinkedList<>();
        outBoxList.forEach(outBox -> {
            validateEmailNumber(objectMapper, completedList, outBox);
        });
        if (!completedList.isEmpty()) {
            outBoxMapper.deleteAllById(completedList);
        }
    }
}

@Transactional
void validateEmailNumber(ObjectMapper objectMapper, List<Long> completedList, OutBox 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("๋ฉ”์ผ ๋ฐœ์†ก ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ . . .");
        outBoxMapper.insertOutBox(outBox);
    } catch (FailedPayloadConvertException | JsonProcessingException e) {
        log.error(e.getMessage());
        outBoxMapper.insertOutBox(outBox);
    }
}

=> ํŠธ๋žœ์žญ์…˜ ๋‹จ์œ„๋ฅผ ์„ธ๋ถ„ํ™”์‹œ์ผฐ๋‹ค. ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ๋‹ค์‹œ ์•„์›ƒ๋ฐ•์Šค ํ์˜ ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๋˜๋ฉด ๋ฌธ์ œ ์žˆ๋Š” ๋ ˆ์ฝ”๋“œ ์ดํ›„์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ํ์—์„œ ๊บผ๋‚ด์ ธ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค. 

 

2. SAGA  ํŒจํ„ด

SAGA ํŒจํ„ด ๊ตฌํ˜„ ์‹œ ๊ณ ๋ คํ•ด์•ผ ํ•  ์‚ฌํ•ญ?

- ์ž ์žฌํ•˜๋Š” ์ผ์‹œ์ ์ธ ์˜ค๋ฅ˜๋“ค์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋ฉฑ๋“ฑ์„ฑ ์ œ๊ณต์ด ํ•„์š”

- Work Flow๋ฅผ ํ•ญ์ƒ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ์ถ”์ ํ•˜๋Š” ๊ด€์ฐฐ ๊ฐ€๋Šฅ์„ฑ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. 

 

 

3. OutBox ํŒจํ„ด

๋ฐœ์ƒ ๊ฐ€๋Šฅํ•œ ๋ฌธ์ œ?

- ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ ๋ณด์žฅ ๋ฌธ์ œ

- ๋ฉ”์„ธ์ง€์˜ ์ „๋‹ฌ ๋ณด์ฆ ์ˆ˜์ค€์„ ์ž˜ ๋”ฐ์ ธ์•ผ ํ•œ๋‹ค.

 

 

4. ์Šคํ”„๋ง์—์„œ ๋Œ€ํ‘œ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” AOP

- @Transactional์ด ๋Œ€ํ‘œ์ 

- aop์˜ ์ถ”๊ฐ€์ ์ธ ๊ณต๋ถ€๊ฐ€ ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

 

 

๋ฐ˜์‘ํ˜•