๐ ๋๋ฉ์ธ ์ฃผ๋ ๊ฐ๋ฐ ์์ํ๊ธฐ: DDD ํต์ฌ ๊ฐ๋ ์ ๋ฆฌ๋ถํฐ ๊ตฌํ๊น์ง
๋๋ฉ์ธ ์ฃผ๋ ๊ฐ๋ฐ ์์ํ๊ธฐ: DDD ํต์ฌ ๊ฐ๋ ์ ๋ฆฌ๋ถํฐ ๊ตฌํ๊น์ง ์ฑ ์ ์ฝ๊ณ ๋ด์ฉ์ ์์ฃผ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํ ๊ธ์ ๋๋ค. ์ฑ ์๋ ์์ธํ ์ค๋ช ๊ณผ ์์ ๊ฐ ๋ง์ผ๋ ๊ผญ ๊ตฌ์ ํด์ ์ฝ๋๊ฒ์ ์ถ์ฒํฉ๋๋ค~๐
1. ๋๋ฉ์ธ ๋ชจ๋ธ ์์ํ๊ธฐ
1.1 ๋๋ฉ์ธ์ด๋?
์ํํธ์จ์ด๋ก ํด๊ฒฐํ๊ณ ์ ํ๋ ๋ฌธ์ ์์ญ์ ๋๋ฉ์ธ์ด๋ผ ํ๋ค.
1.2 ๋๋ฉ์ธ ์ ๋ฌธ๊ฐ์ ๊ฐ๋ฐ์ ๊ฐ ์ง์๊ณต์
์ ๋ฌธ๊ฐ๋ ๊ด๋ จ์๊ฐ ์๊ตฌํ ๋ด์ฉ์ด ํญ์ ์ฌ๋ฐ๋ฅธ ๊ฒ์ ์๋๋ค. ๊ทธ๋์ ๊ฐ๋ฐ์๋ ์๊ตฌ์ฌํญ์ ์ดํดํ ๋ ์ ์ด๋ฐ ๊ธฐ๋ฅ์ ์๊ตฌํ๋์ง ๋๋ ์ค์ ๋ก ์ํ๋๊ฒ ๋ฌด์์ธ์ง ์๊ฐํ๊ณ ์ ๋ฌธ๊ฐ์ ๋ํ๋ฅผ ํตํด ์ง์ง๋ก ์ํ๋ ๊ฒ์ ์ฐพ์์ผ ํ๋ค.
1.3 ๋๋ฉ์ธ ๋ชจ๋ธ
๋๋ฉ์ธ ๋ชจ๋ธ์ ํน์ ๋๋ฉ์ธ์ ๊ฐ๋ ์ ์ผ๋ก ํํํ ๊ฒ์ด๋ค. ๋๋ฉ์ธ ๋ชจ๋ธ์ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ๊ด๊ณ์๋ค์ด ๋์ผํ ๋ชจ์ต์ผ๋ก ๋๋ฉ์ธ์ ์ดํดํ๊ณ ๋๋ฉ์ธ ์ง์์ ๊ณต์ ํ๋๋ฐ ๋์์ด ๋๋ค. ๋๋ฉ์ธ ๋ชจ๋ธ์ ์ดํดํ๋ ๋ฐ ๋์์ด ๋๋ค๋ฉด ๊ฐ์ฒด๊ธฐ๋ฐ ํํ์ด๋ , ํจ์ ํํ์ด๋ ํํ ๋ฐฉ์์ด ๋ฌด์์ธ์ง๋ ์ค์ํ์ง ์๋ค.
1.4 ๋๋ฉ์ธ ๋ชจ๋ธ ํจํด
์์ญ | ์ค๋ช |
---|---|
์ฌ์ฉ์ ์ธํฐํ์ด์ค(UI) ๋๋ ํํ(Presentation) | ์ฌ์ฉ์์ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ ์ฌ์ฉ์์๊ฒ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ค๋ค. ์ฌ๊ธฐ์ ์ฌ์ฉ์๋ ์ํํธ์จ์ด๋ฅผ ์ฌ์ฉํ๋ ์ฌ๋๋ฟ๋ง ์๋๋ผ ์ธ๋ถ ์์คํ ์ผ ์๋ ์๋ค. |
์์ฉ(Application) | ์ฌ์ฉ์๊ฐ ์์ฒญํ ๊ธฐ๋ฅ์ ์คํํ๋ค. ์ ๋ฌด ๋ก์ง์ ์ง์ ๊ตฌํํ์ง ์์ผ๋ฉฐ ๋๋ฉ์ธ ๊ณ์ธต์ ์กฐํฉํด์ ๊ธฐ๋ฅ์ ์คํํ๋ค. |
๋๋ฉ์ธ(Domain) | ์์คํ ์ด ์ ๊ณตํ ๋๋ฉ์ธ ๊ท์น์ ๊ตฌํํ๋ค. |
์ธํ๋ผ์คํธ๋ญ์ฒ(Infrastructure) | ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ๋ฉ์์ง ์์คํ ๊ณผ ๊ฐ์ ์ธ๋ถ ์์คํ ๊ณผ์ ์ฐ๋์ ์ฒ๋ฆฌํ๋ค. |
๊ฐ๋
๋ชจ๋ธ๊ณผ ๊ตฌํ ๋ชจ๋ธ
๊ฐ๋
๋ชจ๋ธ์ ์์ํ๊ฒ ๋ฌธ์ ๋ฅผ ๋ถ์ํ ๊ฒฐ๊ณผ๋ฌผ์ด๋ค. ๊ฐ๋
๋ชจ๋ธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค, ํธ๋์ญ์
์ฒ๋ฆฌ, ์ฑ๋ฅ, ๊ตฌํ ๊ธฐ์ ๊ณผ ๊ฐ์ ๊ฒ์ ๊ณ ๋ คํ๊ณ ์์ง ์๊ธฐ ๋๋ฌธ์ ์ค์ ์ฝ๋๋ฅผ ์์ฑํ ๋ ๊ฐ๋
๋ชจ๋ธ์ ์๋ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์๋ค. ๊ทธ๋์ ๊ฐ๋
๋ชจ๋ธ์ ๊ตฌํ ๊ฐ๋ฅํ ํํ์ ๋ชจ๋ธ๋ก ์ ํํ๋ ๊ณผ์ ์ ๊ฑฐ์น๊ฒ ๋๋ค.
์ฒ์๋ถํฐ ์๋ฒฝํ ๊ฐ๋ ๋ชจ๋ธ์ ๋ง๋ค๊ธฐ๋ณด๋ค๋ ์ ๋ฐ์ ์ธ ๊ฐ์๋ฅผ ์ ์ ์๋ ์์ค์ผ๋ก ๊ฐ๋ ๋ชจ๋ธ์ ์์ฑํด์ผํ๋ค. ํ๋ก์ ํธ ์ด๊ธฐ์๋ ๊ฐ์ ์์ค์ ๊ฐ๋ ๋ชจ๋ธ๋ก ๋๋ฉ์ธ์ ๋ํ ์ ์ฒด ์ค๊ณฝ์ ์ดํดํ๋ ๋ฐ ์ง์คํ๊ณ , ๊ตฌํํ๋ ๊ณผ์ ์์ ๊ฐ๋ ๋ชจ๋ธ์ ๊ตฌํ ๋ชจ๋ธ๋ก ์ ์ง์ ์ผ๋ก ๋ฐ์ ์์ผ ๋๊ฐ์ผ ํ๋ค.
1.5 ๋๋ฉ์ธ ๋ชจ๋ธ ๋์ถ
๋๋ฉ์ธ์ ๋ชจ๋ธ๋งํ ๋ ๊ธฐ๋ณธ์ด ๋๋ ์์ ์ ๋ชจ๋ธ์ ๊ตฌ์ฑํ๋ ํต์ฌ ๊ตฌ์ฑ์์, ๊ท์น, ๊ธฐ๋ฅ์ ์ฐพ๋ ๊ฒ์ด๋ค. ์ด ๊ณผ์ ์ ์๊ตฌ์ฌํญ์์ ์ถ๋ฐํ๋ค. ์๊ตฌ์ฌํญ์ ํตํด ๋๋ฉ์ธ ๋ชจ๋ธ์ ์ ์ง์ ์ผ๋ก ๋ง๋ค์ด ๋๊ฐ๋ค. ์ด๋ ๊ฒ ๋ง๋ ๋ชจ๋ธ์ ์๊ตฌ์ฌํญ ์ ๋ จ์ ์ํด ๋๋ฉ์ธ ์ ๋ฌธ๊ฐ๋ ๋ค๋ฅธ ๊ฐ๋ฐ์์ ๋ ผ์ํ๋ ๊ณผ์ ์์ ๊ณต์ ๋๊ธฐ๋ ํ๋ค. ๋ชจ๋ธ์ ๊ณต์ ํ ๋๋ ํ์ดํธ๋ณด๋๋ ์ํค ๊ฐ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํด์ ๋๊ตฌ๋ ์ฝ๊ฒ ์ ๊ทผํ ์ ์๋๋ก ํ๋ฉด ์ข๋ค.
1.6 ์ํฐํฐ์ ๋ฒจ๋ฅ
1.6.1 ์ํฐํฐ
- ์๋ณ์๋ฅผ ๊ฐ์ง๋ค.
- ์๋ณ์๋ ์ํฐํฐ ๊ฐ์ฒด๋ง๋ค ๊ณ ์ ํ๋ค.
1.6.2 ์ํฐํฐ์ ์๋ณ์ ์์ฑ
- ํน์ ๊ท์น์ ๋ฐ๋ผ ์์ฑ.
- UUID๋ Nano ID์ ๊ฐ์ ๊ณ ์ ์๋ณ์ ์์ฑ๊ธฐ ์ฌ์ฉ.
- ๊ฐ์ ์ง์ ์ ๋ ฅ.
- ์ผ๋ จ๋ฒํธ ์ฌ์ฉ(์ํ์ค๋ DB์ ์๋ ์ฆ๊ฐ ์ปฌ๋ผ ์ฌ์ฉ).
1.6.3 ๋ฐธ๋ฅ ํ์
- ๋ฐธ๋ฅ ํ์ ์ ๊ฐ๋ ์ ์ผ๋ก ์์ ํ ํ๋๋ฅผ ํํํ ๋ ์ฌ์ฉํ๋ค.
- ์๋ฏธ๋ฅผ ๋ช ํํ๊ฒ ํํํ๊ธฐ ์ํด ์ฌ์ฉ๋๊ธฐ๋ ํ๋ค.
- ๋ฐธ๋ฅ ํ์ ์ ์ํ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์๋ค.
- ๋ถ๋ณ์ผ๋ก ๊ตฌํํ๋ค.
1.6.4 ์ํฐํฐ ์๋ณ์์ ๋ฐธ๋ฅ ํ์
- ์๋ณ์๋ฅผ ์ํ ๋ฐธ๋ฅ ํ์ ์ ์ฌ์ฉํด์ ์๋ฏธ๊ฐ ์ ๋๋ฌ๋๋๋ก ํ ์ ์๋ค.
1.6.5 ๋๋ฉ์ธ ๋ชจ๋ธ์ set ๋ฃ์ง ์๊ธฐ
- ์ํ ๋ณ๊ฒฝ์ ์ํ set ์ฌ์ฉ์ ๋๋ฉ์ธ ์ง์์ด ์ฝ๋์์ ์ฌ๋ผ์ง๋ค.
- ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ ์จ์ ํ์ง ์์ ์ํ๊ฐ ๋ ์ ์๋ค.
DTO๋ ์ต๋ํ ๋ถ๋ณ ๊ฐ์ฒด๋ก ์ฌ์ฉํ๋๋ก ํ์
1.7 ๋๋ฉ์ธ ์ฉ์ด์ ์ ๋น์ฟผํฐ์ค ์ธ์ด
๋๋ฉ์ธ ์ฉ์ด
STEP1, STEP2 ๊ฐ์๊ฒ์ด ์๋ PAYMENT_WAITING, PREPARING ๊ฐ์ ๋๋ฉ์ธ ์ฉ์ด๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
์ ๋น์ฟผํฐ์ค ์ธ์ด
์ ๋ฌธ๊ฐ, ๊ด๊ณ์ ๊ฐ๋ฐ์๊ฐ ๋๋ฉ์ธ๊ณผ ๊ด๋ จ๋ ๊ณตํต์ ์ธ์ด๋ฅผ ๋ง๋ค๊ณ ์ด๋ฅผ ๋ํ, ๋ฌธ์, ๋๋ฉ์ธ ๋ชจ๋ธ, ์ฝ๋ ํ
์คํธ ๋ฑ ๋ชจ๋ ๊ณณ์์ ๊ฐ์ ์ฉ์ด๋ฅผ ์ฌ์ฉํ๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ํต ๊ณผ์ ์์ ๋ฐ์ํ๋ ์ฉ์ด์ ๋ชจํธํจ์ ์ค์ผ ์ ์๊ณ ๊ฐ๋ฐ์๋ ๋๋ฉ์ธ๊ณผ ์ฝ๋ ์ฌ์ด์์ ๋ถํ์ํ ํด์ ๊ณผ์ ์ ์ค์ผ ์ ์๋ค.
2. ์ํคํ์ฒ ๊ฐ์
2.1 ๋ค ๊ฐ์ ์์ญ
ํํ ์์ญ
์ฌ์ฉ์์ ์์ฒญ์ ๋ฐ์ ์์ฉ ์์ญ์ ์ ๋ฌํ๊ณ ์์ฉ ์์ญ์ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋ ์ญํ
์์ฉ ์์ญ
ํํ ์์ญ์ ํตํด ์์ฒญ์ ์ ๋ฌ๋ฐ์ ์์คํ
์ด ์ฌ์ฉ์์๊ฒ ์ ๊ณตํด์ผ ํ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ์ญํ . ๋ก์ง์ ์ง์ ์ํํ๊ธฐ๋ณด๋ค๋ ๋๋ฉ์ธ ๋ชจ๋ธ์ ๋ก์ง ์ํ์ ์์ํ๋ค.
๋๋ฉ์ธ ์์ญ
๋๋ฉ์ธ ๋ชจ๋ธ์ ๊ตฌํ. ๋๋ฉ์ธ ๋ชจ๋ธ์ ๋๋ฉ์ธ์ ํต์ฌ ๋ก์ง์ ๊ตฌํํ๋ค.
์ธํ๋ผ์คํธ๋ญ์ฒ ์์ญ
๊ตฌํ ๊ธฐ์ ์ ๋ํ ๊ฒ์ ๋ค๋ฃฌ๋ค. ์ด ์์ญ์ ๋
ผ๋ฆฌ์ ์ธ ๊ฐ๋
์ ํํํ๊ธฐ๋ณด๋ค๋ ์ค์ ๊ตฌํ์ ๋ค๋ฃฌ๋ค.
2.2 ๊ณ์ธต ๊ตฌ์กฐ ์ํคํ ์ฒ
- ์์ ๊ณ์ธต์์ ํ์ ๊ณ์ธต์ผ๋ก ์์กด๋ง ์กด์ฌํ๊ณ ํ์ ๊ณ์ธต์์ ์์ ๊ณ์ธต์ ์์กดํ์ง ์๋๋ค.
2.3 DIP
DIP(Dependency Inversion Principle) ์์กด์ฑ ์ญ์ ์์น
2.4 ๋๋ฉ์ธ ์์ญ์ ์ฃผ์ ๊ตฌ์ฑ์์
์์ | ์ค๋ช |
---|---|
์ํฐํฐ ENTITY | ๊ณ ์ ์ ์๋ณ์๋ฅผ ๊ฐ๋ ๊ฐ์ฒด๋ก ์์ ์ ๋ผ์ดํ ์ฌ์ดํด์ ๊ฐ๋๋ค. ์ฃผ๋ฌธ, ํ์, ์ํ๊ณผ ๊ฐ์ด ๋๋ฉ์ธ์ ๊ณ ์ ํ ๊ฐ๋ ์ ํํํ๋ค. ๋๋ฉ์ธ ๋ชจ๋ธ์ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๋ฉฐ ํด๋น ๋ฐ์ดํฐ์ ๊ด๋ จ๋ ๊ธฐ๋ฅ์ ํจ๊ป ์ ๊ณตํ๋ค. |
๋ฐธ๋ฅ VALUE | ๊ณ ์ ์ ์๋ณ์๋ฅผ ๊ฐ์ง ์๋ ๊ฐ์ฒด๋ก ์ฃผ๋ก ๊ฐ๋ ์ ์ผ๋ก ํ๋์ธ ๊ฐ์ ํํํ๋ค. ์ํฐํฐ์ ์์ฑ์ผ๋ก ์ฌ์ฉํ ๋ฟ๋ง ์๋๋ผ ๋ค๋ฅธ ๋ฐธ๋ฅ ํ์ ์ ์์ฑ์ผ๋ก๋ ์ฌ์ฉํ ์ ์๋ค. |
์ ๊ทธ๋ฆฌ๊ฑฐํธ AGGREGATE | ์ฐ๊ด๋ ์ํฐํฐ์ ๋ฐธ๋ฅ ๊ฐ์ฒด๋ฅผ ๊ฐ๋ ์ ์ผ๋ก ํ๋๋ก ๋ฌถ์ ๊ฒ์ด๋ค. ์๋ฅผ ๋ค์ด ์ฃผ๋ฌธ๊ณผ ๊ด๋ จ๋ Order ์ํฐํฐ, OrderLine ๋ฐธ๋ฅ, Orderer ๋ฐธ๋ฅ ๊ฐ์ฒด๋ฅผ โ์ฃผ๋ฌธโ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ก ๋ฌถ์ ์ ์๋ค. |
๋ฆฌํฌ์งํฐ๋ฆฌ REPOSITORY | ๋๋ฉ์ธ ๋ชจ๋ธ์ ์์์ฑ์ ์ฒ๋ฆฌํ๋ค. ์๋ฅผ ๋ค์ด DBMS ํ ์ด๋ธ์์ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋ก๋ฉํ๊ฑฐ๋ ์ ์ฅํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. |
๋๋ฉ์ธ ์๋น์ค DOMAIN SERVICE | ํน์ ์ํฐํฐ์ ์ํ์ง ์์ ๋๋ฉ์ธ ๋ก์ง์ ์ ๊ณตํ๋ค. ๋๋ฉ์ธ ๋ก์ง์ด ์ฌ๋ฌ ์ํฐํฐ์ ๋ฐธ๋ฅ๋ฅผ ํ์๋ก ํ๋ฉด ๋๋ฉ์ธ ์๋น์ค์์ ๋ก์ง์ ๊ตฌํํ๋ค. |
2.5 ์์ฒญ ์ฒ๋ฆฌ ํ๋ฆ
2.6 ์ธํ๋ผ์คํธ๋ญ์ฒ ๊ฐ์
- ๋๋ฉ์ธ ๊ฐ์ฒด์ ์์์ฑ ์ฒ๋ฆฌ, ํธ๋์ ์ , REST ํด๋ผ์ด์ธํธ ๋ฑ ๋ค๋ฅธ์์ญ์์ ํ์๋ก ํ๋ ํ๋ ์์ํฌ, ๊ตฌํ ๊ธฐ์ , ๋ณด์กฐ ๊ธฐ๋ฅ์ ์ง์ํ๋ค.
- ๋ณดํต ์์กด์ฑ์ญ์ ์ฌ์ฉ
- @Transactional ๊ฐ์ DIP๋ฅผ ์ฌ์ฉํ์ง ์๋ ์์ธ๋ ํธ์๋ฅผ ์ํด ํ์ฉ
2.7 ๋ชจ๋ ๊ตฌ์ฑ
3. ์ ๊ทธ๋ฆฌ๊ฑฐํธ
3.1 ์ ๊ทธ๋ฆฌ๊ฑฐํธ
๋ณต์กํ ๋๋ฉ์ธ์ ์ดํดํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ฌ์ด ๋จ์๋ก ๋ง๋๋ ค๋ฉด ์์ ์์ค์์ ๋ชจ๋ธ์ ์กฐ๋งํ ์ ์๋ ๋ฐฉ๋ฒ์ด ํ์ํ๋ฐ, ๊ทธ ๋ฐฉ๋ฒ์ด ๋ฐ๋ก ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ค. ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ ๊ด๋ จ๋ ๊ฐ์ฒด๋ฅผ ํ๋์ ๊ตฐ์ผ๋ก ๋ฌถ์ด ์ค๋ค.
์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ ๊ฒฝ๊ณ๋ฅผ ๊ฐ๋๋ค. ํ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ ๊ฐ์ฒด๋ ๋ค๋ฅธ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ์ง ์๋๋ค. ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ ๋ ๋ฆฝ๋ ๊ฐ์ฒด ๊ตฐ์ด๋ฉฐ ๊ฐ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ ์๊ธฐ ์์ ์ ๊ด๋ฆฌํ ๋ฟ ๋ค๋ฅธ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ๊ด๋ฆฌํ์ง ์๋๋ค. ๊ฒฝ๊ณ๋ฅผ ์ค์ ํ ๋ ๊ธฐ๋ณธ์ด ๋๋ ๊ฒ์ ๋๋ฉ์ธ ๊ท์น๊ณผ ์๊ตฌ์ฌํญ์ด๋ค. ๋๋ฉ์ธ ๊ท์น์ ๋ฐ๋ผ ํจ๊ป ์์ฑ๋๋ ๊ตฌ์ฑ์์๋ ํ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ ๊ฐ๋ฅ์ฑ์ด ๋๋ค.
3.2 ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ
์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ ๋ชจ๋ ๊ฐ์ฒด๊ฐ ์ผ๊ด๋ ์ํ๋ฅผ ์ ์งํ๋ ค๋ฉด ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์ ์ฒด๋ฅผ ๊ด๋ฆฌํ ์ฃผ์ฒด๊ฐ ํ์ํ๋ฐ, ์ด ์ฑ ์์ ์ง๋ ๊ฒ์ด ๋ฐ๋ก ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ ์ํฐํฐ์ด๋ค. ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ ๊ฐ์ฒด๋ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ ์ํฐํฐ์ ์ง์ ๋๋ ๊ฐ์ ์ ์ผ๋ก ์ํ๊ฒ ๋๋ค.
3.2.1 ๋๋ฉ์ธ ๊ท์น๊ณผ ์ผ๊ด์ฑ
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ์ ํต์ฌ ์ญํ ์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ผ๊ด์ฑ์ด ๊นจ์ง์ง ์๋๋ก ํ๋ ๊ฒ์ด๋ค. ์ด๋ฅผ ์ํด ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๋ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๊ฐ ์ ๊ณตํด์ผ ํ ๋๋ฉ์ธ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค.
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์ธ๋ถ์์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ ๊ฐ์ฒด๋ฅผ ์ง์ ๋ณ๊ฒฝํ๋ฉด ์๋๋ค. ์ด๊ฒ์ ๋ชจ๋ธ์ ์ผ๊ด์ฑ์ ๊นจ๋ ์์ธ์ด ๋๋ค.
- ๋จ์ํ ํ๋๋ฅผ ๋ณ๊ฒฝํ๋ set ๋ฉ์๋๋ฅผ ๊ณต๊ฐ(public) ๋ฒ์๋ก ๋ง๋ค์ง ์๋๋ค.
- ๋ฐธ๋ฅ ํ์ ์ ๋ถ๋ณ์ผ๋ก ๊ตฌํํ๋ค.
3.2.2 ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ์ ๊ธฐ๋ฅ ๊ตฌํ
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๋ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ด๋ถ์ ๋ค๋ฅธ ๊ฐ์ฒด๋ฅผ ์กฐํฉํด ๊ธฐ๋ฅ์ ์์ฑํ๋ค.
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๊ฐ ๊ตฌ์ฑ์์์ ์ํ๋ง ์ฐธ์กฐํ๋ ๊ฒ์ ์๋๋ค. ๊ธฐ๋ฅ ์คํ์ ์์ํ๊ธฐ๋ ํ๋ค.
3.2.3 ํธ๋์ ์ ๋ฒ์
- ์์์๋ก ์ข๋ค
- ํ ํธ๋์ ์ ์์๋ ํ ๊ฐ์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ง ์์ ํ๋ค.
- ๋ถ๋์ดํ๊ฒ ํ ํธ๋์ ์ ์ผ๋ก ๋ ๊ฐ ์ด์์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ํด์ผ ํ๋ค๋ฉด ์ ๊ทธ๋ฆฌ๊ฑฐํธ์์ ๋ค๋ฅธ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ํ์ง ๋ง๊ณ , ์์ฉ ์๋น์ค์์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ํ๋๋ก ๊ตฌํํ๋ค.
- ๋ ๊ฐ ์ด์์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ํด์ผ ํ๋ค๋ฉด, ๋๋ฉ์ธ ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ๋ค.
3.3 ๋ฆฌํฌ์งํฐ๋ฆฌ์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ: ๊ฐ๋ ์ ์์ ํ ํ๋์ ๋๋ฉ์ธ ๋ชจ๋ธ
- ๋ฆฌํฌ์งํฐ๋ฆฌ: ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์์์ฑ์ ์ฒ๋ฆฌ
- save: ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์ ์ฅ
- findById: ID๋ก ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ๊ตฌํจ
3.4 ID๋ฅผ ์ด์ฉํ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์ฐธ์กฐ
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ ๋ค๋ฅธ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์ฐธ์กฐํ๋ค.
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์ฐธ์กฐ๋ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๊ฐ ๋ค๋ฅธ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๋ฅผ ์ฐธ์กฐํ๋ค๋ ๋ป์ด๋ค.
ํ๋ ์ฐธ์กฐ
ํ๋ ์ฐธ์กฐ ๋ฌธ์ ์
- ํธํ ํ์ ์ค์ฉ
- ๊ฒฐํฉ๋ ์ฆ๊ฐ
- ์ฑ๋ฅ์ ๋ํ ๊ณ ๋ฏผ
- ํ์ฅ ์ด๋ ค์
ID๋ฅผ ์ด์ฉํ ์ฐธ์กฐ
ID ์ฐธ์กฐ ์ฅ์
- ๋ชจ๋ธ์ ๋ณต์ก๋๋ฅผ ๋ฎ์ถ๋ค
- ์์ง๋ ์ฆ๊ฐ
- ๊ตฌํ ๋์ด๋ ๊ฐ์
- ํ์ฅ ์ฉ์ด
3.4.1 ID๋ฅผ ์ด์ฉํ ์ฐธ์กฐ์ ์กฐํ ์ฑ๋ฅ
ID๋ฅผ ์ด์ฉํ ์กฐํ๋ N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
ํด๊ฒฐ๋ฐฉ๋ฒ์ ์๋์ ๊ฐ๋ค.
- ์กฐํ ์ ์ฉ ์ฟผ๋ฆฌ๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ๋ค.
- ์ฟผ๋ฆฌ๊ฐ ๋ณต์กํ๊ฑฐ๋ ํนํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ผ ํ๋ค๋ฉด ๋ง์ด๋ฐํฐ์ค์ ๊ฐ์ ๊ธฐ์ ๋ก ๊ตฌํํ๋๊ฑธ ๊ณ ๋ คํ๋ค.
3.5 ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๊ฐ ์งํฉ ์ฐ๊ด
1-N, M-N ์ฐ๊ด์ ๋ํด ์ดํด๋ณธ๋ค.
1-N
public class Category {
private Set<Product> products; //๋ค๋ฅธ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ 1-N ์ฐ๊ด
}
์กฐํ์ ๊ฐ๋ ์ ์ผ๋ก ์ ๊ฑฐ๋ฆฌ๊ฑฐํธ ๊ฐ์ 1-N ์ฐ๊ด์ด ์๋๋ผ๋ ์ฑ๋ฅ ๋ฌธ์ ๋๋ฌธ์ ์ค์ ๊ตฌํ์ ๋ฐ์ํ์ง ์๋๋ค. N-1๋ก ์ฐ๊ด์ง์ด ๊ตฌํํ๋ค.
public class Product {
private CategoryId categoryId;
}
public class ProductListService {
public Page<Product> getProductOfCategory(Long categoryId, int page, int size) {
Category category = categoryRepository.findById(categoryId);
checkCategory(category);
List<Product> products = productRepository.findByCategoryId(category.getId(), page, size);
int totalCount = productRepository.countByCategoryId(category.getId());
return new Page(page, size, totalCount, products);
}
}
M-N
M-N ์ฐ๊ด์ ๊ฐ๋
์ ์ผ๋ก ์์ชฝ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ปฌ๋ ์
์ผ๋ก ์ฐ๊ด์ ๋ง๋ ๋ค. ํ์ง๋ง ๊ตฌํ์ ์๊ตฌ์ฌํญ์ ๊ณ ๋ คํด์ ๊ฒฐ์ ํ๋ค. ๊ฐ๋
์ ์ผ๋ก๋ ์ํ๊ณผ ์นดํ
๊ณ ๋ฆฌ์ ์๋ฐฉํฅ M-N ์ฐ๊ด์ด ์กด์ฌํ์ง๋ง ์ค์ ๊ตฌํ์์๋ ์ํ์์ ์นดํ
๊ณ ๋ฆฌ๋ก์ ๋จ๋ฐฉํฅ M-N ์ฐ๊ด๋ง ์ ์ฉํ๋ฉด ๋๋ ๊ฒ์ด๋ค.
public class Product {
private Set<CategoryId> categoryIds;
}
RDBMS์์ M-N ์ฐ๊ด ๊ตฌํ
JPA๋ฅผ ์ด์ฉํ์ฌ ID ์ฐธ์กฐ๋ฅผ ์ด์ฉํ M-N ๋จ๋ฐฉํฅ ์ฐ๊ด ๊ตฌํ
@Entity
@Table(name = "product")
public class Product {
@EmbeddedId
private ProductId id;
@ElementCollection
@CollectionTable(name = "product_category",
joinColumns = @JoinColumn(name = "product_id"))
private Set<CategoryId> categoryIds;
...
}
3.6 ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ํฉํ ๋ฆฌ๋ก ์ฌ์ฉํ๊ธฐ
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ๊ฐ ๊ฐ๊ณ ์๋ ๋ฐ์ดํฐ๋ฅผ ์ด์ฉํด์ ๋ค๋ฅธ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ฑํด์ผ ํ๋ค๋ฉด ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๊ณ ๋ คํ๋ค.
- ๋ณ๋์ ๋๋ฉ์ธ ์๋น์ค๋, ํฉํ ๋ฆฌ ํด๋์ค๋ฅผ ๋ง๋ค ์๋ ์๋ค.
public class Store {
public Product createProduct (ProductId newProductId, ...์๋ต) {
if (isBlocked()) {
throw new StoreBlockedException();
}
return new Product(newProductId, getId(), ...์๋ต);
}
}
4. ๋ฆฌํฌ์งํฐ๋ฆฌ์ ๋ชจ๋ธ ๊ตฌํ
์์ธํ ์ค๋ช ์ ์๋์ ๋ฌธ์์ ์ฑ ์ ์ฝ๋ ๊ฒ์ ์ถ์ฒํฉ๋๋ค. ๐
- ๋๋ฉ์ธ ์ฃผ๋ ๊ฐ๋ฐ ์์ํ๊ธฐ: DDD ํต์ฌ ๊ฐ๋ ์ ๋ฆฌ๋ถํฐ ๊ตฌํ๊น์ง
- ์๋ฐ ORM ํ์ค JPA ํ๋ก๊ทธ๋๋ฐ
- Spring Data JPA
4.1 JPA๋ฅผ ์ด์ฉํ ๋ฆฌํฌ์งํฐ๋ฆฌ ๊ตฌํ
4.1.1 ๋ชจ๋ ์์น
4.1.2 ๋ฆฌํฌ์งํฐ๋ฆฌ ๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ
public interface OrderRepository {
Order findById(OrderNo no);
void save(Order order);
}
public class JpaOrderRepository implements OrderRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public Order findById(OrderNo id) {
return entityManager.find(Order.class, id);
}
@Override
public void save(Order order) {
entityManager.persist(order);
}
}
4.2 ์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ์ด์ฉํ ๋ฆฌํฌ์งํฐ๋ฆฌ ๊ตฌํ
@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
@EmbeddedId
private OrderNo number;
@Version
private long version;
@Embedded
private Orderer orderer;
...์๋ต...
}
public interface OrderRepository extends Repository<Order, OrderNo> {
Optional<Order> findById(OrderNo id);
void save(Order order);
default OrderNo nextOrderNo() {
int randomNo = ThreadLocalRandom.current().nextInt(900000) + 100000;
String number = String.format("%tY%<tm%<td%<tH-%d", new Date(), randomNo);
return new OrderNo(number);
}
}
@Service
public class CancelOrderService {
private OrderRepository orderRepository;
private CancelPolicy cancelPolicy;
public CancelOrderService(OrderRepository orderRepository, CancelPolicy cancelPolicy) {
this.orderRepository = orderRepository;
this.cancelPolicy = cancelPolicy;
}
@Transactional
public void cancel(OrderNo orderNo, Canceller canceller) {
Order order = orderRepository.findById(orderNo)
.orElseThrow(() -> new NoOrderException());
if (!cancelPolicy.hasCancellationPermission(order, canceller)) {
throw new NoCancellablePermission();
}
order.cancel();
}
}
4.3 ๋งคํ ๊ตฌํ
4.3.1 ์ํฐํฐ์ ๋ฐธ๋ฅ ๊ธฐ๋ณธ ๋งคํ ๊ตฌํ
์ ๊ทธ๋ฆฌ๊ฑฐํธ์ JPA ๋งคํ์ ์ํ ๊ธฐ๋ณธ ๊ท์น
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๋ ์ํฐํฐ์ด๋ฏ๋ก
@Entity
๋ก ๋งคํ ์ค์ ํ๋ค.
ํ ํ ์ด๋ธ์ ์ํฐํฐ์ ๋ฐธ๋ฅ ๋ฐ์ดํฐ๊ฐ ๊ฐ์ด ์๋ค๋ฉด
- ๋ฐธ๋ฅ๋
@Embeddable
๋ก ๋งคํ ์ค์ ํ๋ค. - ๋ฐธ๋ฅ ํ์
ํ๋กํผํฐ๋
@Embedded
๋ก ๋งคํ ์ค์ ํ๋ค. - ๋งคํํ ์ปฌ๋ผ๋ช
๋ณ๊ฒฝ์
@AttributeOverrides
๋ฅผ ์ด์ฉํ๋ค
@Embeddable
public class ShippingInfo {
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "zipCode", column = @Column(name = "shipping_zip_code")),
@AttributeOverride(name = "address1", column = @Column(name = "shipping_addr1")),
@AttributeOverride(name = "address2", column = @Column(name = "shipping_addr2"))
})
private Address address;
@Column(name = "shipping_message")
private String message;
@Embedded
private Receiver receiver;
@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
...
@Embedded
private Orderer orderer;
@Embedded
private ShippingInfo shippingInfo;
...
4.3.2 ๊ธฐ๋ณธ ์์ฑ์
์ํฐํฐ๋ ๊ธฐ๋ณธ ์์ฑ์๊ฐ ํ์ํ๋ค.
protected Receiver(){}
4.3.2 ํ๋ ์ ๊ทผ ๋ฐฉ์ ์ฌ์ฉ
@Access
๋ฅผ ์ด์ฉํด์ ๋ช
์์ ์ผ๋ก ์ ๊ทผ ๋ฐฉ์์ ์ง์ ํ์ง ์์ผ๋ฉด @Id
๋ @EmbeddedId
์ ์์น์ ๋ฐ๋ผ ์ ๊ทผ ๋ฐฉ์์ ๊ฒฐ์ ํ๋ค.
@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
...์๋ต...
}
4.3.4 AttributeConverter๋ฅผ ์ด์ฉํ ๋ฐธ๋ฅ ๋งคํ ์ฒ๋ฆฌ
๋ ๊ฐ ์ด์์ ํ๋กํผํฐ๋ฅผ ๊ฐ์ง ๋ฐธ๋ฅ ํ์
์ ํ ๊ฐ ์ปฌ๋ผ์ ๋งคํํ๋ ค๋ฉด @Embeddable
์ ๋ํ
์ด์
์ผ๋ก๋ ์ฒ๋ฆฌํ ์ ์๋ค. ์ด๋ด ๋ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด AttributeConverter
์ด๋ค.
@Converter(autoApply = true)
public class MoneyConverter implements AttributeConverter<Money, Integer> {
// Money๋ ๋ฐธ๋ฅ ํ์
, Integer๋ DB ํ์
// ๋ฐธ๋ฅ ํ์
์ DB ์นผ๋ผ ๊ฐ์ผ๋ก ๋ณํ
@Override
public Integer convertToDatabaseColumn(Money money) {
return money == null ? null : money.getValue();
}
// DB ์นผ๋ผ ๊ฐ์ ๋ฐธ๋ฅ๋ก ๋ณํ
@Override
public Money convertToEntityAttribute(Integer value) {
return value == null ? null : new Money(value);
}
}
// @Converter(autoApply = true) ์์ฑํ ํ์
์ ๋ํด ์๋ ์ ์ฉ
public class Order {
@Column(name = "total_amounts")
private Money totalAmounts;
}
// @Converter(autoApply = false) ๊ธฐ๋ณธ๊ฐ, ์ง์ ์ง์ ํด์ผํจ
public class Order {
@Convert(converter = MoneyConverter.class)
@Column(name = "total_amounts")
private Money totalAmounts;
}
4.3.5 ๋ฐธ๋ฅ ์ปฌ๋ ์ : ๋ณ๋ ํ ์ด๋ธ ๋งคํ
๋ฐธ๋ฅ ์ปฌ๋ ์
์ ๋ณ๋ ํ
์ด๋ธ๋ก ๋งคํํ ๋๋ @ElementCollection
๊ณผ @CollectionTable
์ ํจ๊ป ์ฌ์ฉํ๋ค.
@Entity
@Table(name = "purchase_order")
public class Order {
@EmbeddedId
private OrderNo number;
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "order_line", joinColumns = @JoinColumn(name = "order_number"))
@OrderColumn(name = "line_idx")
private List<OrderLine> orderLines;
...์๋ต...
}
@Embeddable
public class OrderLine {
@Embedded
private ProductId productId;
@Column(name = "price")
private Money price;
@Column(name = "quantity")
private int quantity;
@Column(name = "amounts")
private Money amounts;
}
4.3.6 ๋ฐธ๋ฅ ์ปฌ๋ ์ : ํ ๊ฐ ์ปฌ๋ผ ๋งคํ
ํ ๊ฐ ์ปฌ๋ผ์ ์ฝค๋ง๋ก ๊ตฌ๋ถํด์ ์ ์ฅํ ๋ ์ฌ์ฉ. AttributeConverter
๋ฅผ ์ด์ฉํ๋ค.
public class EmailSet {
private Set<Email> emails = new HashSet<>();
public EmailSet(Set<Email> emails) {
this.emails.addAll(emails);
}
public Set<Email> getEmails() {
return Collections.unmodifiableSet(emails);
}
}
public class EmailSetConverter implements AttributeConverter<EmailSet, String> {
@Override
public String convertToDatabaseColumn(EmailSet attribute) {
if (attribute == null) return null;
return attribute.getEmails().stream()
.map(email -> email.getAddress())
.collect(Collectors.joining(","));
}
@Override
public EmailSet convertToEntityAttribute(String dbData) {
if (dbData == null) return null;
String[] emails = dbData.split(",");
Set<Email> emailSet = Arrays.stream(emails)
.map(value -> new Email(value))
.collect(toSet());
return new EmailSet(emailSet);
}
}
4.3.7 ๋ฐธ๋ฅ๋ฅผ ์ด์ฉํ ID ๋งคํ
JPA์์ ์๋ณ์ ํ์
์ Serializable
ํ์
์ด์ด์ผ ํ๋ฏ๋ก Serializable
์ธํฐํ์ด์ค๋ฅผ ์์๋ฐ์์ผ ํ๋ค. ๋ฐธ๋ฅ ํ์
์ผ๋ก ์๋ณ์๋ฅผ ๊ตฌํํ ๋ ์ป์ ์ ์๋ ์ฅ์ ์ ์๋ณ์์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์๋ค๋ ์ ์ด๋ค.
@Entity
@Table(name = "purchase_order")
public class Order {
@EmbeddedId
private OrderNo number;
...์๋ต...
}
@Embeddable
public class OrderNo implements Serializable {
@Column(name = "order_number")
private String number;
...์๋ต...
}
4.3.8 ๋ณ๋ ํ ์ด๋ธ์ ์ ์ฅํ๋ ๋ฐธ๋ฅ ๋งคํ
๋ฃจํธ ์ํฐํฐ ์ธ์ ๋ ๋ค๋ฅธ ์ํฐํฐ๊ฐ ์๋ค๋ฉด ์ง์ง ์ํฐํฐ์ธ์ง ์์ฌํด ๋ด์ผํ๋ค. ๋จ์ง ๋ณ๋ ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ ํ๋ค๊ณ ํด์ ์ํฐํฐ์ธ ๊ฒ์ ์๋๋ค.
์๋ฅผ ๋ค์ด ๊ฒ์๊ธ ๋ฐ์ดํฐ๋ฅผ ARTICLE
ํ
์ด๋ธ๊ณผ ARTICLE_CONTENT
ํ
์ด๋ธ๋ก ๋๋ ์ ์ ์ฅํ๋ค๊ณ ํ์.
์ํฐํฐ๋ก ๋งคํ ์ (์๋ชป ๋จ)
ARTICLE_CONTENT
์ ID๋ ์๋ณ์์ด๊ธด ํ์ง๋ง ์ด๋ ARTICLE
ํ
์ด๋ธ๊ณผ ์ฐ๊ฒฐ์ ์ํจ์ด์ง ARTICLE_CONTENT
๋ฅผ ์ํ ๋ณ๋ ์๋ณ์๊ฐ ํ์ํด์๊ฐ ์๋๋ค.
๋ณ๋ ํ ์ด๋ธ๋ก ๋งคํ
ARTICLE_CONTENT
์ ๋ฐธ๋ฅ๋ก ๋ง๋ค๊ณ @Embeddable๋ก ๋งคํํ๋ค.
@Entity
@Table(name = "article")
@SecondaryTable(
name = "article_content",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "id")
)
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@AttributeOverrides({
@AttributeOverride(
name = "content",
column = @Column(table = "article_content", name = "content")),
@AttributeOverride(
name = "contentType",
column = @Column(table = "article_content", name = "content_type"))
})
@Embedded
private ArticleContent content;
}
๋ฐธ๋ฅ ๋งคํ์ ๋ณ๋ ํ
์ด๋ธ๋ก ์ ์ฅ ํ๋ ค๋ฉด @SecondaryTable
๊ณผ @AttributeOverride
์ ์ฌ์ฉํ๋ค.
ํ์ง๋ง @SecondaryTable
์ jpa.find("id");
(์กฐํ)๋ฅผ ์คํํ ๋ ๋ ํ
์ด๋ธ์ ์กฐ์ธํด์ ๊ฐ์ ธ์จ๋ค. ๊ฒ์๊ธ ๋ชฉ๋ก ๊ฐ์ ํ๋ฉด์ ARTICLE
์ ๋ฐ์ดํฐ๋ง ํ์ํ์ง ARTICLE_CONTENT
๋ ํ์์๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ARTICLE_CONTENT
์ ์ํฐํฐ๋ก ๋งคํํ๊ณ ARTICLE
์์ ARTICLE_CONTENT
์ ์ง์ฐ๋ก๋ฉ ํ ์๋ ์๋ค. ํ์ง๋ง ์ด๋ ๋ฐธ๋ฅ ๋ชจ๋ธ์ ์ํฐํฐ๋ก ๋ง๋๋ ๊ฒ์ด๋ฏ๋ก ์ข์ ๋ฐฉ๋ฒ์ ์๋๋ค. ๋์ ์กฐํ ์ ์ฉ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
4.3.9 ๋ฐธ๋ฅ ์ปฌ๋ ์ ์ @Entity๋ก ๋งคํํ๊ธฐ
@OneToMany
์ฌ์ฉํ์ฌ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ๋๋ค.
@Entity
@Table(name = "product")
public class Product {
...์๋ต...
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE},
orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
@OrderColumn(name = "list_idx")
private List<Image> images = new ArrayList<>();
...์๋ต...
@Inheritance
๋ฅผ ์ด์ฉํ์ฌ ์ํฐํฐ ์์๋ ๊ฐ๋ฅํ๋ค.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "image_type")
@Table(name = "image")
public abstract class Image {
...์๋ต...
}
@Entity
@DiscriminatorValue("II")
public class InternalImage extends Image {
...์๋ต...
}
@Entity
@DiscriminatorValue("EI")
public class ExternalImage extends Image {
...์๋ต...
4.3.10 ID ์ฐธ์กฐ์ ์กฐ์ธ ํ ์ด๋ธ์ ์ด์ฉํ ๋จ๋ฐฉํฅ M-N ๋งคํ
@Entity
@Table(name = "product")
public class Product {
...์๋ต...
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "product_category",
joinColumns = @JoinColumn(name = "product_id"))
private Set<CategoryId> categoryIds;
...์๋ต...
}
4.4 ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ก๋ฉ ์ ๋ต
// @Embeddable ์ปฌ๋ ์
์ ๋ํ ์ฆ์ ๋ก๋ฉ ์ค์
@ElementCollection(fetch = FetchType.EAGER)
// @Entity ์ปฌ๋ ์
์ ๋ํ ์ง์ฐ ๋ก๋ฉ ์ค์
@OneToMany(fetch = FetchType.LAZY)
- ์ฆ์ ๋ก๋ฉ: ์๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๋ฅผ ๊ตฌํ ๋ ์ฐ๊ด๋ ๊ตฌ์ฑ ์์๋ฅผ DB์์ ํจ๊ป ์ฝ์ด์จ๋ค.
- ์ง์ฐ ๋ก๋ฉ: ์ค์ ์ปฌ๋ ์ ์ ์ ๊ทผํ ๋ DB์์ ์กฐํํ๋ค.
4.5 ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์์์ฑ ์ ํ
์ ๊ทธ๋ฆฌ๊ฑฐํธ๊ฐ ์์ ํ ์ํ์ฌ์ผ ํ๋ค๋ ๊ฒ์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๋ฅผ ์กฐํํ ๋๋ฟ๋ง ์๋๋ผ ์ ์ฅํ๊ณ ์ญ์ ํ ๋๋ ํ๋๋ก ์ฒ๋ฆฌํด์ผ ํจ์ ์๋ฏธํ๋ค.
- ์ ์ฅ ๋ฉ์๋๋ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๋ง ์ ์ฅํ๋ฉด ์๋๊ณ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ ๋ชจ๋ ๊ฐ์ฒด๋ฅผ ์ ์ฅํด์ผ ํ๋ค.
- ์ญ์ ๋ฉ์๋๋ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ๋ฟ๋ง ์๋๋ผ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ ๋ชจ๋ ๊ฐ์ฒด๋ฅผ ์ญ์ ํด์ผ ํ๋ค.
@Embeddable
๋งคํ ํ์ ์ ํจ๊ป ์ ์ฅ๋๊ณ ์ญ์ ๋๋ฏ๋ก ์ถ๊ฐ ์ค์ ์ด ํ์์๋ค.@Entity
ํ์ ์ ๋ํ ๋งคํ์cascade
์์ฑ์ ์ฌ์ฉํ๋ค.
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
4.6 ์๋ณ์ ์์ฑ ๊ธฐ๋ฅ
์๋ณ์๋ ํฌ๊ฒ ์ธ ๊ฐ์ง ๋ฐฉ์ ์ค ํ๋๋ก ์์ฑํ๋ค.
- ์ฌ์ฉ์๊ฐ ์ง์ ์์ฑ
- ๋๋ฉ์ธ ๋ก์ง์ผ๋ก ์์ฑ
- DB๋ฅผ ์ด์ฉํ ์ผ๋ จ๋ฒํธ ์ฌ์ฉ
4.7 ๋๋ฉ์ธ ๊ตฌํ๊ณผ DIP
JPA์ @Entity๋ @Table, extends Repository ์ธํฐํ์ด์ค๋ DIP ์์น์ ์ด๊ธฐ๊ณ ์์ง๋ง ๊ฐ๋ฐ ํธ์์ฑ๊ณผ ์ค์ฉ์ฑ์ ๊ฐ์ง๊ณ , ๋ณต์ก๋๋ฅผ ๋์ด์ง ์์ผ๋ฉด์ ๊ธฐ์ ์ ๋ฐ๋ฅธ ๊ตฌํ ์ ์ฝ์ด ๋ฎ๋ค๋ฉด ํฉ๋ฆฌ์ ์ผ๋ก ์ ํํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
5. ์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ์ด์ฉํ ์กฐํ ๊ธฐ๋ฅ
5.1 ์์์ ์์
5.2 ๊ฒ์์ ์ํ ์คํ
์กฐํ๋ฅผ ์ํด ๋ค์ํ ๊ฒ์ ์กฐ๊ฑด์ ์กฐํฉํด์ผ ํ ๋๊ฐ ์๋ค. ์ด ๋ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด ์คํ(Specification)์ด๋ค.
5.3 ์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ์ด์ฉํ ์คํ ๊ตฌํ
์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ์ ๊ณตํ๋ Specification ์ธํฐํ์ด์ค
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder);
}
๋ ํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค ์์
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
List<Customer> findAll(Specification<Customer> spec);
List<Customer> findAll(Specification<Customer> spec, Sort sort);
List<Customer> findAll(Specification<Customer> spec, Pageable pageable);
}
์คํ ์์ฑ ๊ธฐ๋ฅ์ ๋ณ๋ ํด๋์ค๋ก ๊ตฌํ
public class CustomerSpecs {
public static Specification<Customer> isLongTermCustomer() {
return (root, query, builder) -> {
LocalDate date = LocalDate.now().minusYears(2);
return builder.lessThan(root.get(Customer_.createdAt), date);
};
}
public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
return (root, query, builder) -> {
// build query here
};
}
}
5.4 ๋ฆฌํฌ์งํฐ๋ฆฌ/DAO์์ ์คํ ์ฌ์ฉํ๊ธฐ
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
5.5 ์คํ ์กฐํฉ
์คํ ์ธํฐํ์ด์ค๋ and
์ or
๋ฑ ์กฐํฉ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค.
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
isLongTermCustomer().or(hasSalesOfMoreThan(amount)));
null
๊ฐ๋ฅ์ฑ์ด ์๋ ์คํ ๊ฐ์ฒด์ ์กฐํฉ์ where
๋ฅผ ์ฌ์ฉํ๋ค.
Specification<Customer> spec = Specification.where(createNullableSpec()).and(createOtherSpec());
5.6 ์ ๋ ฌ ์ง์ ํ๊ธฐ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฌ์ฉํด์ ์ ๋ ฌ์ ์ง์ ํ ์ ์๋ค.
- ๋ฉ์๋ ์ด๋ฆ์ OrderBy๋ฅผ ์ฌ์ฉํด์ ์ ๋ ฌ ๊ธฐ์ค ์ง์
- Sort๋ฅผ ์ธ์๋ก ์ ๋ฌ
public interface OrderSummaryDao extends Repository<OrderSummary, String> {
// OrderBy
List<OrderSummary> findByOrdererIdOrderByNumberDesc(String ordererId);
// Sort
List<OrderSummary> findByOrdererId(String ordererId, Sort sort);
}
Sort sort = Sort.by(Sort.Direction.DESC, "number");
List<OrderSummary> results = orderSummaryDao.findByOrdererId("id", sort);
5.7 ํ์ด์ง ์ฒ๋ฆฌํ๊ธฐ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ์ํด Pageable
ํ์
์ ์ด์ฉํ๋ค.
public interface MemberDataDao extends Repository<MemberData, String> {
List<MemberData> findByNameLike(String name, Pageable pageable);
Page<MemberData> findByBlocked(boolean blocked, Pageable pageable);
}
// 1์ ํ์ด์ง ๋ฒํธ, 0๋ถํฐ ์์ํ๋ค. 10์ ๊ฐ์
// Sort ์ฌ์ฉ ๊ฐ๋ฅ
Sort sort = Sort.by(Sort.Direction.DESC, "name");
PageRequest pageReq = PageRequest.of(1, 10, sort);
List<MemberData> user = memberDataDao.findByNameLike("์ด๋ฆ%", pageReq);
Page ๋ฆฌํด ํ์
PageRequest pageReq = PageRequest.of(1, 5);
Page<MemberData> page = memberDataDao.findByBlocked(false, pageReq);
List<MemberData> content = page.getContent(); // ์กฐํ ๊ฒฐ๊ณผ ๋ชฉ๋ก
long totalElements = page.getTotalElements(); // ์กฐ๊ฑด์ ํด๋นํ๋ ์ ์ฒด ๊ฐ์
int totalPages = page.getTotalPages(); // ์ ์ฒด ํ์ด์ง ๋ฒํธ
int number = page.getNumber(); // ํ์ฌ ํ์ด์ง ๋ฒํธ
int numberOfElements = page.getNumberOfElements() // ์กฐํ ๊ฒฐ๊ณผ ๊ฐ์
int size = page.getSize(); // ํ์ด์ง ํฌ๊ธฐ
Page ๋ฆฌํด ํ์ ์ COUNT ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ฏ๋ก List๋ง ํ์ํ ๊ฒฝ์ฐ ๋ฆฌํด ํ์ ์ List๋ก ํ๋ค.
5.8 ์คํ ์กฐํฉ์ ์ํ ์คํ ๋น๋ ํด๋์ค
์คํ์ ์กฐํฉํ ๋ ์คํ ๋น๋๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ ์ ์๋ค.
Specification<MemberData> spec = SpecBuilder.builder(MemberData.class)
.ifTrue(searchRequest.isOnlyNotBlocked(), () -> MemberDataSpecs.nonBlocked())
.ifHasText(searchRequest.getName(), name -> MemberDataSpecs.nameLike(searchRequest.getName()))
.toSpec();
List<MemberData> result = memberDataDao.findAll(spec, PageRequest.of(0, 5));
public class SpecBuilder {
public static <T> Builder<T> builder(Class<T> type) {
return new Builder<T>();
}
public static class Builder<T> {
private List<Specification<T>> specs = new ArrayList<>();
public Builder<T> and(Specification<T> spec) {
specs.add(spec);
return this;
}
public Builder<T> ifHasText(String str,
Function<String, Specification<T>> specSupplier) {
if (StringUtils.hasText(str)) {
specs.add(specSupplier.apply(str));
}
return this;
}
public Builder<T> ifTrue(Boolean cond,
Supplier<Specification<T>> specSupplier) {
if (cond != null && cond.booleanValue()) {
specs.add(specSupplier.get());
}
return this;
}
public Specification<T> toSpec() {
Specification<T> spec = Specification.where(null);
for (Specification<T> s : specs) {
spec = spec.and(s);
}
return spec;
}
}
}
5.9 ๋์ ์ธ์คํด์ค ์์ฑ
JPQL์ new ํค์๋๋ฅผ ํตํด ๊ฐ์ฒด๋ฅผ ๋์ ์ผ๋ก ์์ฑํ ์ ์๋ค.
public interface OrderSummaryDao extends Repository<OrderSummary, String> {
@Query("""
select new com.myshop.order.query.dto.OrderView(
o.number, o.state, m.name, m.id, p.name
)
from Order o join o.orderLines ol, Member m, Product p
where o.orderer.memberId.id = :ordererId
and o.orderer.memberId.id = m.id
and index(ol) = 0
and ol.productId.id = p.id
order by o.number.number desc
""")
List<OrderView> findOrderView(String ordererId);
}
5.10 ํ์ด๋ฒ๋ค์ดํธ @Subselect ์ฌ์ฉ
@Subselect
๋ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ@Entity
๋ก ๋งคํํ ์ ์๋ค.@Immutable
,@Subselect
,@Synchronize
์ ๊ฐ์ด ์ฌ์ฉํ๋ค.- ๋ทฐ๋ฅผ ์์ ํ ์ ์๋ฏ
@Subselect
๋ก ์กฐํํ@Entity
์ญ์ ์์ ํ ์ ์๋ค.
@Entity
@Immutable
@Subselect(
"""
select o.order_number as number,
o.version,
o.orderer_id,
o.orderer_name,
o.total_amounts,
o.receiver_name,
o.state,
o.order_date,
p.product_id,
p.name as product_name
from purchase_order o inner join order_line ol
on o.order_number = ol.order_number
cross join product p
where
ol.line_idx = 0
and ol.product_id = p.product_id"""
)
@Synchronize({"purchase_order", "order_line", "product"})
public class OrderSummary {
@Id
private String number;
private long version;
@Column(name = "orderer_id")
private String ordererId;
...์๋ต...
protected OrderSummary() {
}
}
6. ์์ฉ ์๋น์ค์ ํํ ์์ญ
6.1 ํํ ์์ญ๊ณผ ์์ฉ ์์ญ
ํํ ์์ญ
- ํํ ์์ญ์ ์ฌ์ฉ์์ ์์ฒญ์ ํด์ํ๋ค.
- ์ฌ์ฉ์๊ฐ ์คํํ๊ณ ์ถ์ ๊ธฐ๋ฅ์ ํ๋ณํ๊ณ ๊ทธ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์์ฉ ์๋น์ค๋ฅผ ์คํํ๋ค.
- ์์ฉ ์๋น์ค๊ฐ ์๊ตฌํ๋ ํ์์ผ๋ก ์ฌ์ฉ์ ์์ฒญ์ ๋ณํํ๋ค.
- ์์ฉ ์๋น์ค์ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉ์์๊ฒ ์๋ง์ ํ์์ผ๋ก ์๋ตํ๋ค.
์์ฉ ์์ญ
- ์ค์ ์ฌ์ฉ์๊ฐ ์ํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
- ์์ฉ ์๋น์ค๋ ํํ ์์ญ์ ์์กดํ์ง ์๋๋ค.
6.2 ์์ฉ ์๋น์ค์ ์ญํ
- ์ฌ์ฉ์(ํํ ์์ญ)๊ฐ ์์ฒญํ ๊ธฐ๋ฅ์ ์คํํ๋ค.
- ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํด์ ์์ฒญ์ ์ฒ๋ฆฌํ๋ค.
- ๋๋ฉ์ธ ๊ฐ์ฒด ๊ฐ์ ํ๋ฆ์ ์ ์ดํ๋ค.
- ํธ๋์ญ์ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํ๋ค.
- ์ ๊ทผ ์ ์ด์ ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ํ๋ค.
6.2.1 ๋๋ฉ์ธ ๋ก์ง ๋ฃ์ง ์๊ธฐ
๋๋ฉ์ธ ๋ก์ง์ ๋๋ฉ์ธ ์์ญ์ ์์นํ๊ณ , ์์ฉ ์๋น์ค๋ ๋๋ฉ์ธ ๋ก์ง์ ๊ตฌํํ์ง ์๋๋ค.
์ด์
- ์ฝ๋์ ์์ง์ฑ์ด ๋จ์ด์ง๋ค.
- ์ฌ๋ฌ ์์ฉ ์๋น์ค์์ ๋์ผํ ๋๋ฉ์ธ ๋ก์ง์ ๊ตฌํํ ๊ฐ๋ฅ์ฑ์ด ๋์์ง๋ค.
- ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฝ๋ ๋ณ๊ฒฝ์ ์ด๋ ต๊ฒ ๋ง๋ ๋ค.
6.3 ์์ฉ ์๋น์ค์ ๊ตฌํ
์์ฉ ์๋น์ค๋ ํํ ์์ญ๊ณผ ๋๋ฉ์ธ ์์ญ์ ์ฐ๊ฒฐํ๋ ๋งค๊ฐ์ฒด ์ญํ ์ ํ๋ค.(ํ์ฌ๋ facade)
6.3.1 ์์ฉ ์๋น์ค์ ํฌ๊ธฐ
์์ฉ ์๋น์ค์ ๊ตฌํ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ๋ ๊ฐ์ง๊ฐ ์๋ค.
- ํ ์์ฉ ์๋น์ค ํด๋์ค์ ํ์ ๋๋ฉ์ธ ๋ชจ๋ ๊ธฐ๋ฅ ๊ตฌํํ๊ธฐ
- ๊ตฌ๋ถ๋๋ ๊ธฐ๋ฅ๋ณ๋ก ์์ฉ ์๋น์ค ํด๋์ค๋ฅผ ๋ฐ๋ก ๊ตฌํํ๊ธฐ(์ถ์ฒ) ๐
// ๊ฐ ์์ฉ ์๋น์ค์์ ๊ณตํต๋๋ ๋ก์ง์ ๋ณ๋ ํด๋์ค๋ก ๊ตฌํ
public final class MemberServiceHelper {
public static Member findExistingMember(MemberRepository repo, String memberId) {
Member member = memberRepository.findById(memberId);
if (member == null) {
throw new NoMemberException(memberId);
}
return member;
}
}
// ๊ณตํต ๋ก์ง์ ์ ๊ณตํ๋ ๋ฉ์๋๋ฅผ ์์ฉ ์๋น์ค์์ ์ฌ์ฉ
import static com.myshop.member.application.MemberServiceHelper.*;
public class ChangePasswordService {
private MemberRepository memberRepository;
public void changePassword(String memberId, String curPw, String newPw) {
Member member = findExistingMember(memberRepository, memberId);
member.changePassword(curPw, newPw);
}
}
6.3.2 ์์ฉ ์๋น์ค์ ์ธํฐํ์ด์ค์ ํด๋์ค
์ธํฐํ์ด์ค๊ฐ ๋ช ํํ๊ฒ ํ์ํ์ง ์๋ค๋ฉด ์์ฉ ์๋น์ค์ ์ธํฐํ์ด์ค ์์ฑ์ ์ข์ ์ ํ์ด ์๋๋ค. ์์ค ํ์ผ๋ง ๋ง์์ง๊ณ ๊ฐ์ ์ฐธ์กฐ๊ฐ ์ฆ๊ฐํด์ ์ ์ฒด ๊ตฌ์กฐ๊ฐ ๋ณต์กํด์ง๋ค.
์ธํฐํ์ด์ค๊ฐ ํ์ํ ์ํฉ
- ๊ตฌํ ํด๋์ค๊ฐ ์ฌ๋ฌ๊ฐ์ธ ๊ฒฝ์ฐ
6.3.3 ๋ฉ์๋ ํ๋ผ๋ฏธํฐ์ ๊ฐ ๋ฆฌํด
- ๋๋ฉ์ธ์ ์ด์ฉํด ๊ธฐ๋ฅ์ ์คํํ๋ ๋ฐ ํ์ํ ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ฐ์์ผ ํ๋ค.
- ๊ฐ ๊ฐ์ ๊ฐ๋ณ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ฐ์ ์๋ ์๊ณ DTO๋ฅผ ๋ง๋ค์ด ์ ๋ฌ๋ฐ์ ์๋ ์๋ค.
- ์์ฉ ์๋น์ค์ ๊ฒฐ๊ณผ๋ฅผ ํํ ์์ญ์์ ์ฌ์ฉํด์ผ ํ๋ค๋ฉด, ์์ฉ ์๋น์ค์ ๊ฒฐ๊ณผ๋ก ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฆฌํดํ๋ค.
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ ์์ฒด๋ฅผ ๋ฆฌํดํ๋ ๊ฒ์ ์์ง๋๋ฅผ ๋ฎ์ถ์ด ๋น์ถ์ฒํ๋ค.
6.3.4 ํํ ์์ญ์ ์์กดํ์ง ์๊ธฐ
ํํ ์์ญ์ ํด๋นํ๋ HttpServletRequest, HttpSession ๋ฑ์ ์์ฉ ์๋น์ค์์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๋ฉด ์ ๋๋ค.
private ChangePasswordService changePasswordService;
@PostMapping
public String sumbit(HttpServletRequest request) {
// ์์ฉ ์๋น์ค๊ฐ ํํ ์์ญ์ ์์กดํ๋ฉด ์๋๋ค.
changePasswordService.changePassword(request);
...์๋ต...
}
6.3.5 ํธ๋์ญ์ ์ฒ๋ฆฌ
์คํ๋ง๊ณผ ๊ฐ์ ํ๋ ์์ํฌ๊ฐ ์ ๊ณตํ๋ ํธ๋์ญ์ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ ์ด์ฉํ๋ค.
@Transactional
6.4 ํํ ์์ญ
- ์ฌ์ฉ์๊ฐ ์์คํ ์ ์ฌ์ฉํ ์ ์๋ ํ๋ฆ(ํ๋ฉด)์ ์ ๊ณตํ๊ณ ์ ์ดํ๋ค.
- ์ฌ์ฉ์์ ์์ฒญ์ ์๋ง์ ์์ฉ ์๋น์ค์ ์ ๋ฌํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉ์์๊ฒ ์ ๊ณตํ๋ค.
- ์ฌ์ฉ์์ ์ธ์ ์ ๊ด๋ฆฌํ๋ค.
6.5 ๊ฐ ๊ฒ์ฆ
๊ฐ ๊ฒ์ฆ์ ํํ ์์ญ๊ณผ ์์ฉ ์๋น์ค ๋ ๊ณณ์์ ๋ชจ๋ ์ํํ ์ ์๋ค. ์์น์ ์ผ๋ก๋ ์์ฉ ์๋น์ค์์ ์ฒ๋ฆฌํ๋ค.
์์ฉ ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ํํ ์์ญ์ ์ฝ๋๊ฐ ํ ๊ณณ์ด๋ฉด ๊ตฌํ์ ํธ๋ฆฌํจ์ ์ํด ๋ค์๊ณผ ๊ฐ์ด ์ญํ ์ ๋๋์ด ๊ฒ์ฆ์ ์ํํ ์๋ ์๋ค.
- ํํ ์์ญ: ํ์ ๊ฐ, ๊ฐ์ ํ์, ๋ฒ์ ๋ฑ์ ๊ฒ์ฆํ๋ค.
- ์์ฉ ์๋น์ค: ๋ฐ์ดํฐ์ ์กด์ฌ ์ ๋ฌด์ ๊ฐ์ ๋ ผ๋ฆฌ์ ์ค๋ฅ๋ฅผ ๊ฒ์ฆํ๋ค.
6.6 ๊ถํ ๊ฒ์ฌ
๋ค์ ์ธ ๊ณณ์์ ๊ถํ ๊ฒ์ฌ๋ฅผ ์ํํ ์ ์๋ค.
- ํํ ์์ญ
- ์๋ธ๋ฆฟ ํํฐ
- ์์ฉ ์๋น์ค
- AOP
- ๋๋ฉ์ธ
- ์ง์ ๋ก์ง ๊ตฌํ
// ์คํ๋ง ์ํ๋ฆฌํฐ ํ์ฉ
@PreAuthorize("hasRole('ADMIN')")
6.7 ์กฐํ ์ ์ฉ ๊ธฐ๋ฅ๊ณผ ์์ฉ ์๋น์ค
์์ฉ ์๋น์ค๊ฐ ์ฌ์ฉ์ ์์ฒญ ๊ธฐ๋ฅ์ ์คํํ๋ ๋ฐ ๋ณ๋ค๋ฅธ ๊ธฐ์ฌ๋ฅผ ํ์ง ๋ชปํ๋ค๋ฉด ๊ตณ์ด ์๋น์ค๋ฅผ ๋ง๋ค์ง ์์๋ ๋๋ค.
7. ๋๋ฉ์ธ ์๋น์ค
7.1 ์ฌ๋ฌ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๊ฐ ํ์ํ ๊ธฐ๋ฅ
ํ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ฃ๊ธฐ ์ ๋งคํ, ์ฌ๋ฌ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๊ฐ ํ์ํ ๊ธฐ๋ฅ์ด๋ผ๋ฉด ๋ณ๋ ๋๋ฉ์ธ ์๋น์ค๋ก ๊ตฌํํ๋ค.
7.2 ๋๋ฉ์ธ ์๋น์ค
๋๋ฉ์ธ ์๋น์ค๋ ๋๋ฉ์ธ ์์ญ์ ์์นํ ๋๋ฉ์ธ ๋ก์ง์ ํํํ ๋ ์ฌ์ฉํ๋ค. ์ฃผ๋ก ๋ค์ ์ํฉ์์ ๋๋ฉ์ธ ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ค.
- ๊ณ์ฐ ๋ก์ง: ์ฌ๋ฌ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๊ฐ ํ์ํ ๊ณ์ฐ๋ก์ง์ด๋, ํ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ฃ๊ธฐ์๋ ๋ค์ ๋ณต์กํ ๊ณ์ฐ ๋ก์ง
- ์ธ๋ถ ์์คํ ์ฐ๋์ด ํ์ํ ๋๋ฉ์ธ ๋ก์ง: ๊ตฌํํ๊ธฐ ์ํด ํ ์์คํ ์ ์ฌ์ฉํด์ผ ํ๋ ๋๋ฉ์ธ ๋ก์ง
7.2.1 ๊ณ์ฐ ๋ก์ง๊ณผ ๋๋ฉ์ธ ์๋น์ค
๋๋ฉ์ธ ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ์ฃผ์ฒด
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ
- ์์ฉ ์๋น์ค
// ์ ๊ทธ๋ฆฌ๊ฑฐํธ๊ฐ ์ฌ์ฉ
public class OrderService {
// ๋๋ฉ์ธ ์๋น์ค
private DiscountCalculationService discountCalculationService;
private Order createOrder(OrderNo orderNo, OrderRequest orderReq) {
Member member = findMember(orderReq.getOrdererId());
Order order = new Order(orderNo, ...์๋ต);
// ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๊ฐ์ฒด์ ๋๋ฉ์ธ ์๋น์ค ์ ๋ฌ
order.calculateAmounts(this.discountCalculationService, member.getGrade());
}
}
// ์์ฉ ์๋น์ค๊ฐ ์ฌ์ฉ
public class TransferService { // ๋๋ฉ์ธ ์๋น์ค
// ๋๋ฉ์ธ ์๋น์ค์ ๊ธฐ๋ฅ์ ์คํํ ๋ ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์ ๋ฌ
public void transfer(Account fromAcc, Account toAcc, Money amounts) {
fromAcc.withdraw(amounts);
toAcc.credit(amounts);
}
...
}
ํน์ ๊ธฐ๋ฅ์ด ์์ฉ ์๋น์ค์ธ์ง ๋๋ฉ์ธ ์๋น์ค์ธ์ง ํ์ธํ๋ ๋ฐฉ๋ฒ
์๋์ ํ์๊ฐ ์ผ์ด๋๋ฉด ๋๋ฉ์ธ ์๋น์ค
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ๋ฅผ ๋ณ๊ฒฝ
- ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ์ํ ๊ฐ์ ๊ณ์ฐ
7.2.2 ์ธ๋ถ ์์คํ ์ฐ๋๊ณผ ๋๋ฉ์ธ ์๋น์ค
์์คํ ๊ฐ ์ฐ๋์ HTTP API ํธ์ถ๋ก ์ด๋ฃจ์ด์ง ์ ์์ง๋ง, ๋๋ฉ์ธ ์ ์ฅ์์๋ ๋๋ฉ์ธ ๋ก์ง์ผ๋ก ๋ณผ ์ ์๋ค. ๋๋ฉ์ธ ๊ด์ ์์ ์ธํฐํ์ด์ค๋ฅผ ์์ฑํ๋ค.
// ๋๋ฉ์ธ ์๋น์ค
public interface SurveyPermissionChecker {
boolean hasUserCreationPermission(String userId);
}
//์์ฉ ์๋น์ค๋ ๋๋ฉ์ธ ์๋น์ค๋ฅผ ์ด์ฉ
public class CreateSurveyService {
private SurveyPermissionChecker surveyPermissionChecker;
public Long createSurvey(CreateSurveyRequest req) {
validate(req);
// ๋๋ฉ์ธ ์๋น์ค๋ฅผ ์ด์ฉํด์ ์ธ๋ถ ์์คํ
์ฐ๋์ ํํ
if (surveyPermissionChecker.hasUserCreationPermission(req.getRequestorId())) {
throw new NoPermissionException();
}
...์๋ต...
}
}
SurveyPermissionChecker
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค๋ ์ธํ๋ผ์คํธ๋ญ์ฒ ์์ญ์ ์์นํ๋ค.
7.2.3 ๋๋ฉ์ธ ์๋น์ค์ ํจํค์ง ์์น
๋๋ฉ์ธ ์๋น์ค์ ๊ฐ์๊ฐ ๋ง๊ฑฐ๋ ๋ช ์์ ์ผ๋ก ๊ตฌ๋ถํ๊ณ ์ถ๋ค๋ฉด ์๋์ ๊ฐ์ด ํ์ ํจํค์ง๋ก ๊ตฌ๋ถํ๋ค.
- domain
- domain.model
- domain.service
- domain.repository
7.2.4 ๋๋ฉ์ธ ์๋น์ค์ ์ธํฐํ์ด์ค์ ํด๋์ค
๋๋ฉ์ธ ์๋น์ค์ ๋ก์ง์ด ๊ณ ์ ๋์ด ์์ง ์์ ๊ฒฝ์ฐ ๋๋ฉ์ธ ์๋น์ค ์์ฒด๋ฅผ ์ธํฐํ์ด์ค๋ก ๊ตฌํํ๊ณ ์ด๋ฅผ ๊ตฌํํ ํด๋์ค๋ฅผ ๋ ์๋ ์๋ค.
8. ์ ๊ทธ๋ฆฌ๊ฑฐํธ ํธ๋์ญ์ ๊ด๋ฆฌ
8.1 ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ํธ๋์ญ์
ํธ๋์ญ์ ์ฒ๋ฆฌ ๋ฐฉ์
- ์ ์ ์ ๊ธ (Pessimistic Lock, ๋น๊ด์ ์ ๊ธ)
- ๋น์ ์ ์ ๊ธ (Optimistic Lock, ๋๊ด์ ์ ๊ธ)
8.2 ์ ์ ์ ๊ธ
// JPA
Order order = entityManager.find(Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE);
// ์คํ๋ง ๋ฐ์ดํฐ JPA
public interface MemberREpository extends Repository<Member, MemberId> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Member> findById(MemberId memberId);
}
8.2.1 ์ ์ ์ ๊ธ๊ณผ ๊ต์ฐฉ ์ํ
๋ค์ ์ํฉ์ ๊ต์ฐฉ ์ํ์ ๋น ์ง๋ค.
- ์ค๋ ๋ 1: ๐ณ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ์ ์ ๊ธ ๊ตฌํจ
- ์ค๋ ๋ 2: ๐ฆ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ์ ์ ๊ธ ๊ตฌํจ
- ์ค๋ ๋ 1: ๐ฆ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ์ ์ ๊ธ ์๋
- ์ค๋ ๋ 2: ๐ณ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ํ ์ ์ ์ ๊ธ ์๋
ํด๊ฒฐ ๋ฐฉ๋ฒ
ํํธ๋ฅผ ์ ๊ณตํด ์ต๋ ๋๊ธฐ์๊ฐ์ ์ง์ ํ๋ค.
// JPA
Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.lock.timeout", 2000);
Order order = entityManager.find(Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE, hints);
// ์คํ๋ง ๋ฐ์ดํฐ JPA
public interface MemberREpository extends Repository<Member, MemberId> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({
@QueryHint(name = "javax.persistence.lock.timeout", value = "2000")
})
Optional<Member> findById(MemberId memberId);
}
DBMS์ ๋ฐ๋ผ ๊ต์ฐฉ ์ํ์ ๋น ์ง ์ปค๋ฅ์ ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด ๋ค๋ฅด๋ค. ์ฌ์ฉํ๋ DBMS์ ๋ํด JPA๊ฐ ์ด๋ค ์์ผ๋ก ๋๊ธฐ์๊ฐ์ ์ฒ๋ฆฌํ๋์ง ๋ฐ๋์ ํ์ธํด์ผ ํ๋ค.
8.3 ๋น์ ์ ์ ๊ธ
์ ์ ์ ๊ธ์ผ๋ก ๋ชจ๋ ํธ๋์ญ์ ์ถฉ๋ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋ ๊ฒ์ ์๋๋ค
๋น์ ์ ์ ๊ธ์ ๋์์ ์ ๊ทผํ๋ ๊ฒ์ ๋ง๋ ๋์ ๋ณ๊ฒฝํ ๋ฐ์ดํฐ๋ฅผ ์ค์ DBMS์ ๋ฐ์ํ๋ ์์ ์ ๋ณ๊ฒฝ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ๋ฐฉ์์ด๋ค.
๋น์ ์ ์ ๊ธ์ ๊ตฌํํ๋ ค๋ฉด ์ ๊ทธ๋ฆฌ๊ฑฐํธ์ ๋ฒ์ ์ผ๋ก ์ฌ์ฉํ ์ซ์ ํ์ ํ๋กํผํฐ๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค. ์ ๊ทธ๋ฆฌ๊ฑฐํธ๋ฅผ ์์ ํ ๋๋ง๋ค ๋ฒ์ ์ผ๋ก ์ฌ์ฉํ ํ๋กํผํฐ ๊ฐ์ด 1์ฉ ์ฆ๊ฐํ๋ค.
// JPA
@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
...์๋ต...
@Version
private long version;
...์๋ต...
}
JPA ์ํฐํฐ๊ฐ ๋ณ๊ฒฝ๋์ด UPDATE ์ฟผ๋ฆฌ๋ฅผ ์คํํ ๋ @Version
์ ๋ช
์ํ ํ๋๋ฅผ ์ด์ฉํด์ ๋น์ ์ ์ ๊ธ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ค. ์์ฉ ์๋น์ค๋ ๋ฒ์ ์ ๋ํด ์ ํ์๊ฐ ์๋ค.
ํธ๋์ญ์
์ถฉ๋์ด ๋ฐ์ํ๋ฉด OptimisticLockingFailureException
์ด ๋ฐ์ํ๋ค.
๋น์ ์ ์ ๊ธ ํ์ฅ
๋น์ ์ ์ ๊ธ ๋ฐฉ์์ ์ฌ๋ฌ ํธ๋์ญ์ ์ผ๋ก ํ์ฅํ๋ ค๋ฉด ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฒ์ ์ ๋ณด๋ฅผ ์์ฉ ์๋น์ค์ ์ ๋ฌํ๋ค. ์์ฉ ์๋น์ค๋ ์ ๋ฌ๋ฐ์ ๋ฒ์ ๊ฐ์ ์ด์ฉํด์ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฒ์ ๊ณผ ์ผ์นํ๋์ง ํ์ธํ๊ณ , ์ผ์นํ๋ ๊ฒฝ์ฐ์๋ง ๊ธฐ๋ฅ์ ์ํํ๋ค.
public class StartShippingRequest {
private String orderNumber;
private long version;
...์๋ต...
}
// ์์ฉ ์๋น์ค
public StartShippingService {
@PreAuthorize("hasRole('ADMIN')")
@Transactional
public void startShipping(StartShippingRequest req) {
Order order = orderRepository.findById(new OrderNo(req.getOrderNumber()));
// version ํ์ธ
if (!order.matchVersion(req.getVersion())) {
throw new VersionConflictException();
}
...์๋ต...
}
}
8.3.1 ๊ฐ์ ๋ฒ์ ์ฆ๊ฐ
๊ธฐ๋ฅ ์คํ ๋์ค ๋ฃจํธ๊ฐ ์๋ ๋ค๋ฅธ ์ํฐํฐ์ ๊ฐ๋ง ๋ณ๊ฒฝ๋๋ค๋ฉด, JPA๋ ๋ฃจํธ ์ํฐํฐ ๋ฒ์ ๊ฐ์ ์ฆ๊ฐ์ํค์ง ์๋๋ค.
JPA๋ ์ด๋ฐ ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋๋ก EntityManager#find() ๋ฉ์๋๋ก ์ํฐํฐ๋ฅผ ๊ตฌํ ๋ ๊ฐ์ ๋ก ๋ฒ์ ๊ฐ์ ์ฆ๊ฐ์ํค๋ ์ ๊ธ ๋ชจ๋๋ฅผ ์ง์ํ๋ค. LockModeType.OPTIMISTIC_FORCE_INCREMENT
๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋น ์ํฐํฐ์ ์ํ๊ฐ ๋ณ๊ฒฝ๋์๋์ง์ ์๊ด์์ด ํธ๋์ญ์
์ข
๋ฃ ์์ ์ ๋ฒ์ ๊ฐ ์ฆ๊ฐ ์ฒ๋ฆฌ๋ฅผ ํ๋ค.
// JPA
Order order = entityManager.find(Order.class, orderNo, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
// ์คํ๋ง ๋ฐ์ดํฐ JPA
public interface MemberREpository extends Repository<Member, MemberId> {
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
Optional<Member> findById(MemberId memberId);
}
8.4 ์คํ๋ผ์ธ ์ ์ ์ ๊ธ
๋๊ตฐ๊ฐ ์์ ํ๋ฉด์ ๋ณด๊ณ ์์ ๋ ์์ ํ๋ฉด ์์ฒด๋ฅผ ์คํํ์ง ๋ชปํ๊ฒ ํ๋ ๊ฒ์ด ์คํ๋ผ์ธ ์ ์ ์ ๊ธ ๋ฐฉ์์ด๋ค.
8.4.1 ์คํ๋ผ์ธ ์ ์ ์ ๊ธ์ ์ํ LockManager ์ธํฐํ์ด์ค์ ๊ด๋ จ ํด๋์ค
ํ์ ๊ธฐ๋ฅ
- ์ ๊ธ ์ ์ ์๋
- ์ ๊ธ ํ์ธ
- ์ ๊ธ ํด์
- ์ ๊ธ ์ ํจ์๊ฐ
๊ตฌํ ์ฝ๋๋ ์ฑ ์์ ํ์ธํด์ฃผ์ธ์. ๐
8.4.2 DB๋ฅผ ์ด์ฉํ LockManager ๊ตฌํ
๊ตฌํ ์ฝ๋๋ ์ฑ ์์ ํ์ธํด์ฃผ์ธ์. ๐
9. ๋๋ฉ์ธ ๋ชจ๋ธ๊ณผ ๋ฐ์ด๋๋ ์ปจํ ์คํธ
9.1 ๋๋ฉ์ธ ๋ชจ๋ธ๊ณผ ๊ฒฝ๊ณ
๋ ผ๋ฆฌ์ ์ผ๋ก ๊ฐ์ ์กด์ฌ์ฒ๋ผ ๋ณด์ด์ง๋ง ํ์ ๋๋ฉ์ธ์ ๋ฐ๋ผ ๋ค๋ฅธ ์ฉ์ด๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ ์๋ค.
์ด๋ ๊ฒ ํ์ ๋๋ฉ์ธ๋ง๋ค ๊ฐ์ ์ฉ์ด๋ผ๋ ์๋ฏธ๊ฐ ๋ค๋ฅด๊ณ ๊ฐ์ ๋์์ด๋ผ๋ ์ง์นญํ๋ ์ฉ์ด๊ฐ ๋ค๋ฅผ ์ ์๊ธฐ ๋๋ฌธ์ ํ๊ฐ์ ๋ชจ๋ธ๋ก ๋ชจ๋ ํ์ ๋๋ฉ์ธ์ ํํํ๋ ค๋ ์๋๋ ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ์ด ์๋๋ฉฐ ํํํ ์๋ ์๋ค.
๋ชจ๋ธ์ ํน์ ํ ์ปจํ ์คํธ(๋ฌธ๋งฅ) ํ์์ ์์ ํ ์๋ฏธ๋ฅผ ๊ฐ๋๋ค. ๊ฐ์ ์ ํ์ด๋ผ๋ ์นดํ๋ก๊ทธ ์ปจํ ์คํธ์ ์ฌ๊ณ ์ปจํ ์คํธ์์ ์๋ฏธ๊ฐ ์๋ก ๋ค๋ฅด๋ค. ์ด๋ ๊ฒ ๊ตฌ๋ถ๋๋ ๊ฒฝ๊ณ๋ฅผ ๊ฐ๋ ์ปจํ ์คํธ๋ฅผ DDD์์๋ ๋ฐ์ด๋๋ ์ปจํ ์คํธ(Bounded Context)๋ผ๊ณ ๋ถ๋ฅธ๋ค.
9.2 ๋ฐ์ด๋๋ ์ปจํ ์คํธ
- ๋๋ฉ์ธ ๋ชจ๋ธ์ ๊ฒฝ๊ณ๋ฅผ ๊ฒฐ์ ํ๋ค.
- ์ฉ์ด๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ตฌ๋ถํ๋ค.
- ํ ๊ฐ์ ๋ฐ์ด๋๋ ์ปจํ ์คํธ๋ ๋ ผ๋ฆฌ์ ์ผ๋ก ํ ๊ฐ์ ๋ชจ๋ธ์ ๊ฐ๋๋ค.
- ํ ๊ฐ์ ๋ฐ์ด๋๋ ์ปจํ ์คํธ๊ฐ ์ฌ๋ฌ ํ์ ๋๋ฉ์ธ์ ํฌํจํ๋๋ผ๋, ํ์ ๋๋ฉ์ธ๋ง๋ค ๊ตฌ๋ถ๋๋ ํจํค์ง๋ฅผ ๊ฐ๋๋ก ๊ตฌํํด์ผ ํ๋ค.
์์ง ๋ช
ํํ๊ฒ ๊ตฌ๋ถ๋์ง ์์ ๊ฒฝ์ฐ ๋ ํ์ ๋๋ฉ์ธ์ ํ๋์ ๋ฐ์ด๋๋ ์ปจํ
์คํธ์์ ๊ตฌํํ๊ธฐ๋ ํ๋ค.
ํ ๊ฐ์ ๋ฐ์ด๋๋ ์ปจํ
์คํธ๊ฐ ์ฌ๋ฌ ํ์ ๋๋ฉ์ธ์ ํฌํจํ๋๋ผ๋, ํ์ ๋๋ฉ์ธ๋ง๋ค ๊ตฌ๋ถ๋๋ ํจํค์ง๋ฅผ ๊ฐ๋๋ก ๊ตฌํํด์ผ ํ๋ค.
๊ฐ์ ์ํ์ด๋ผ๋ ๊ฐ์ ๊ตฌํํ๋ ํ์ ๋๋ฉ์ธ์ ๋ง๋ ๋ชจ๋ธ์ ๊ฐ๋๋ค.
9.3 ๋ฐ์ด๋๋ ์ปจํ ์คํธ ๊ตฌํ
๋ฐ์ด๋๋ ์ปจํ ์คํธ๋ ๋๋ฉ์ธ ๊ธฐ๋ฅ์ ์ฌ์ฉ์์๊ฒ ์ ๊ณตํ๋ ๋ฐ ํ์ํ ํํ ์์ญ, ์์ฉ ์๋น์ค, ์ธํ๋ผ์คํธ๋ญ์ฒ, ํ ์ด๋ธ ์์ญ์ ํฌํจํ๋ค.
CQRS (Command Query Responsibility Segregation, ๋ช ๋ น ์กฐํ ์ฑ ์ ๋ถ๋ฆฌ) ํจํด์ ์ฌ์ฉํ ์๋ ์๋ค.
๋ฐ์ด๋๋ ์ปจํ ์คํธ๋ UI๋ฅผ ๊ฐ์ง ์์ ์๋ ์๋ค.
๋ฐ์ด๋๋ ์ปจํ ์คํธ๋ UI์๋ฒ๋ฅผ ํตํด ๊ฐ์ ์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ์ ํต์ ํ ์๋ ์๋ค. ์ฌ๊ธฐ์ UI์๋ฒ๋ ํ์ฌ๋ ์ญํ ์ ์ํํ๋ค.
9.4 ๋ฐ์ด๋๋ ์ปจํ ์คํธ ๊ฐ ํตํฉ
๋ฐ์ด๋๋ ์ปจํ ์คํธ ๊ฐ ํตํฉ์ด ํ์ํ ๋๋ ์๋ค.
REST API๋ฅผ ์ด์ฉํ ์ง์ ํตํฉ
- ํต์
- ๋๋ฉ์ธ์ ๋ง๋ ๋ชจ๋ธ๋ก ๋ณํ
๋ฉ์์ง ํ๋ฅผ ์ฌ์ฉํ์ฌ ํตํฉ
๋ง์ดํฌ๋ก์๋น์ค์ ๋ฐ์ด๋๋ ์ปจํ
์คํธ
๋ง์ดํฌ๋ก์๋น์ค๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์์ ์๋น์ค๋ก ๋๋์ด ๊ฐ๋ฐํ๋ ์ํคํ
์ฒ ์คํ์ผ์ด๋ค. ๊ฐ๋ณ ์๋น์ค๋ฅผ ๋
๋ฆฝ๋ ํ๋ก์ธ์ค๋ก ์คํํ๊ณ ๊ฐ ์๋น์ค๊ฐ REST API๋ ๋ฉ์์ง์ ์ด์ฉํด์ ํต์ ํ๋ ๊ตฌ์กฐ๋ฅผ ๊ฐ๋๋ค.
๋ฐ์ด๋๋ ์ปจํ ์คํธ๋ฅผ ๋ง์ดํฌ๋ก์๋น์ค๋ก ๊ตฌํํ๋ฉด ์์ฐ์ค๋ฝ๊ฒ ์ปจํ ์คํธ๋ณ๋ก ๋ชจ๋ธ์ด ๋ถ๋ฆฌ๋๋ค. ๋ง์ดํฌ๋ก์๋น์ค๋ง๋ค ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ฏ๋ก ๋ฐ์ด๋๋ ์ปจํ ์คํธ๋ง๋ค ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๊ฒ ๋๋ค. ์ด๊ฒ์ ์ฝ๋ ์์ค์์ ๋ชจ๋ธ์ ๋ถ๋ฆฌํ์ฌ ๋ ๋ฐ์ด๋๋ ์ปจํ ์คํธ์ ๋ชจ๋ธ์ด ์์ด์ง ์๋๋ก ํด์ค๋ค.
9.5 ๋ฐ์ด๋๋ ์ปจํ ์คํธ ๊ฐ ๊ด๊ณ
๋ฐ์ด๋๋ ์ปจํ ์คํธ๋ ์ด๋ค ์์ผ๋ก๋ ์ฐ๊ฒฐ๋๊ธฐ ๋๋ฌธ์ ๋ ๋ฐ์ด๋๋ ์ปจํ ์คํธ๋ ๋ค์ํ ๋ฐฉ์์ผ๋ก ๊ด๊ณ๋ฅผ ๋งบ๋๋ค.
REST API
๋จ์ผ API
๋
๋ฆฝ ๋ฐฉ์
๊ทธ๋ฅ ์๋ก ํตํฉํ์ง ์๋ ๋ฐฉ์์ด๋ค. ์๋ก ๋
๋ฆฝ์ ์ผ๋ก ๋ชจ๋ธ์ ๋ฐ์ ์ํจ๋ค. ํ์ง๋ง ๊ท๋ชจ๊ฐ ์ปค์ง์๋ก ํ๊ณ๊ฐ ์์ผ๋ฏ๋ก ๊ทธ ์ ์ ๋ ๋ฐ์ด๋๋ ์ปจํ
์คํธ๋ฅผ ํตํฉํด์ผ ํ๋ค.
9.6 ์ปจํ ์คํธ ๋งต
๊ฐ๋ณ ๋ฐ์ด๋๋ ์ปจํ ์คํธ์ ๋งค๋ชฐ๋๋ฉด ์ ์ฒด๋ฅผ ๋ณด์ง ๋ชปํ ๋๊ฐ ์๋ค. ์ ์ฒด ๋ฐ์ด๋๋ ์ปจํ ์คํธ ๊ฐ ๊ด๊ณ๋ฅผ ๋ณผ ์ ์๋ ์ง๋๊ฐ ํ์ํ๋ฐ ๊ทธ๊ฒ์ด ๋ฐ๋ก ์ปจํ ์คํธ ๋งต์ด๋ค.
10. ์ด๋ฒคํธ
10.1 ์์คํ ๊ฐ ๊ฐ๊ฒฐํฉ ๋ฌธ์
- ํธ๋ ์ ์ ๋ฌธ์
- ์ธ๋ถ ์๋น์ค์ ์ฑ๋ฅ์ ์ํฅ์ ๋ฐ์
- ์ค๊ณ์ ๋ฌธ์ (๋ก์ง์ด ์์)
- ๊ธฐ๋ฅ ์ถ๊ฐ์ ์ด๋ ค์
10.2 ์ด๋ฒคํธ ๊ฐ์
์ฌ๊ธฐ์ ์ฌ์ฉ๋๋ ์ด๋ฒคํธ๋ผ๋ ์ฉ์ด๋ โ๊ณผ๊ฑฐ์ ๋ฒ์ด์ง ์ด๋ค ๊ฒโ์ ์๋ฏธํ๋ค.
- ์ด๋ฒคํธ ๋ฐ์
- ์ด๋ฒคํธ์ ๋ฐ์ํ์ฌ ๋์ ์ํ
10.2.1 ์ด๋ฒคํธ ๊ด๋ จ ๊ตฌ์ฑ์์
- ์ด๋ฒคํธ
- ์ด๋ฒคํธ ์์ฑ ์ฃผ์ฒด
- ์ด๋ฒคํธ ๋์คํจ์ฒ (ํผ๋ธ๋ฆฌ์ )
- ์ด๋ฒคํธ ํธ๋ค๋ฌ (๊ตฌ๋ ์)
๋๋ฉ์ธ ๋ชจ๋ธ์์ ์ด๋ฒคํธ ์์ฑ ์ฃผ์ฒด๋ ์ํฐํฐ, ๋ฐธ๋ฅ, ๋๋ฉ์ธ ์๋น์ค์ ๊ฐ์ ๋๋ฉ์ธ ๊ฐ์ฒด์ด๋ค. ๋๋ฉ์ธ ๊ฐ์ฒด๋ ๋๋ฉ์ธ ๋ก์ง์ ์คํํด์ ์ํ๊ฐ ๋ฐ๋๋ฉด ๊ด๋ จ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํจ๋ค.
10.2.2 ์ด๋ฒคํธ์ ๊ตฌ์ฑ
์ด๋ฒคํธ๋ ๋ฐ์ํ ์ด๋ฒคํธ์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๋๋ค.
- ์ด๋ฒคํธ ์ข ๋ฅ: ํด๋์ค ์ด๋ฆ์ผ๋ก ์ด๋ฒคํธ ์ข ๋ฅ๋ฅผ ํํ
- ์ด๋ฒคํธ ๋ฐ์ ์๊ฐ
- ์ถ๊ฐ ๋ฐ์ดํฐ: ์ฃผ๋ฌธ๋ฒํธ, ์๊ท๋ฐฐ์ก์ง ์ ๋ณด ๋ฑ ์ด๋ฒคํธ์ ๊ด๋ จ๋ ์ ๋ณด
10.2.3 ์ด๋ฒคํธ ์ฉ๋
- ํ์ฒ๋ฆฌ๋ฅผ ์คํํ๊ธฐ ์ํ ํธ๋ฆฌ๊ฑฐ
- ์๋ก ๋ค๋ฅธ ์์คํ ๊ฐ์ ๋ฐ์ดํฐ ๋๊ธฐํ
10.2.4 ์ด๋ฒคํธ ์ฅ์
- ์๋ก ๋ค๋ฅธ ๋๋ฉ์ธ ๋ก์ง์ด ์์ด๋ ๊ฒ์ ๋ฐฉ์ง
- ๋๋ฉ์ธ ๋ก์ง์ ์ํฅ ์์ด ๊ธฐ๋ฅ ํ์ฅ
10.3 ์ด๋ฒคํธ, ํธ๋ค๋ฌ, ๋์คํจ์ฒ ๊ตฌํ
์ด๋ฒคํธ์ ๊ด๋ จ๋ ์ฝ๋
- ์ด๋ฒคํธ ํด๋์ค: ์ด๋ฒคํธ๋ฅผ ํํํ๋ค.
- ๋์คํจ์ฒ: ์คํ๋ง์ด ์ ๊ณตํ๋ ApplicationEventPublisher๋ฅผ ์ด์ฉํ๋ค.
- Events: ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๋ค. ์ด๋ฒคํธ ๋ฐํ์ ์ํด ApplicationEventPublisher๋ฅผ ์ฌ์ฉํ๋ค.
- ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ด๋ฒคํธ๋ฅผ ์์ ํด์ ์ฒ๋ฆฌํ๋ค. ์คํ๋ง์ด ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค.
10.3.1 ์ด๋ฒคํธ ํด๋์ค
// ๊ณตํต ์ถ์ ํด๋์ค
package com.myshop.common.event;
public abstract class Event {
private long timestamp;
public Event() {
this.timestamp = System.currentTimeMillis();
}
...์๋ต...
}
public class OrderCanceledEvent extends Event {
// ์ด๋ฒคํธ๋ ํธ๋ค๋ฌ์์ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ํฌํจ
private String orderNumber;
public OrderCanceledEvent(String number) {
super();
this.orderNumber = number;
}
...์๋ต...
}
10.3.2 Events ํด๋์ค์ ApplicationEventPublisher
์ด๋ฒคํธ ๋ฐ์๊ณผ ์ถํ์ ์ํด ์คํ๋ง์ด ์ ๊ณตํ๋ ApplicationEventPublisher
๋ฅผ ์ฌ์ฉํ๋ค.
// ์ค์
@Configuration
public class EventsConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Bean
public InitializingBean eventsInitializer() {
return () -> Events.setPublisher(applicationContext);
}
}
// Events๋ ApplicationEventPublisher๋ฅผ ์ฌ์ฉํด์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์
public class Events {
private static ApplicationEventPublisher publisher;
static void setPublisher(ApplicationEventPublisher publisher) {
Events.publisher = publisher;
}
public static void raise(Object event) {
if (publisher != null) {
publisher.publishEvent(event);
}
}
}
10.3.3 ์ด๋ฒคํธ ๋ฐ์๊ณผ ์ด๋ฒคํธ ํธ๋ค๋ฌ
// ์ด๋ฒคํธ ๋ฐ์
public class Order {
public void cancel() {
...์๋ต...
Events.raise(new OrderCanceledEvent(number.getNumber()));
}
}
// ์ด๋ฒคํธ ํธ๋ค๋ฌ
@Service
public class OrderCanceledEventHandler {
private RefundService refundService;
public OrderCanceledEventHandler(RefundService refundService) {
this.refundService = refundService;
}
@EventListener(OrderCanceledEvent.class)
public void handle(OrderCanceledEvent event) {
refundService.refund(event.getOrderNumber());
}
}
10.3.4 ํ๋ฆ ์ ๋ฆฌ
10.4 ๋๊ธฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ ๋ฌธ์
๋๊ธฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
- ์ธ๋ถ ์ฐ๋ ๊ณผ์ ์์ ์ต์ ์ ์ด ๋ฐ์ํ๋ฉด ํธ๋์ญ์ ์ฒ๋ฆฌ๋?
- ์ด๋ฒคํธ ์ฒ๋ฆฌํ๋ ์ฝ๋๊ฐ ๋๋ ค์ง๊ฑฐ๋ ์ต์ ์ ์ด ๋ฐ์ํ๋ฉด?
10.5 ๋น๋๊ธฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ
๋น๋๊ธฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ก ํด๊ฒฐํ๋ค.
- ๋ก์ปฌ ํธ๋ค๋ฌ๋ฅผ ๋น๋๊ธฐ๋ก ์คํํ๊ธฐ
- ๋ฉ์์ง ํ๋ฅผ ์ฌ์ฉํ๊ธฐ
- ์ด๋ฒคํธ ์ ์ฅ์์ ์ด๋ฒคํธ ํฌ์๋ ์ฌ์ฉํ๊ธฐ
- ์ด๋ฒคํธ ์ ์ฅ์์ ์ด๋ฒคํธ ์ ๊ณต API ์ฌ์ฉํ๊ธฐ
10.5.1 ๋ก์ปฌ ํธ๋ค๋ฌ ๋น๋๊ธฐ ์คํ
์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ๋ณ๋ ์ค๋ ๋๋ก ์คํํ๋ค. ์คํ๋ง์ด ์ ๊ณตํ๋ @Async
์๋ํ
์ด์
์ ์ฌ์ฉํ๋ฉด ์์ฝ๊ฒ ๋น๋๊ธฐ๋ก ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์คํํ ์ ์๋ค.
@SpringBootApplication
@EnableAsync // ๊ธฐ๋ฅ ํ์ฑํ
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}
// ํธ๋ค๋ฌ
@Service
public class OrderCanceledEventHandler {
@Async // @Async ์๋ํ
์ด์
์ฌ์ฉ
@EventListener(OrderCanceledEvent.class)
public void handle(OrderCanceledEvent event) {
refundService.refund(event.getOrderNumber());
}
}
10.5.2 ๋ฉ์์ง ์์คํ ์ ์ด์ฉํ ๋น๋๊ธฐ ๊ตฌํ
Kafka๋ RabbitMQ์ ๊ฐ์ ๋ฉ์์ง ์์คํ ์ ์ฌ์ฉ
- RabbitMQ
- ๊ธ๋ก๋ฒ ํธ๋์ญ์ ์ง์
- Kafka
- ๊ธ๋ก๋ฒ ํธ๋์ญ์ ์ง์ X
- ๋ค๋ฅธ ๋ฉ์์ง ์์คํ ์ ๋นํด ๋์ ์ฑ๋ฅ
10.5.3 ์ด๋ฒคํธ ์ ์ฅ์๋ฅผ ์ด์ฉํ ๋น๋๊ธฐ ์ฒ๋ฆฌ
์ด๋ฒคํธ๋ฅผ ์ผ๋จ DB์ ์ ์ฅํ ๋ค์ ๋ณ๋ ํ๋ก๊ทธ๋จ์ ์ด์ฉํด์ ์ด๋ฒคํธ ํธ๋ค๋ฌ์ ์ ๋ฌ
API๋ฐฉ์๊ณผ ํฌ์๋ ๋ฐฉ์์ ์ฐจ์ด์ ์ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ๋ ๋ฐฉ์์ ์๋ค.
- ํฌ์๋
- ํฌ์๋๋ฅผ ์ด์ฉํด์ ์ด๋ฒคํธ๋ฅผ ์ธ๋ถ์ ์ ๋ฌ.
- ์ด๋ฒคํธ๋ฅผ ์ด๋๊น์ง ์ฒ๋ฆฌํ๋์ง ์ถ์ ํ๋ ์ญํ ์ด ํฌ์๋์ ์๋ค.
- API
- ์ธ๋ถ ํธ๋ค๋ฌ๊ฐ API ์๋ฒ๋ฅผ ํตํด ์ด๋ฒคํธ ๋ชฉ๋ก์ ๊ฐ์ ธ๊ฐ.
- ์ด๋ฒคํธ๋ฅผ ์ด๋๊น์ง ์ฒ๋ฆฌํ๋์ง ์ถ์ ํ๋ ์ญํ ์ด ์ธ๋ถ ํธ๋ค๋ฌ์ ์๋ค.
๊ตฌํ ๋ฐฉ๋ฒ์ ์ฑ
์ฐธ๊ณ
โฆ์๋ตโฆ๐
์๋ ์ฆ๊ฐ ์นผ๋ผ ์ฃผ์ ์ฌํญ
ํธ๋์ญ์
์ปค๋ฐ ์์ ์ ๋ฐ๋ฅธ ์๋ ์ฆ๊ฐ ์นผ๋ผ ๋ฌธ์
์๋ ์ฆ๊ฐ ์นผ๋ผ ์ฃผ์ ์ฌํญ ๋งํฌ
// ํธ๋์ญ์
์ปค๋ฐ ์์ ์ ๋ฐ๋ฅธ ID ๊ฐ
์๊ฐ ํ๋ฆ --->
ํธ๋์ญ์
1: 1, 2 5, 6
ํธ๋์ญ์
2: 3, 4 7, 8
10.6 ์ด๋ฒคํธ ์ ์ฉ ์ ์ถ๊ฐ ๊ณ ๋ ค ์ฌํญ
์ด๋ฒคํธ ์ ์ฉ ์ ๋ค์ ๊ณ ๋ ค์ฌํญ์ ์๊ฐํด๋ณธ๋ค.
- ์ด๋ฒคํธ ์์ค๋ฅผ EventEntry์ ์ถ๊ฐํ ์ง?
- EventEntry๋ ์ด๋ฒคํธ ๋ฐ์ ์ฃผ์ฒด์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ง ์๋๋ค.
- ํน์ ์ฃผ์ฒด๊ฐ ๋ฐ์์ํจ ์ด๋ฒคํธ๋ง ์กฐํํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์๋ค.
- ์ด ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ค๋ฉด ์ด๋ฒคํธ์ ๋ฐ์ ์ฃผ์ฒด ์ ๋ณด๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
- ํฌ์๋์์ ์ ์ก ์คํจ๋ฅผ ์ผ๋ง๋ ํ์ฉํ ์ง?
- ํฌ์๋๋ ์ด๋ฒคํธ ์ ์ก์ ์คํจํ๋ฉด ์คํจํ ์ด๋ฒคํธ๋ถํฐ ๋ค์ ์ฝ์ด์ ์ ์ก์ ์๋ํ๋ค.
- ํน์ ์ด๋ฒคํธ๊ฐ ๊ณ์ ์คํจํ๋ค๋ฉด?
- ์คํจํ ์ด๋ฒคํธ์ ์ฌ์ ์ก ํ์ ์ ํ์ ๋์ด์ผ ํ๋ค.
- ์คํจํ ์ด๋ฒคํธ๋ ์คํจ์ฉ DB๋ ๋ฉ์์ง ํ์ ์ ์ฅํ๋ค.
- ์ด๋ฒคํธ ์์ค์?
- ์ด๋ฒคํธ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ฒคํธ ๋ฐ์๊ณผ ์ด๋ฒคํธ ์ ์ฅ์ ํ ํธ๋์ญ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ํธ๋์ญ์ ์ ์ฑ๊ณตํ๋ฉด ์ด๋ฒคํธ๊ฐ ์ ์ฅ์์ ๋ณด๊ด๋๋ค๋ ๊ฒ์ ๋ณด์ฅํ ์ ์๋ค.
- ์ด๋ฒคํธ๋ฅผ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํ ๊ฒฝ์ฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ์ ์คํจํ๋ฉด ์ด๋ฒคํธ๋ฅผ ์ ์คํ๊ฒ ๋๋ค.
- ์ด๋ฒคํธ ์์๋?
- ์ด๋ฒคํธ ๋ฐ์ ์์๋๋ก ์ธ๋ถ ์์คํ ์ ์ ๋ฌํด์ผ ํ ๊ฒฝ์ฐ๋ ์ด๋ฒคํธ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ค.
- ๋ฉ์์ง ์์คํ ์ ์ฌ์ฉ ๊ธฐ์ ์ ๋ฐ๋ผ ์ด๋ฒคํธ ๋ฐ์ ์์์ ๋ฉ์์ง ์์๊ฐ ๋ค๋ฅผ ์ ์๋ค.
- ์ด๋ฒคํธ ์ฌ์ฒ๋ฆฌ๋?
- ์ด๋ฒคํธ์ ์๋ฒ์ ๊ธฐ์ตํ๋ค.
- ์ด๋ฒคํธ ๋ฉฑ๋ฑ์ฑ์ผ๋ก ์ฒ๋ฆฌํ๋ค
๋ฉฑ๋ฑ์ฑ์ด๋?
์ฐ์ฐ์ ์ฌ๋ฌ๋ฒ ์ ์ฉํด๋ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง์ง ์๋ ์ฑ์ง์ ๋ฉฑ๋ฑ์ฑ์ด๋ผ๊ณ ํ๋ค.
10.6.1 ์ด๋ฒคํธ ์ฒ๋ฆฌ์ DB ํธ๋์ญ์ ๊ณ ๋ ค
์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ๋๊ธฐ๋ก ํ๋ ๋น๋๊ธฐ๋ก ํ๋ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์คํจ์ ํธ๋์ญ์ ์คํจ๋ฅผ ํจ๊ป ๊ณ ๋ คํด์ผ ํ๋ค. ํธ๋์ญ์ ์คํจ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์คํจ ๋ชจ๋ ๊ณ ๋ คํ๋ฉด ๋ณต์กํด์ง๋ฏ๋ก ๊ฒฝ์ฐ์ ์๋ฅผ ์ค์ด๋ฉด ๋์์ด ๋๋ค. ๊ฒฝ์ฐ์ ์๋ฅผ ์ค์ด๋ ๋ฐฉ๋ฒ์ ํธ๋์ญ์ ์ด ์ฑ๊ณตํ ๋๋ง ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์คํํ๋ ๊ฒ์ด๋ค.
์คํ๋ง์ @TransactionalEventListener
์๋ํ
์ด์
์ ์ง์ํ๋ค. ์ด ์ ๋ํ
์ด์
์ ํธ๋์ญ์
์ํ์ ๋ฐ๋ผ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์คํํ ์ ์๊ฒ ํ๋ค.
@TransactionalEventListener(
classes = OrderCanceledEvent.class,
phase = TransactionPhase.AFTER_COMMIT
)
public void handle(OrderCanceledEvent event) {
refundService.refund(event.getOrderNumber());
}
11. CQRS
11.1 ๋จ์ผ ๋ชจ๋ธ์ ๋จ์
์กฐํ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ค๋ฉด ์ฌ๋ฌ ์ ๊ทธ๋ฆฌ๊ฑฐํธ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ผ ํ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ์ด ๋ ์๋ณ์๋ฅผ ์ด์ฉํ ์ฐธ์กฐ ๋ฐฉ์์ด๋ , ์ง์ ์ฐธ์กฐํ๋ ๋ฐฉ์์ด๋ ๊ณ ๋ฏผํด์ผ ํ ๊ฒ๋ค์ด ๋ง๋ค. ์ด๋ ๊ตฌํ ๋ณต์ก๋๋ฅผ ๋์ธ๋ค.
์ด๋ฐ ๊ตฌํ ๋ณต์ก๋๋ฅผ ๋ฎ์ถ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด ๋ฐ๋ก CQRS (Command Query Responsibility Segregation, ๋ช ๋ น ์กฐํ ์ฑ ์ ๋ถ๋ฆฌ)๋ค.
11.2 CQRS
CQRS๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ ๋ชจ๋ธ์ ๋ง๋ ๊ตฌํ ๊ธฐ์ ์ ์ ํํ ์ ์๋ค.
๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ ์กฐํํ๋ ๊ธฐ๋ฅ์ ์์ฉ ์๋น์ค๋ฅผ ์ ์ธํ ์๋ ์๋ค.
CQRS๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ ๋ชจ๋ธ์ ๋ง๋ ๋ฐ์ดํฐ ์ ์ฅ์๋ฅผ ์ ํํ ์ ์๋ค.
๋ช ๋ น ๋ชจ๋ธ์ ํธ๋์ญ์ ์ ์ง์ํ๋ RDBMS๋ฅผ ์ฌ์ฉํ๊ณ , ์กฐํ ๋ชจ๋ธ์ ์กฐํ ์ฑ๋ฅ์ด ์ข์ ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ NoSQL์ ์ฌ์ฉํ ์ ์๋ค.
11.2.1 ์น๊ณผ CQRS
์ผ๋ฐ์ ์ธ ์น ์๋น์ค๋ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ์์ฒญ๋ณด๋ค ์ํ๋ฅผ ์กฐํํ๋ ์์ฒญ์ด ๋ง๋ค. ๋๊ท๋ชจ ํธ๋ํฝ์ด ๋ฐ์ํ๋ ์น ์๋น์ค๋ ์๊ฒ ๋ชจ๋ฅด๊ฒ CQRS๋ฅผ ์ ์ฉํ๊ฒ ๋๋ค. ์กฐํ ์๋๋ฅผ ๋์ด๊ธฐ ์ํด ๋ณ๋ ์ฒ๋ฆฌ๋ฅผ ํ๊ณ ์๋ค๋ฉด CQRS๋ฅผ ์ ์ฉํ์. ์ด๋ฅผ ํตํด ์กฐํ ๊ธฐ๋ฅ ๋๋ฌธ์ ๋ช ๋ น ๋ชจ๋ธ์ด ๋ณต์กํด์ง๋ ๊ฒ์ ๋ง์ ์ ์๊ณ , ๋ช ๋ น ๋ชจ๋ธ์ ๊ด๊ณ์์ด ์กฐํ ๊ธฐ๋ฅ์ ํนํ๋ ๊ตฌํ ๊ธฐ๋ฒ์ ๋ณด๋ค ์ฝ๊ฒ ์ ์ฉํ ์ ์๋ค.
11.2.2 CQRS ์ฅ๋จ์
- ์ฅ์
- ๋ช ๋ น ๋ชจ๋ธ์ ๊ตฌํํ ๋ ๋๋ฉ์ธ ์์ฒด์ ์ง์คํ ์ ์๋ค.
- ์กฐํ ์ฑ๋ฅ ํฅ์์ ์ ๋ฆฌ
- ๋จ์
- ๊ตฌํํด์ผ ํ ์ฝ๋๊ฐ ๋ ๋ง๋ค.
- ๋ ๋ง์ ๊ตฌํ ๊ธฐ์ ์ด ํ์ํ๋ค.
์ฅ๋จ์ ์ ๊ณ ๋ คํด CQRS ํจํด ๋์ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ค. ๋๋ฉ์ธ์ด ๋ณต์กํ์ง ์์๋ฐ CQRS๋ฅผ ๋์ ํ๋ฉด ์ ์ง ๋น์ฉ๋ง ๋์์ง๋ค. ๋ฐ๋ฉด ํธ๋ํฝ์ด ๋์ ์๋น์ค์ธ๋ฐ ๋จ์ผ ๋ชจ๋ธ์ ๊ณ ์งํ๋ฉด ์ ์ง ๋ณด์ ๋น์ฉ์ด ์คํ๋ ค ๋์์ง ์ ์์ผ๋ฏ๋ก CQRS ๋์ ์ ๊ณ ๋ คํ์.
๊ฐ์ฌํฉ๋๋ค ๐๐ปโโ๏ธ
๋๊ธ๋จ๊ธฐ๊ธฐ