CJ์จ์คํ์ผ์ ์๋๋ก์ด๋ ์ฑ 'ํด๋ฆฐ ์ํคํ ์ฒ' ๋์ ๊ธฐ
CJ์จ์คํ์ผ์ ์๋๋ก์ด๋ ์ฑ 'ํด๋ฆฐ ์ํคํ ์ฒ' ๋์ ๊ธฐ ๊ด๋ จ
๊ตญ๋ด IT ๊ธฐ์ ์ ํ๊ตญ์ ๋์ด ์ธ๊ณ๋ฅผ ๋ฌด๋๋ก ํ ์ ๋๋ก ๋ฐ์ด๋ ๊ธฐ์ ๊ณผ ์์ด๋์ด๋ฅผ ์๋ํฉ๋๋ค. ์ด๋ค์ ๊ธฐ์ ๋ธ๋ก๊ทธ๋ฅผ ํตํด ์ด๋ฌํ ์ ๋ณด๋ฅผ ๊ณต๊ฐํ๊ณ ์์ต๋๋ค. ์์ฆIT๋ ๊ฐ ๊ธฐ์ ์ ํน์ ์๊ณ ์ ์ตํ ์ฝํ ์ธ ๋ฅผ ์๊ฐํ๋ ์๋ฆฌ์ฆ๋ฅผ ์ค๋นํ์ต๋๋ค. ์ด๋ค์ ์ด๋ป๊ฒ ์ฌ๊ณ ํ๊ณ , ์ด๋ค ๋ฐฉ์์ผ๋ก ์ผํ๊ณ ์์๊น์?
์ด๋ฒ ๊ธ์์๋ CJ์จ์คํ์ผ ์๋๋ก์ด๋ ๊ฐ๋ฐ์๊ฐ ์๋๋ก์ด๋ ์ ๊ท ํ๋ก์ ํธ์ ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ๋์ ํ ์ด์ ์ ์ด๋ฅผ ํตํด ๊ฐ๋ฐ์์ ํผ๋ก๋๋ฅผ ๊ฐ์์ํจ ๊ฒฝํ์ ๊ณต์ ํฉ๋๋ค.
์ํํธ์จ์ด ๊ฐ๋ฐ์ ๋ณต์กํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ ์ด์์ ํ์๋ก ํฉ๋๋ค. ๋จ์ํ ๊ฒฐ๊ณผ๋ก ์ผ๋ก ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๊ฒ๋ง ์๊ฐํ๊ณ ๊ฐ๋ฐํ๋ค๋ฉด, ์ฝ๋๋ ์ ์ฐจ ์ปค์ง๋ฉฐ ๋์ค์๋ ์๋ณผ ์ ์์ ์ ๋๋ก ๋ํญํ(?) ์ฝ๋๋ก ๋ณํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ ๊ธฐ์ ๊ฐ๋ฐํ๋ค๋ ๊ฒ์ ์ ์ง๋ณด์์ฑ, ํ์ฅ์ฑ, ํ
์คํธ ์ฉ์ด์ฑ ๋ฑ์ ์๊ตฌ ์ฌํญ์ ์ถฉ์กฑํ๊ธฐ ์ํ ๊ณ ๋ฏผ์ด ํ์ํ๋ฉฐ, ์ ์ ํ ์ํคํ
์ฒ ์ค๊ณ๊ฐ ํ์์
๋๋ค. ์ด๋ฌํ ๋ชฉํ๋ฅผ ๋ฌ์ฑํ๊ธฐ ์ํ ์ค์ํ ๊ฐ๋
์ค ํ๋๊ฐ ํด๋ฆฐ ์ํคํ
์ฒ
์
๋๋ค.
์ ๋ ์ต APP ๊ตฌ์ถ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ๋์ ํ ์ด์ ์ ๋์ ํ ๊ฐ ๊ณ์ธต(Layer)์ ๊ตฌ์ฑ ์์ ๋ชจ๋ ๊ตฌํํ๊ธฐ ์ํด ์ฆ๊ฐํ ๊ฐ๋ฐ์์ ํผ๋ก๋๋ฅผ ํด๊ฒฐํ ๋ฐฉ๋ฒ์ ๋ํด์ ์๊ฐํด ๋๋ฆฌ๊ฒ ์ต๋๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ๋?
ํด๋ฆฐ ์ํคํ ์ฒ๋ ์ํํธ์จ์ด ์์คํ ์ ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ ๋ ์ง์ผ์ผ ํ ์์น๊ณผ ๋ฐฉ๋ฒ์ ์ ์ํ ๊ฐ๋ ์ ๋๋ค. ์ด ๊ฐ๋ ์ ๋ก๋ฒํธ C. ๋งํด(Robert C. Martin)์ ์ํด ์๊ฐ๋์์ผ๋ฉฐ, ๋ณต์กํ ์ํํธ์จ์ด ์์คํ ์ ๋ณด๋ค ๊ด๋ฆฌ ๊ฐ๋ฅํ๊ณ ์ ์ง๋ณด์ ๊ฐ๋ฅํ ํํ๋ก ๊ตฌ์ถํ๊ธฐ ์ํ ์ง์นจ์ ์ ๊ณตํฉ๋๋ค.
์ด ์ํคํ ์ฒ๋ ์ํํธ์จ์ด์ ์ ์ง๋ณด์์ฑ, ํ ์คํธ ์ฉ์ด์ฑ ๋ฐ ๋ชจ๋ ๊ฐ์ ๋ถ๋ฆฌ๋ฅผ ๊ฐ์กฐํ์ฌ ์๋๋ก์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ๊ตฌ์กฐํ๋ ๋ฐฉ์์ผ๋ก ๊ฐ๋ฐํ ์ ์๋๋ก ๋์ต๋๋ค.
์ ํด๋ฆฐ ์ํคํ ์ฒ๊ฐ ํ์ํ๊ฐ?
์ํํธ์จ์ด๊ฐ ๊ฐ์ง ๋ณธ์ฐ์ ๋ชฉ์ ์ ์ถ๊ตฌํ๋ ค๋ฉด ์ํํธ์จ์ด๋ ๋ฐ๋์ '๋ถ๋๋ฌ์'์ผ ํ๋ค. ๋ค์ ๋งํด ๋ณ๊ฒฝํ๊ธฐ ์ฌ์์ผ ํ๋ค. ์ดํด๊ด๊ณ์๊ฐ ๊ธฐ๋ฅ์ ๋ํ ์๊ฐ์ ๋ฐ๊พธ๋ฉด, ์ด๋ฌํ ๋ณ๊ฒฝ์ฌํญ์ ์ฝ๊ฒ ์ ์ฉํ ์ ์์ด์ผ ํ๋ค.
(๋ก๋ฒํธ C. ๋งํด, ํด๋ฆฐ ์ํคํ ์ฒ)
ํด๋ฆฐ ์ํคํ ์ฒ๋ ์ํํธ์จ์ด ์์คํ ์ ๊ตฌ์กฐ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์ค๊ณํจ์ผ๋ก์จ ์ฌ๋ฌ ๊ฐ์ง ์ฅ์ ์ ์ ๊ณตํฉ๋๋ค.
๊ฐ์ฅ ์ค์ํ ์ฅ์ ์ค ํ๋๋ ์์คํ ์ ๊ฐ ๋ถ๋ถ์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐํ๊ณ ํ ์คํธํ ์ ์๋ ํ๊ฒฝ์ ์กฐ์ฑํ ์ ์๋ค๋ ์ ์ ๋๋ค. ๋ํ ์์คํ ์ ๋ณ๊ฒฝ์ด๋ ์ ๊ทธ๋ ์ด๋๊ฐ ํ์ํ ๋ ์ ์ฒด ์์คํ ์ ๋ค์ ์์ฑํ์ง ์์๋ ๋๋ฉฐ, ํน์ ๋ถ๋ถ๋ง ์์ ํ๋ ๊ฒ์ด ๊ฐ๋ฅํด์ง๋๋ค.
์๋ฅผ ๋ค์ด๋ณด๋ฉด Network ํต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Retrofit์ ConvertFactory๋ฅผ Moshi Factory๋ก ์ฌ์ฉํ๋ ํ๋ก์ ํธ๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๊ฒ ์ต๋๋ค. ์ด๋ ๋ Moshi Facory๊ฐ ์๋ GsonconvertFactory
์ผ๋ก ์ ๋ฉด ๋ณ๊ฒฝํ๋ผ๋ ๊ธฐ์ ์ง์นจ์ด ๋ด๋ ค์จ๋ค๋ฉด ๊ฐ๋ฐ์๋ ํ๋ก์ ํธ ์์์ Network ํต์ ํ๋ ๊ณณ์ ๋ชจ๋ ์ฐพ์์ Moshi Factory ๋ฅผ GsonconvertFactory
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ณ๊ฒฝํ๋ ๊ท์ฐฎ์๊ณผ ์ฝ๋๋ฅผ ์ฐพ๋ ์๊ณ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๋ง์ฝ ํด๋ฆฐ ์ํคํ ์ฒ๋ก ๋ณ๊ฒฝ๋์๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? Network ํต์ ์ ๋ด๋นํ๋ Layer ๋ง ํ์ธ ํ Network ํต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ณ๊ฒฝ์์ ํ๋ฉด ๋ฉ๋๋ค. ์ด๋ ๊ฒ ๋๋ค๋ฉด, ํด๋จผ ์๋ฌ๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ณ๊ฒฝ ์ฒ๋ฆฌ๋ฅผ ๋์น๋ ์ผ์ด์ค๋ ์์ ๊ฒ์ ๋๋ค.
์ฃผ์ ์์น
ํด๋ฆฐ ์ํคํ ์ฒ์ ์ฃผ์ ์์น์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์์กด์ฑ ์ญ์ ์์น(Dependency Inversion Principle) : ๊ณ ์์ค ๋ชจ๋์ ์ ์์ค ๋ชจ๋์ ์์กดํด์๋ ์๋๋ฉฐ, ์์ชฝ ๋ชจ๋ ๋ชจ๋ ์ถ์ํ์ ์์กดํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋์จํ ๊ฒฐํฉ์ ์ ์งํ ์ ์์ต๋๋ค.
- ๊ฒฝ๊ณ(Boundary)์ ๋ถ๋ฆฌ : ์์คํ ์ ์ฌ๋ฌ ์์ญ์ผ๋ก ๋๋๊ณ , ๊ฐ ์์ญ ์ฌ์ด์ ์ธํฐํ์ด์ค๋ฅผ ์ ์ํ์ฌ ๊ฐ ์์ญ์ ๋ ๋ฆฝ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
- ์ธํฐํ์ด์ค ๋ถ๋ฆฌ ์์น(Interface Segregation Principle) : ํด๋ผ์ด์ธํธ๊ฐ ์์ ์ด ์ฌ์ฉํ์ง ์๋ ๋ฉ์๋์ ์์กดํ์ง ์์์ผ ํฉ๋๋ค. ์ฆ, ์ธํฐํ์ด์ค๋ ํด๋ผ์ด์ธํธ์ ์๊ตฌ์ ๋ฑ ๋ง๋ ํํ๋ก ๋ถ๋ฆฌ๋์ด์ผ ํฉ๋๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ ๊ณ์ธต
ํด๋ฆฐ ์ํคํ ์ฒ๋ ๋ค์ํ ๊ณ์ธต(Layer)์ผ๋ก ๊ตฌ์ฑ๋๋ฉฐ, ๊ฐ ๊ณ์ธต์ ํน์ ํ ์ญํ ๊ณผ ์ฑ ์์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ์ ์ฃผ์ ๊ตฌ์ฑ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
ํ๋ ์ ํฐ(Presenter) ๋๋ ๋ทฐ๋ชจ๋ธ(ViewModel) : ์ฌ์ฉ์ ์ธํฐํ์ด์ค(UI)์ ๋น์ฆ๋์ค ๋ก์ง ๊ฐ์ ์ค๊ฐ ๊ณ์ธต์ผ๋ก, UI์์ ๋ฐ์ํ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ณ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋น์ฆ๋์ค ๋ก์ง์ ์ ๋ฌํฉ๋๋ค. ์ ์ค์ผ์ด์ค(Use Case) : ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค์ ๋น์ฆ๋์ค ๋ก์ง์ ํฌํจํ๋ ๋ถ๋ถ์ผ๋ก, ํ๋ ์ ํฐ๋ ๋ทฐ๋ชจ๋ธ๋ก๋ถํฐ ์ ๋ฌ๋ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ์ฌ ๋ฐํํฉ๋๋ค. ๋ฆฌํฌ์งํ ๋ฆฌ(Repository) : ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์ธ๋ถ ๋ฐ์ดํฐ ์๋ณธ๊ณผ์ ์ํธ ์์ฉ์ ๋ด๋นํ๋ ๋ถ๋ถ์ผ๋ก, ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ ์ฅํ๋ ์์ ์ ์ํํฉ๋๋ค. ์ ์ค์ผ์ด์ค๋ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ป์ด์ต๋๋ค. ์ํฐํฐ(Entity) : ์ ํ๋ฆฌ์ผ์ด์ ์ ํต์ฌ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ํ๋ด๋ฉฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ๋คํธ์ํฌ์์ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ฒด๋ก ๋ณํํ ํํ์ ๋๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ์ ์ฅ์ ์ ๋ฌด์์ผ๊น?
ํด๋ฆฐ ์ํคํ ์ฒ์ ์ฃผ์ ์ฅ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ ์ง๋ณด์ ์ฉ์ด์ฑ : ๊ฐ ๊ณ์ธต์ด ๋ถ๋ฆฌ๋์ด ์๊ธฐ ๋๋ฌธ์ ํ ๊ณ์ธต์ ๋ณ๊ฒฝํด๋ ๋ค๋ฅธ ๊ณ์ธต์ ์ํฅ์ ๋ฏธ์น์ง ์์ ์ ์ง๋ณด์๊ฐ ์ฝ์ต๋๋ค. ํ ์คํธ ์ฉ์ด์ฑ : ์์กด์ฑ์ ์ฃผ์ ํ์ฌ ์ ๋ ํ ์คํธ ๋ฐ ํตํฉ ํ ์คํธ๋ฅผ ์ํํ๊ธฐ ์ฉ์ดํฉ๋๋ค. ๋ชจ๋ ๊ฐ์ ๋ถ๋ฆฌ : ๊ฐ ๊ณ์ธต์ด ์์ฒด ์ญํ ์ ๊ฐ์ง๋ฉฐ, ์ด๋ก ์ธํด ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ์ด ๋์์ง๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ UI ํ๋ ์์ํฌ ๋ณ๊ฒฝ ์ฉ์ด์ฑ : ์ค์ํ ๋น์ฆ๋์ค ๋ก์ง์ ์ธ๋ถ ํ๋ ์์ํฌ์ ๋ถ๋ฆฌ๋์ด ์์ด ํด๋น ํ๋ ์์ํฌ๋ฅผ ๋ณ๊ฒฝํ๋๋ผ๋ ๊ธฐ์กด์ ์์ฑํ ์ ์ฒด ์ฝ๋๋ฅผ ๋ค์ ์์ฑํ ํ์๊ฐ ์์ต๋๋ค.
์ ๋ ์ต ํ๋ก์ ํธ X ํด๋ฆฐ ์ํคํ ์ฒ
์ ํฌ ํ์ ์ต๊ทผ ์ ๋ ์ต APP ๊ตฌ์ถ ํ๋ก์ ํธ์์ ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ์ ์ฉํ์ฌ ๋ง์ ์ด์ ์ ์ป์์ต๋๋ค.
๊ธฐ๋ฅ ๊ตฌํ์ ์์ด์ ๋ค์์ ๊ฐ๋ฐ์๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐํ ์ ์์์ผ๋ฉฐ, ์์กด์ฑ์ด ๋ฎ์ ๊ฐ๋ฐํ ์์ค์ ๋ํ ์ํฅ์ ๋ฏธ์น์ง ์๋ ์ฅ์ ์ ๊ฒฝํํ์ต๋๋ค. ๋ํ ๊ณ์ธต(Layer)์ ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธํ ์ ์๋ ํ๊ฒฝ์ ๊ตฌ์ถํ์ฌ ํ์ง ์ ์ง๋ ์ด์ ํ๋ก์ ํธ์ ๋นํด ํฅ์๋์์ต๋๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ์ ๋์ ํ์๋?
๊ธฐ์กด ํ๋ก์ ํธ๋ MVVM(Model-View-ViewModel) ์ํคํ ์ฒ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ๊ฐ๋ฐ์๊ฐ ๋ง์์ง ๋งํผ ํ๋ก์ ํธ๊ฐ ์ปค์ง๋ฉฐ ๊ด๋ฆฌ์ธก๋ฉด์์ ์ด๋ ค์์ ๊ฒฝํํ์์ต๋๋ค.
์์กด์ฑ์ ์ต์ํํ์ฌ ๋ ๋ฆฝ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๊ณ ๋ฏผํ์๊ณ , ์ด๋ฏธ ๋ง์ ๋น ํ ํฌํ์ฌ์์ ์ฌ์ฉํ๊ณ ์๋ Clean Architecture ๋์ ์ ๊ฒํ ํ์์ต๋๋ค.
- ์ ์ง ๋ณด์์ฑ ํฅ์ : Clean Architecture๋ ์์กด์ฑ ๊ด๋ฆฌ ๋ฐ ๋ชจ๋ํ๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ง ๋ณด์์ฑ์ด MVVM ๋ณด๋ค ํฅ์๋ฉ๋๋ค. ๊ฐ ๊ณ์ธต(Layer)์ ๋ช ํํ๊ฒ ์ ์๋๊ณ ๋ถ๋ฆฌ๋์ด ์์ผ๋ฏ๋ก ๋ณ๊ฒฝ ์๊ตฌ์ฌํญ์ ๋ง์ถฐ ๋ณ๊ฒฝ์ด ์ฝ๊ฒ ์ด๋ฃจ์ด์ง ์ ์์ต๋๋ค. ํ ์คํธ ์ฉ์ด์ฑ : ์ ์ง ๋ณด์์ฑ ํฅ์๊ณผ ๋์ผ ์ด์ ๋ก ๊ฐ ๊ณ์ธต์ด ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธ ๊ฐ๋ฅํ๋๋ก ์ค๊ณ๋์ด ์์ด ํ ์คํธ ์ฉ์ด์ฑ์ ๋์ผ ์ ์์ต๋๋ค. ์ ๋ ํ ์คํธ, ํตํฉ ํ ์คํธ ๋ฑ์ ๋ณด๋ค ์ฝ๊ฒ ํ ์คํธ๋ฅผ ์งํํ ์ ์์ต๋๋ค. ๋ฏธ๋ ํ์ฅ์ฑ : ์๋ก์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ฑฐ๋ ๊ธฐ์กด ๊ธฐ๋ฅ์ ๋ณ๊ฒฝํ๋ ค๋ ๊ฒฝ์ฐ ๋ณ๊ฒฝ์ ๋ฒ์๋ฅผ ์ต์ํํ๋ฉฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ฅ ์ ์ฉํฉ๋๋ค. MVVM์ ๋ทฐ์ ๋ทฐ ๋ชจ๋ธ ๊ฐ์ ๊ด๊ณ์ ์ค์ ์ ๋๋ ๋ฐ๋ฉด Clean Architecture๋ ์์คํ ์ ์ ๋ฐ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๊ณ ๋ คํฉ๋๋ค. ๋น์ฆ๋์ค ๋ก์ง ๋ถ๋ฆฌ : ๋น์ฆ๋์ค ๋ก์ง์ ๋ค๋ฅธ ๊ณ์ธต์ผ๋ก ๋ถ๋ฆฌํ์ฌ ๋น์ฆ๋์ค ๋ฃฐ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ์ ๋ถ๋ฆฌ์ํต๋๋ค. ์ด๊ฒ์ ๋น์ฆ๋์ค ๋ก์ง์ ๋จ์ผ ์์น์์ ์ ์งํ๊ณ ์ดํดํ๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค. MVVM๋ ๋น์ฆ๋์ค ๋ก์ง์ ViewModel์์ ์ฒ๋ฆฌํ์ฌ ๋ ๋ฆฝ์ ์ผ๋ก ์ด์ํ ์ ์์ต๋๋ค. ํ์ง๋ง ํ๋ก์ ํธ๊ฐ ์ปค์ง์ ๋ฐ๋ผ ๋น์ฆ๋์ค ๋ก์ง์ ViewModel์์ ๊ด๋ฆฌํ๊ธฐ์๋ ์ด๋ ค์ธ ์ ์์ต๋๋ค.
์ 4๊ฐ์ง ์ธก๋ฉด๊ณผ ํจ๊ป ํ๋ก์ ํธ ํ์ฅ์ฑ์ ๊ณ ๋ คํ์ ๋ ํด๋ฆฐ ์ํคํ ์ฒ์ ๋์ ์ ์ฃผ์ ํ ์ด์ ๊ฐ ์์์ต๋๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ์ ์ฉํ๋ฉฐ
ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ Android ์ ๊ตฌ์กฐ์ ๋ง์ถฐ์ ๊ฐ๋ฐํ๋ ๊ณผ์ ์ ๋์นจํ์ ๋ค๊ณ ํฅํดํ๋ ๊ธฐ๋ถ์ด์์ต๋๋ค. ๊ทธ ์ด์ ๋ ์ด๋ฏธ Google Developer Document(Guilde to app architecture ์์ Android ์ ํด๋ฆฐ์ํคํ ์ฒ์ ๋ํด ๊ธฐ์ ๋์ด ์์ผ๋ฉฐ, ๋ค์์ ๋ธ๋ก๊ทธ์์ ํด๋ฆฐ์ํคํ ์ฒ ์ ์ฉ์ ๋ํด์ ๋ค๋ค์ง๊ณ ์์ต๋๋ค.
์ ํฌ ํ์ ํด๋ฆฐ ์ํคํ ์ฒ ๋์ ์ ํ๋ฉด์ View์ ๋ํ ๋น์ฆ๋์ค ๋ก์ง์ View Model์์ ๊ด๋ฆฌํ๊ณ ์ถ์์ต๋๋ค. ๊ทธ๋ ๊ฒ ํ๊ธฐ ์ํด์๋ MVVM ์ํคํ ์ฒ ๊ฐ๋ ์ ์ผ๋ถ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ๋ํด์ ๊ณ ๋ฏผ์ด ํ์ํ์ต๋๋ค.
๊ทธ ๊ฒฐ๊ณผ ํด๋ฆฐ ์ํคํ ์ฒ ๊ณ์ธต(Layer) ์ค Presentation Layer ๋ฅผ MVVM์ View ์ ViewModel๋ก ์ ์ํ์๊ณ , Domain ๊ณ์ธต(Layer) ๊ณผ Data ๊ณ์ธต(Layer)์ MVVM์ Model๋ก ์ ์ํ์์ต๋๋ค.
ํ๋ก ๊ฐ๋ตํ๊ฒ ํํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Clean Architecture | Presentation Layer | Domain Layer | Data Layer |
---|---|---|---|
MVVM | View, View Model | Model | Model |
Android ๊ฐ๋ฐ ์ ํด๋ฆฐ ์ํคํ
์ฒ์ ๊ฐ ๊ณ์ธต(Layer)๋ ๋ชจ๋(Module)
๋ก ๊ตฌ์ฑํ์์ต๋๋ค. ๊ณ์ธต(Layer)์ ๋ชจ๋๋ก ๊ตฌ์ฑํ ์ด์ ๋ ๋ชจ๋์ ๋ค์ํ ์ด์ ์ ํ์ฉํ๊ธฐ ์ํด ์ ํํ ๊ฒ์
๋๋ค. ์ด๋ฌํ ์ด์ ์ผ๋ก๋ ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ, ์ ์ง๋ณด์ ์ฉ์ด์ฑ, ๋ณ๋ ฌ ๊ฐ๋ฐ ๊ฐ๋ฅ์ฑ, ๊ฐ๋
์ฑ ํฅ์, ๋ ๊ฑฐ์ ์ฝ๋ ๊ด๋ฆฌ์ ํจ์จ์ฑ, ๊ทธ๋ฆฌ๊ณ ์ ๋ฐ์ ์ธ ๊ฐ๋ฐ ์๋ ํฅ์ ๋ฑ์ด ์์ต๋๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ ๊ตฌ์ฑ์์ ์ฐ๊ฒฐ
ํด๋ฆฐ ์ํคํ ์ฒ์์ ๊ณ์ธต(Layer)๊ณผ ๊ตฌ์ฑ ์์ ๊ฐ์ ์ฐ๊ฒฐ์ ์ธํฐํ์ด์ค๋ฅผ ํ์ฉํ์ฌ ๊ตฌ์ฑ๋๋ฉฐ, ๋ค๋ฅธ ๊ตฌ์ฑ ์์ ๊ฐ์ ์์กด์ฑ์ ์์กด์ฑ ์ฃผ์ (DI)์ ํตํด ์ต์ํํ์์ต๋๋ค.
์๋ฅผ ๋ค์ด Best Item ์ ๊ฐ์ ธ์ค๋ UseCase ์ ๊ตฌํํ๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค.
BestItemUseCase
๋ Domain ๋ชจ๋(Module)์ ์์นํ๊ฒ ๋๋ฉฐ ์ด UseCase
๋ ๋์ผ ์์น(Domain)์ ์๋ Repository์ getBestItems()
์ ํธ์ถํ๊ฒ ๋ฉ๋๋ค.
class BestItemUseCase(
private val repository: BestItemRepository
) : BaseUseCase<String, BestItemEntity>() {
override suspend fun execute(parameters: String?): Result<BestItemEntity> {
return
// todo.. result ์ฑ๊ณต, ์คํจ์ ๋ํ ์ฒ๋ฆฌ ๋ก์ง ์ถ๊ฐ
}
}
}
์ด๋ ์ ์ํ BestItemRepository
๋ interface
๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
interface BestItemRepository {
suspend fun getBestItemItems(): Result<BestItemEntity>
}
BestItemUseCase
์ Repository
๋ DI๋ก ์ ์ํ์ฌ @Provides
๋ฅผ ๊ตฌ์ฑํ์ฌ์ผ ํฉ๋๋ค. API ํต์ ์ Activity์ Lifecycle์ ๋ฐ๋ฅด๋๋ก DI Component๋ฅผ ActivityRetainedComponent
๋ก ์ ์ํ์์ต๋๋ค.
@Module
@InstallIn(ActivityRetainedComponent::class)
class BestItemUseCaseModule{
@Provides
fun provideBestItemUseCase(repository: BestItemRepository) = BestItemUseCase(repository)
}
BestItemRepository
์ implement
์ฒ๋ฆฌ๋ Data ๋ชจ๋(Module) ์์ ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค. Repository๋ DataSource๋ฅผ ํธ์ถํ๊ฒ ๋๋ฉฐ DI๋ก Repository์ Datasource์ ์์กด์ ๊ตฌ์ฑํฉ๋๋ค.
class BestItemRepositoryImpl @Inject constructor(
private val remote: BestItemRemoteDataSource
) : BestItemRepository {
override suspend fun getBestItems(): Result<BestItemEntity> {
val response = remote.getBestItemItems()
// todo.. response์ ๋ํ ์ฒ๋ฆฌ ๋ก์ง ์ถ๊ฐ
}
}
Repository ์ ๋ํ @Provides
์ ์ ์๋ Data ๋ชจ๋(Module) ์ ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, UseCase์ ๋์ผํ๊ฒ DI Component๋ฅผ ActivityRetainedComponent
๋ก ์ ์ํ์์ต๋๋ค.
@Module
@InstallIn(ActivityRetainedComponent::class)
class BestItemRepositoryModule {
@Provides
fun provideBestItemRepository(
remote: BestItemRemoteDataSource
): BestItemRepository = BestItemRepositoryImpl(remote)
}
๋๋ฉ์ธ ๊ณ์ธต(Layer)๊ณผ ๋ฐ์ดํฐ ๊ณ์ธต(Layer) ๊ฐ์ ์ฐ๊ฒฐ์ DI(Dependency Injection)๋ฅผ ํ์ฉํ์ฌ ์ค์ ํจ์ผ๋ก์จ, ์์กด์ฑ์ ์ ์ ํ ๊ด๋ฆฌํ๊ณ ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
API ์ ๋ฐ๋ฅธ ConvertFactory ์ค์ ๋ณ๊ฒฝ
ํด๋ฆฐ ์ํคํ ์ฒ์ ์ฅ์ ์ ์ ์ฐํ ์ํํธ์จ์ด๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ๋๋ค. ์ ์ฐํ ์ํํธ์จ์ด๋ฅผ ์ ์ฉ ์ฌ๋ก๋ก API Response์ ๋ฐ๋ฅธ Retrofit ConvertFactory ๋ณ๊ฒฝ์ ์๊ฐํ๋ ค๊ณ ํฉ๋๋ค.
์ ํฌ ํ์์ ์ฌ์ฉํ๋ Network Library๋ Retrofit ์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๋ง์ฝ API ์๋ต์ด Type
๊ตฌ์ฑ์ ๋ฐ๋ผ ๋ค์ํ๊ฒ ๋ณํ ๊ฒฝ์ฐ, GsonConvertFactory๋ฅผ ์ฌ์ฉํ์ฌ API ์๋ต์ DTO๋ก ๊ฐ๋ณ์ ์ผ๋ก ๋ณํํ๋ ๋ฐฉ์์ ์ฝ๋์ ๊ฐ๋
์ฑ ๋ฌธ์ ๋ฟ๋ง ์๋๋ผ ์ธ๊ฐ ์๋ฌ(human error) ๋ฐ์ ๊ฐ๋ฅ์ฑ๋ ์ฆ๊ฐํฉ๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Type
๊ตฌ์ฑ์ ๋ฐ๋ฅธ ๋์ ์ผ๋ก DTO์ ์ ํํ ์ ์๋๋ก Convert Factory๋ฅผ ๊ตฌ์ฑํ๋ฉด, ์ฝ๋๋ฅผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์๋ API ์๋ต ์ ์๋ฅผ ์ค์ ํ๋ ๋ฐ ์ง์คํ ์ ์์ผ๋ฉฐ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ฒ ์ป์ ์ ์์ต๋๋ค. ์ ํฌ ํ์ Data ๊ณ์ธต(Layer)์ DataSource์์ Dynamic Response ๊ฐ ํ์ํ๋ฉด MoshiConvertFactory
๋ฅผ ์ฌ์ฉํ์๊ณ , ๊ทธ ์ธ์ ๋ชจ๋ ํต์ ์ ๋ํด์๋ GsonConvertFactory
์ ์ฌ์ฉํ ์ ์๋๋ก ๊ตฌ์ฑํ์์ต๋๋ค.
Dynamic Response ์ ๋ฐ๋ฅธ MoshiConverterFactory
๊ตฌ์ฑ์ Retorfit ํธ์ถ๋ถ์์ Moshi Factory ์ ์ํ ์ ์๋๋ก ๊ตฌ์ฑํ์๊ณ , ์ ๋ฌ๋ฐ์ Factory๋ก API ํต์ ํ ์ ์๋๋ก ๊ตฌ์ฑํ์์ต๋๋ค.
class MoshiApiClient<FACTORY, CLAZZ>(
private val factory: PolymorphicJsonAdapterFactory<FACTORY>,
private val url: String,
private val clazz: Class<CLAZZ>
) {
@Inject
fun create(client: OkHttpClient): CLAZZ {
val moshi = Moshi.Builder()
.add(factory)
.add(KotlinJsonAdapterFactory())
.build()
```eturn Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(client)
.build()
.create(clazz)
}
}
Default ํต์ ์ GsonconvertFactory
๋ฅผ ์ฌ์ฉํ์๊ณ , Factory๊ตฌ์ฑ์ Retrofit.Builder
์์ ์ฒ๋ฆฌํ์์ต๋๋ค.
class ApiClient<T>(
private val networkService: BaseNetworkService,
private val clazz: Class<T>
) {
@Inject fun create(client: OkHttpClient): T {
return Retrofit.Builder()
.baseUrl(networkService.hostUrl())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
.create(clazz)
}
}
ํด๋ฆฐ ์ํคํ ์ฒ ๋์ ์ผ๋ก ์ธํ ๊ฐ๋ฐ์์ ํผ๋ก๋
ํด๋ฆฐ ์ํคํ ์ฒ๋ ์ ์ฐํ ์ํํธ์จ์ด๋ฅผ ๊ตฌ์ฑํ๋๋ฐ ๋์์ด ๋๋ ์ํคํ ์ฒ๋ ๋ถ๋ช ํฉ๋๋ค. ํ์ง๋ง ํด๋ฆฐ ์ํคํ ์ฒ์ ๊ฐ ๊ณ์ธต(Layer)๊ณผ ๊ตฌ์ฑ์์๋ฅผ ๋ชจ๋ ๊ตฌํํ๋ ๊ฒ์ ์ํฉ์ ๋ฐ๋ผ์ ๋ถํ์ํ ์์ ์ ๊ฐ๋ฐํด์ผ ํ๋ ๊ฐ๋ฐ์์ ํผ๋ก๋๋ฅผ ์ฆ๊ฐ์ํฌ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ์ ๊ท API ๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค๊ณ ๊ฐ์ ํ๋ค๋ฉด UseCase, Repository, Datasource ๋ฅผ ๊ตฌ์ฑํด์ผ ํ๋ฉฐ ๊ฐ ๊ตฌ์ฑ์์๋ interface์ implement ํ class ์์ฑํด์ผ ํฉ๋๋ค. API ๊ฐ๋ฐํ๋๋ฐ 6๊ฐ์ Class๋ฅผ ์์ฑํด์ผ ํ๋ฉฐ, ๋ ๋์๊ฐ Model์ Entity๋ก ๋ณ๊ฒฝํ Mapper ๋ ๊ตฌ์ฑํ๊ฒ ๋ฉ๋๋ค. ๊ทธ๋ผ API ํ๋ ์ถ๊ฐํ๋๋ฐ ์ต์ 9๊ฐ ์ด์์ Class๋ฅผ ๊ตฌ์ฑํ๊ฒ ๋ฉ๋๋ค.
Layer Create Template ๊ฐ๋ฐ
์ ํฌ ํ์ ๊ฐ๋ฐ์์ ํผ๋ก๋๋ฅผ ์ค์ด๊ธฐ ์ํด์ ๊ฐ์ฅ ๋จผ์ ํ ๊ฒ์ ๊ณ์ธต(Layer) ์์ฑ์ ์๋ํ์ ๋๋ค. ์์ ์์์ฒ๋ผ API ํธ์ถ์ ํ๊ธฐ ์ํด์๋ ๊ฐ๋ฐ์๊ฐ ๋ชจ๋ ๊ณ์ธต(Layer)์ ๊ตฌ์ฑ์์๋ฅผ ์์ฑํด์ผ ํ์ต๋๋ค.
์ ํฌ ํ์ ์๋์ผ๋ก ๊ณ์ธต(Layer)์ ์์ฑํ๋ค๋ฉด ๊ฐ๋ฐ์์ ํผ๋ก๋๊ฐ ์ค์ฌ๋ค ๊ฒ์ผ๋ก ํ๋จํ์๊ณ , ํด๋ฆฐ ์ํคํ ์ฒ์ Damain ๊ณ์ธต(Layer)๊ณผ Data ๊ณ์ธต(Layer)์ ์๋์ผ๋ก ์์ฑํด ์ฃผ๋ Template ์ ๊ฐ๋ฐํ์์ต๋๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ ์ ์ฉ์ ๋ํ ํผ๋ก๋๋ฅผ ์ค์ด๊ธฐ ์ํด์ ์ ๊ณตํ Template์ Android Studio Plugin ํํ๋ก ์ ๊ณตํ์์ต๋๋ค. Android Studio์์ Class ์์ฑํ๋ ๋ฐฉ๋ฒ ๋๋ก Domain, Data ๊ณ์ธต(Layer)์ ๊ตฌ์ฑ์์๋ฅผ ์์ฑํ ์ ์๊ฒ ํ๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ฏผํ์ ๋ Plugin์ผ๋ก ์ ๊ณตํ๋ ๊ฒ์ด ๊ฐ๋ฐ์๊ฐ ๊ฐ๋ฐ ์ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์์ ๊ฒ์ผ๋ก ํ๋จํ์ฌ Plugin์ผ๋ก ์ ๊ณตํ์์ต๋๋ค.
Template ์์ฑ ๋ฐฉ๋ฒ์ ์์ฑํ๊ณ ์ ํ๋ ๋ชจ๋ ์๋ฅผ ๋ค์ด Domain ๋ชจ๋(Module)
์ด๋ผ๊ณ ํ๋ค๋ฉด Domain ๋ชจ๋(Module)
์์ [New]
> [Other]
์ ๋ณด๋ฉด [Clean Architecture Domain]
์ด๋ผ๋ Template ์ ํด๋ฆญ ํ ์์ฑํ๋ฉด ๋ฉ๋๋ค.
Template ์์ฑ ์ Prefix Class Name์ ๋ฐ๊ฒ ๋์ด ์์ต๋๋ค. ํด๋น Prefix Name์ ํตํด {Prefix Name}
๊ตฌ์ฑ์์๋ก ์์ฑ์ด ๋ฉ๋๋ค. ์๋ฅผ ๋ค๋ฉด Domain ๊ณ์ธต(Layer)์์ BestItem ์ด๋ผ๋ Prefix Name ์ผ๋ก ํ๋ค๋ฉด BestItemUseCaseModule
, BestItemEntity
, BestItemRepository
, BestItemUseCase
์ ๊ฐ์ด Domain Layer(๊ณ์ธต)์ ๋ํ ๊ตฌ์ฑ์์๊ฐ ์์ฑ๋ฉ๋๋ค.
๋ถํ์ํ ๊ตฌ์ฑ์์ ์ ๊ฑฐ
์ ํฌ ํ์ ํด๋ฆฐ ์ํคํ
์ฒ๋ฅผ ์ฒ์ ๋์
ํ๋ค ๋ณด๋ ์ต๋ํ ์ํคํ
์ฒ์ ๊ตฌ์ฑ์์๋ฅผ ์ ์ฉํ๋ ๊ฒ์ ์ ์ฑ
์ผ๋ก ์ ํ์ต๋๋ค. ๋ค๋ง, ์ ํฌ ํ์ฌ๋ Open API๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋ด๋ถ API ๊ฐ๋ฐ์๊ฐ ํ๋ก ํธ(Front) ํ๊ฒฝ์ ๋ง์ถฐ API์ Response Model์ ๋ด๋ ค์ฃผ๊ณ ์๊ธฐ์ Mapper
๋ฅผ ํตํ Model ์ Entity๋ก ๋ณ๊ฒฝํ๋ ์์
์ ๋ถํ์ํ์ต๋๋ค.
UseCase์ ๋ฐ๋ผ์ Mapper
์ ๊ธฐ๋ฅ์ ์๋ตํ๊ณ ์งํํ๋ฉฐ ๋ถํ์ํ ๊ตฌ์ฑ์์ ์์ฑ์ ๋ํ ๊ฐ๋ฐ์์ ํผ๋ก๋๋ฅผ ์ค์์ต๋๋ค.
๊ฒฐ๋ก
ํด๋ฆฐ ์ํคํ ์ฒ๋ ์ํํธ์จ์ด ์์คํ ์ ํจ์จ์ ์ด๊ณ ์ ์ง๋ณด์ ๊ฐ๋ฅํ ํํ๋ก ์ค๊ณํ๊ธฐ ์ํ ์ค์ํ ๊ฐ๋ ์ ๋๋ค.
์ ํฌ ํ์ ํด๋ฆฐ ์ํคํ ์ฒ์ ๋์ ์ ์ ํฌ ํ๋ก์ ํธ์ ๋ง๊ฒ ๊ตฌ์ฑํ ๋ถ๋ถ๋ ์๊ธฐ์ ์๋ฒฝํ ํด๋ฆฐ ์ํคํ ์ฒ๋ผ๊ณ ๋ณผ ์ ์์ง๋ง ์ต๋ํ ํด๋ฆฐ์ํคํ ์ฒ์ ๊ฐ๋ ์ ์ ์ฉํ๋ ค๊ณ ๋ ธ๋ ฅํ์์ผ๋ฉฐ ๊ทธ ๊ฒฐ๊ณผ ์ฑ๊ณต์ ์ผ๋ก ์คํ์ ํ ์ ์์์ต๋๋ค.
์ฌ๋ฐ๋ฅธ ๊ณ์ธต ๊ตฌ์กฐ์ ์์น์ ์ค์ํ๋ค๋ฉด ์ํํธ์จ์ด ๊ฐ๋ฐ์ ํ์ง๊ณผ ์์ฐ์ฑ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.