ํ๋ก์ ํธ/Airbnb Clone (13) ์ธ๋ค์ผํ ๋ฆฌ์คํธํ Github Actions ์ผ๋ก CI ํ ์คํธ ์๋ํ ์ค ๊ฒช์ ์ด์ ์ ๋ฆฌ (Embedded Redis, Profile, Spring Rest Docs) 210๊ฐ์ ํ ์คํธ ์ฝ๋์ 99% ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง์์ ์์ฝ ํ๋ซํผ์ ๊ฐ๋ฐํ๋ฉฐ ์ด 210์ฌ ๊ฐ์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ณ , 99% ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ฌ์ฑํ์ต๋๋ค. ํ์ง๋ง ์ฝ๋์ ๋ณ๊ฒฝ ์ฌํญ์ด ์์ ๋๋ง๋ค ์ ์ฒด ํ ์คํธ๋ฅผ ์๋์ผ๋ก ์คํํด์ผ ํ๊ณ , ์ด๋ ์ ์ ๋ถํธํ ์์ ์ด ๋์์ต๋๋ค. ๊ทธ๋์ CI/CD ํด์ธ GitHub Actions๋ฅผ ๋์ ํ์ฌ push ๋๋ pull_request๊ฐ ๋ฐ์ํ ๋ ์๋์ผ๋ก ํ ์คํธ๊ฐ ์คํ๋๋๋ก ์ค์ ํ์ต๋๋ค. GitHub Actions๋ฅผ ์ ํํ ์ด์ GitHub Actions๋ฅผ ์ ํํ ์ด์ ๋ ๋จ์ํฉ๋๋ค.Jenkins์ฒ๋ผ ๋ณ๋์ ์๋ฒ๋ฅผ ๊ตฌ์ถํ ํ์๊ฐ ์์ผ๋ฉฐ,๋จ์ํ .github/workflows ๋๋ ํ ๋ฆฌ์ YAML ํ์ผ๋ง ์์ฑํ๋ฉด ๋ฐ๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.๋ํ Groovy์ ๊ฐ์ ์๋ก์ด ๋ฌธ๋ฒ์.. ํธ๋์ญ์ ๋กค๋ฐฑ ์ ๊ฒฐ์ ์ทจ์ ์์ฒญํ๊ธฐ - @TransactionalEventListener & ApplicationEventPublisher ๋ชจํน ์ด์ ๊ฒฐ์ ํ๋ก์ธ์ค๋ฅผ ์ค๊ณํ๋ฉด์, ๊ธฐ์กด์๋ try-catch๋ฅผ ํ์ฉํด ํธ๋์ญ์ ๋ด๋ถ์์ ๋ฐ์ํ๋ ๋ฐํ์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๊ฒฐ์ ์ทจ์ ์์ฒญ์ ๋ณด๋์ต๋๋ค. ๊ทธ๋ฌ๋, ์ด์ ๋ํ ํ๊ณ๋ฅผ ๋๊ผ๊ณ ํธ๋์ญ์ ๋กค๋ฐฑ ์ ๊ฒฐ์ ์ทจ์ ์์ฒญ์ ๋ช ํํ๊ณ ์ง๊ด์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด @TransactionalEventListener๋ฅผ ๋์ ํ์ต๋๋ค. ๊ธฐ์กด try-catch ๋ฐฉ์์ ํ๊ณ try-catch์์๋ ์ฃผ๋ก DB ์์ธ(์: DataIntegrityViolationException)๋ ๋น์ฆ๋์ค ๋ก์ง์์ ๋ฐ์ํ๋ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๊ฒฐ์ ์ทจ์ ์์ฒญ์ ๋ณด๋์ต๋๋ค. ํ์ง๋ง, try-catch๋ฅผ ์ฌ์ฉํ๋ฉด ์์ธ๋ฅผ ์ ์์ ์ธ ํ๋ฆ์ผ๋ก ์ ์ดํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ฒ ๋ฉ๋๋ค. ์ด๋ "ํธ๋์ญ์ ์ด ๋กค๋ฐฑ๋์์ ๋ ๊ฒฐ์ ์ทจ์๋ฅผ ์ํํ๋ค"๋ผ๋ ์๋ฏธ๋ฅผ ์ ํํ ํํํ์ง.. [ํธ๋ฌ๋ธ์ํ ] RestTemplate ๋ก๊น ์ค 404 ์๋ต์ ๋ํ FileNotFoundException ํด๊ฒฐ ๊ฒฐ์ ์น์ธ POST ์์ฒญ ์ RestTemplate ๋ก๊ทธ์์ ๋ฐ์ํ ์ค๋ฅ ๊ฒฐ์ ๊ธฐ๋ฅ์ ๊ฐ๋ฐํ๋ฉด์, ๊ฒฐ์ ์น์ธ ์์ฒญ์ ๋ณด๋ด๋ RestTemplate์ ํ์์์์ ์ ์ฉํ๊ณ , ์์ฒญ๊ณผ ์๋ต์ ๋ํ ๋ก๊น ์ ์ถ๊ฐํ๊ณ ์ ํ์ต๋๋ค. Baeldung์ "RestTemplage Logging" ๊ณผ ๋ง๋๋ ๊ฐ๋ฐ์๋์ ๋ก๊น ์ ์ฐธ๊ณ ํด์ RestTemplate ์ ๋ก๊ทธ๋ฅผ ์ถ๊ฐ ํ์ง๋ง ์๋ต์ ๋ฐ์ ๋, FileNotFoundException ์ด ๋ฐ์ํ๋ฉฐ ์๋์ ๊ฐ์ ์คํ ํธ๋ ์ด์ค๋ฅผ ํ์ธํ์ต๋๋ค.java.io.FileNotFoundException: https://api.tosspayments.com/v1/payments/confirmjava.base/sun.net.www.protocol.http.HttpURLConnection.. @Async ์ด๋ฉ์ผ ์ ์ก ๊ณ ๋ํ: ์ฌ์๋, ์์ธ ์ฒ๋ฆฌ, ํ ์คํธ ์ธ์ฆ ์ด๋ฉ์ผ ์ ์ก ์, ๊ฐํน ๋คํธ์ํฌ ์ค๋ฅ๋ก ์ธํด ๋ฉ์ผ์ด ์ ์์ ์ผ๋ก ์ ์ก๋์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํฉ๋๋ค.์ด๋ฌํ ์ํฉ์์ ์ฌ์ฉ์์๊ฒ ์ฆ์ ์ค๋ฅ๋ฅผ ์๋ฆฌ๋ ๋์ , ์ฌ์๋ ๋ก์ง์ ํตํด ์ด๋ฉ์ผ ์ ์ก ์ฑ๊ณต ๊ฐ๋ฅ์ฑ์ ๋์ด๋ ๊ฒ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์ํ๋ ์ค์ํ ์์๋ผ๊ณ ํ๋จํ์ต๋๋ค. ๊ทธ๋์, ์ฌ์๋ ์ถ๊ฐ ์ ๊ณ ๋ คํ ๊ฒ๋ค์ ๋ค์๊ณผ ๊ฐ์ ๋ชฉ์ฐจ๋ก ์๊ฐํฉ๋๋ค.์ฌ์๋(@Retryable) ์กฐ๊ฑด ์ค์ ์ฌ์๋ ์์ธ์ ์ฐ๋ ๋ ํ ์์ธ๋ฅผ ๊ตฌ๋ถํด์ ์ฒ๋ฆฌํ๊ธฐ์ฌ์๋ ๋ฐ ๋ณต๊ตฌ๊ฐ ์ ๋์๋์ง ํ ์คํธํ๊ธฐ* ๋น๋๊ธฐ์์ ์ฌ์๋ ๋ก์ง ๊ตฌํ์ ์ํด Baeldung(async-retry)๋ฅผ ์ฐธ๊ณ ํ์ต๋๋ค. ์ฌ์๋(@Retryable) ์กฐ๊ฑด ์ค์ ๋จผ์ , ์ด๋ฉ์ผ ์ ์ก์ ์ฌ์๋ ์กฐ๊ฑด์ ์ค์ ํ๊ธฐ ์ํด Spring Mail ๊ด๋ จ ์์ธ๋ฅผ ์๊ฐํฉ๋๋ค.MailAuthentic.. ์ด๋ฉ์ผ ์ ์ก์ Async-NonBlocking ์ฒ๋ฆฌํ๊ธฐ: ์ฐ๋ ๋ ํ ์ค์ ๊ณผ API ์ต๋ ์๋ต ์๋ 1.4์ด → 0.01์ด ๊ฐ์ @Async ๋ฅผ ๋์ ํ๋ฉฐ ์ปจํ ์คํธ ์ค์์นญ๊ณผ ์ฐ๋ ๋ ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์กฐ ์ง์์ ๋ฐํ์ผ๋ก ์ฐ๋ ๋ ํ ์ค์ ์ ํ์ตํ๊ณ ์ ์ฉํ๋ ๊ณผ์ ์ ์๊ฐํ๋ฉฐ Jmeter ๋ฅผ ํตํด API ์๋ต ์๋๊ฐ ๊ฐ์ ๋์๋์ง ํ์ธํ๋ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ ๋ชฉ์ฐจ๋ก ์๊ฐํฉ๋๋ค.์ด๋ฉ์ผ ์ ์ก์ ๋น๋๊ธฐ๋ก ๊ฒฐ์ ํ ์ด์ ์ฐ๋ ๋ ํ ์ฌ์ฉ ์ฌ๋ถ ๊ณ ๋ ค์ฐ๋ ๋ ํ ์ค์ ๊ณ ๋ ค ์ฌํญ์ฐ๋ ๋ ํ ์ค์ ThreadPoolTaskExecutor ๋์ ๋ฐฉ์์ฐ๋ ๋ ํ ์ค๋ฅ ์ฒ๋ฆฌ ์ค์ Jmeter ๋ก @Async ๋์ ์ ์ฉ ์ ๊ณผ ํ์ API ์๋ต ์๊ฐ์ ๋น๊ต@Async ์ฌ์ฉ ์ ์ฃผ์์ ์ด๋ฉ์ผ ์ ์ก์ ๋น๋๊ธฐ๋ก ๊ฒฐ์ ํ ์ด์ ์์ด๋น์ค๋น ํด๋ก ํ๋ก์ ํธ์์๋ ์ด๋ฉ์ผ ์ธ์ฆ ๊ธฐ๋ฐ์ ํ์๊ฐ์ /๋ก๊ทธ์ธ์ ์งํํ๊ณ ์์ต๋๋ค. ์ด๋ฉ์ผ ์ ์ก์๋ Spring Mail์ JavaMailSender๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฉฐ.. [๊ฒฐ์ ์น์ธ ํ๋ก์ธ์ค ์ค๊ณ] ๊ฒฐ์ ์น์ธ ์์ฒญ๊ณผ DB ํธ๋์ญ์ ์ ์์๊ฐ์ ์ฅ๋จ์ ์ ๋น๊ต ๋ถ์ ์ฌ์ด๋ ํ๋ก์ ํธ ์ค, ์์ ์์ฝ์ ํ ๋์ ๊ฒฐ์ ํ๋ก์ธ์ค๋ฅผ ๊ตฌํ์ค์ด์์ต๋๋ค. ์์ฝ์ ํ์ ํ ๋, ๊ฒฐ์ ํ๋ก์ธ์ค๋ ๋ค์ ์ฌ์ง์ฒ๋ผ ์ฌ๋ฌ ํ๋ก์ธ์ค๋ก ๋๋์ด ์ง๋๋ค. ์ด ๋, ์ ๊ฐ ๊ณ ๋ คํ ๊ฒ์ ์ด 5๊ฐ์ง์ด๋ฉฐ ์ด์ ๋ํด ์ค๋ช ์ ์งํํฉ๋๋ค. ๊ฒฐ์ ํ๋ก์ธ์ค์ ํธ๋์ญ์ ์ค์ ๋ฒ์'๊ฒฐ์ ์น์ธ์ ์ํ PG์ฌ API ํธ์ถ'๊ณผ ํธ๋์ญ์ ์ ์์'๊ฒฐ์ ์น์ธ์ ์ํ PG์ฌ API ํธ์ถ'์ ๋๊ธฐ/๋น๋๊ธฐ ์ค ์ ํ ๋์์ฑ ์ ์ด ๋ฐฉ๋ฒ์์ก ๋ถ์กฑ์ผ๋ก ์ธํ ๊ฒฐ์ ์น์ธ ์คํจ๋ ํธ๋์ญ์ ๋กค๋ฐฑ ์ ๋ฐ์ํ๋ ๊ฒฝ์ฐ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊ฒ์ธ์ง 1. ๊ฒฐ์ ํ๋ก์ธ์ค์ ํธ๋์ญ์ ์ค์ ๋ฒ์'๊ฒฐ์ ์น์ธ์ ์ํ PG์ฌ API ํธ์ถ' ์ ๋คํธ์ํฌ๋ฅผ ํตํด PG์ฌ์๊ฒ POST ์์ฒญ์ ํตํด ๊ฒฐ์ ๋ฅผ ์น์ธ ๋ฐ๋ ๊ณผ์ ์ผ๋ก ํธ๋์ญ์ ์ ํฌํจ๋์ด ํธ๋์ญ์ ์ด ๊ธธ์ด์ง๊ฒ ๋๋ค๋ฉด ๋ค์๊ณผ ๊ฐ.. [๋ก๊ทธ์ธ ๋ฐ ์ธ๊ฐ] Interceptor์์ ArgumentResolver๋ก ๋ฆฌํฉํ ๋งํ๊ธฐ [์๋ก ]์์ด๋น์ค๋น ํด๋ก ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ, ๋ก๊ทธ์ธ ํ์๋ง '์์ ๋ฑ๋ก' ๊ณผ '์์ ์์ฝ' ์ ํ ์ ์๊ธฐ ์ํด(์ธ๊ฐ๋ฅผ ์ฃผ๊ธฐ ์ํด) ๋ก๊ทธ์ธํ ํ์์ ๋์์ผ๋ก ์ธ์ ์ ์ฌ์ฉํด์ stateful ํ๊ฒ ๊ตฌํ ํ์ต๋๋ค.@PostMapping("/login") public ApiResponse login(@RequestBody @Valid MemberLoginRequest request, HttpSession httpSession) { memberService.login(request.toServiceRequest()); // ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด httpSession.setAttribute(LOGIN_MEMBER, true.. [ํธ๋ฌ๋ธ ์ํ ] ํ ์คํธ๋ก ์์๋ณด๋ ์ปจํธ๋กค๋ฌ์์ HttpSession ์ฌ์ฉ ์ ์ฃผ์์ Junit5 ๋ฐ Mock ์ ๋ํด ์ค๋ช ๋๋ฆฌ์ง ์๋ ์ ์ํด ๋ถํ๋๋ฆฝ๋๋ค.ํ ์คํธ ์ฝ๋๋ณด๋ค๋ ์ปจํธ๋กค๋ฌ์์ HttpSession ์ ์ฌ์ฉํ ๋์ ์ฃผ์์ ์ ์์๋ณด๋ ์๊ฐ์ ๊ฐ์ ธ๊ฐ์ จ์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค. ์ปจํธ๋กค๋ฌ์์ ํ๋ผ๋ฏธํฐ๋ก HttpSession ์ ์ฃผ์ ๋ฐ์์ ์ฌ์ฉํ ๋ ์ฃผ์ํ ์ ์ด ์์ต๋๋ค. ์ ์ฃผ์ํด์ ์ฌ์ฉํด์ผ ํ๋์ง HttpSession ๊ณผ HttpServletRequest.getSession() ์ ์ฐจ์ด๋ฅผ ํ ์คํธ ์ฝ๋๋ก ์์๋ณด๊ฒ ์ต๋๋ค. โญ HttpSession ๋ฅผ ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ก ์ฆ์ ์ฃผ์ ๋ฐ๊ธฐ์ฝ๋ ์ค๋ช - ํ์์ด ์ธ์ฆ ์ฝ๋๋ฅผ ์๋ชป ์ ๋ ฅํด์ ์ด๋ฉ์ผ ๊ฒ์ฆ์ ์คํจํ๋ฉด if ๋ฌธ์ด ๊ฑฐ์ง์ด ๋ฉ๋๋ค.- Session ์ ๊ฐ์ ์ค์ ํ์ง ์๊ณ , ๋ฉ์๋๊ฐ ์ข ๋ฃ๋ฉ๋๋ค.@GetMapping("/email/authenticat.. ์ด์ 1 2 ๋ค์