Skip to main content

CJ์˜จ์Šคํƒ€์ผ์˜ ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ 'ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜' ๋„์ž…๊ธฐ

2024๋…„ 2์›” 15์ผAbout 3 minJavaKotlinIntellij IdeaAndroidArticle(s)blogyozm.wishket.comkotlinjetbrainsintellij-ideaintellij-idea-pluginandroidandroid-studio

CJ์˜จ์Šคํƒ€์ผ์˜ ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ 'ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜' ๋„์ž…๊ธฐ ๊ด€๋ จ

Android > Article(s)

Article(s)

CJ์˜จ์Šคํƒ€์ผ์˜ ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ 'ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜' ๋„์ž…๊ธฐ | ์š”์ฆ˜IT
๊ตญ๋‚ด IT ๊ธฐ์—…์€ ํ•œ๊ตญ์„ ๋„˜์–ด ์„ธ๊ณ„๋ฅผ ๋ฌด๋Œ€๋กœ ํ•  ์ •๋„๋กœ ๋›ฐ์–ด๋‚œ ๊ธฐ์ˆ ๊ณผ ์•„์ด๋””์–ด๋ฅผ ์ž๋ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ด๋“ค์€ ๊ธฐ์—… ๋ธ”๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ์ด๋Ÿฌํ•œ ์ •๋ณด๋ฅผ ๊ณต๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์š”์ฆ˜IT๋Š” ๊ฐ ๊ธฐ์—…์˜ ํŠน์ƒ‰ ์žˆ๊ณ  ์œ ์ตํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ์†Œ๊ฐœํ•˜๋Š” ์‹œ๋ฆฌ์ฆˆ๋ฅผ ์ค€๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ ์–ด๋–ป๊ฒŒ ์‚ฌ๊ณ ํ•˜๊ณ , ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ผํ•˜๊ณ  ์žˆ์„๊นŒ์š”?

๊ตญ๋‚ด IT ๊ธฐ์—…์€ ํ•œ๊ตญ์„ ๋„˜์–ด ์„ธ๊ณ„๋ฅผ ๋ฌด๋Œ€๋กœ ํ•  ์ •๋„๋กœ ๋›ฐ์–ด๋‚œ ๊ธฐ์ˆ ๊ณผ ์•„์ด๋””์–ด๋ฅผ ์ž๋ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ด๋“ค์€ ๊ธฐ์—… ๋ธ”๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ์ด๋Ÿฌํ•œ ์ •๋ณด๋ฅผ ๊ณต๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์š”์ฆ˜IT๋Š” ๊ฐ ๊ธฐ์—…์˜ ํŠน์ƒ‰ ์žˆ๊ณ  ์œ ์ตํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ์†Œ๊ฐœํ•˜๋Š” ์‹œ๋ฆฌ์ฆˆ๋ฅผ ์ค€๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ ์–ด๋–ป๊ฒŒ ์‚ฌ๊ณ ํ•˜๊ณ , ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ผํ•˜๊ณ  ์žˆ์„๊นŒ์š”?

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” CJ์˜จ์Šคํƒ€์ผ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์•ˆ๋“œ๋กœ์ด๋“œ ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ์— ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋„์ž…ํ•œ ์ด์œ ์™€ ์ด๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž์˜ ํ”ผ๋กœ๋„๋ฅผ ๊ฐ์†Œ์‹œํ‚จ ๊ฒฝํ—˜์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์€ ๋ณต์žกํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ ์ด์ƒ์„ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ๊ฒฐ๊ณผ๋ก ์œผ๋กœ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋งŒ ์ƒ๊ฐํ•˜๊ณ  ๊ฐœ๋ฐœํ•œ๋‹ค๋ฉด, ์ฝ”๋“œ๋Š” ์ ์ฐจ ์ปค์ง€๋ฉฐ ๋‚˜์ค‘์—๋Š” ์†๋ณผ ์ˆ˜ ์—†์„ ์ •๋„๋กœ ๋‚œํญํ•œ(?) ์ฝ”๋“œ๋กœ ๋ณ€ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ ‡๊ธฐ์— ๊ฐœ๋ฐœํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์œ ์ง€๋ณด์ˆ˜์„ฑ, ํ™•์žฅ์„ฑ, ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ๋“ฑ์˜ ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ๋ฏผ์ด ํ•„์š”ํ•˜๋ฉฐ, ์ ์ ˆํ•œ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„๊ฐ€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์ค‘์š”ํ•œ ๊ฐœ๋… ์ค‘ ํ•˜๋‚˜๊ฐ€ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์ž…๋‹ˆ๋‹ค.

์…€๋ ™์ƒต APP ๊ตฌ์ถ• ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋„์ž…ํ•œ ์ด์œ ์™€ ๋„์ž… ํ›„ ๊ฐ ๊ณ„์ธต(Layer)์˜ ๊ตฌ์„ฑ ์š”์†Œ ๋ชจ๋‘ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ฆ๊ฐ€ํ•œ ๊ฐœ๋ฐœ์ž์˜ ํ”ผ๋กœ๋„๋ฅผ ํ•ด๊ฒฐํ•œ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์†Œ๊ฐœํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.


ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ž€?

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ์‹œ์Šคํ…œ์˜ ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ ์ง€์ผœ์•ผ ํ•  ์›์น™๊ณผ ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•œ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค. ์ด ๊ฐœ๋…์€ ๋กœ๋ฒ„ํŠธ C. ๋งˆํ‹ด(Robert C. Martin)์— ์˜ํ•ด ์†Œ๊ฐœ๋˜์—ˆ์œผ๋ฉฐ, ๋ณต์žกํ•œ ์†Œํ”„ํŠธ์›จ์–ด ์‹œ์Šคํ…œ์„ ๋ณด๋‹ค ๊ด€๋ฆฌ ๊ฐ€๋Šฅํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ์ง€์นจ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ด ์•„ํ‚คํ…์ฒ˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด์˜ ์œ ์ง€๋ณด์ˆ˜์„ฑ, ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ๋ฐ ๋ชจ๋“ˆ ๊ฐ„์˜ ๋ถ„๋ฆฌ๋ฅผ ๊ฐ•์กฐํ•˜์—ฌ ์•ˆ๋“œ๋กœ์ด๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋” ๊ตฌ์กฐํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.


์™œ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ํ•„์š”ํ•œ๊ฐ€?

์†Œํ”„ํŠธ์›จ์–ด๊ฐ€ ๊ฐ€์ง„ ๋ณธ์—ฐ์˜ ๋ชฉ์ ์„ ์ถ”๊ตฌํ•˜๋ ค๋ฉด ์†Œํ”„ํŠธ์›จ์–ด๋Š” ๋ฐ˜๋“œ์‹œ '๋ถ€๋“œ๋Ÿฌ์›Œ'์•ผ ํ•œ๋‹ค. ๋‹ค์‹œ ๋งํ•ด ๋ณ€๊ฒฝํ•˜๊ธฐ ์‰ฌ์›Œ์•ผ ํ•œ๋‹ค. ์ดํ•ด๊ด€๊ณ„์ž๊ฐ€ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ƒ๊ฐ์„ ๋ฐ”๊พธ๋ฉด, ์ด๋Ÿฌํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์‰ฝ๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

(๋กœ๋ฒ„ํŠธ C. ๋งˆํ‹ด, ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜)

The Clean Code Blog
The Clean Code Blog

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ์‹œ์Šคํ…œ์˜ ๊ตฌ์กฐ๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ์„ค๊ณ„ํ•จ์œผ๋กœ์จ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์žฅ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€์žฅ ์ค‘์š”ํ•œ ์žฅ์  ์ค‘ ํ•˜๋‚˜๋Š” ์‹œ์Šคํ…œ์˜ ๊ฐ ๋ถ€๋ถ„์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐœ๋ฐœํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์„ ์กฐ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ ์‹œ์Šคํ…œ์˜ ๋ณ€๊ฒฝ์ด๋‚˜ ์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์ „์ฒด ์‹œ์Šคํ…œ์„ ๋‹ค์‹œ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋˜๋ฉฐ, ํŠน์ • ๋ถ€๋ถ„๋งŒ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด๋ณด๋ฉด Network ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ Retrofit์˜ ConvertFactory๋ฅผ Moshi Factory๋กœ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์–ด๋А ๋‚  Moshi Facory๊ฐ€ ์•„๋‹Œ GsonconvertFactory ์œผ๋กœ ์ „๋ฉด ๋ณ€๊ฒฝํ•˜๋ผ๋Š” ๊ธฐ์ˆ  ์ง€์นจ์ด ๋‚ด๋ ค์˜จ๋‹ค๋ฉด ๊ฐœ๋ฐœ์ž๋Š” ํ”„๋กœ์ ํŠธ ์•ˆ์—์„œ Network ํ†ต์‹ ํ•˜๋Š” ๊ณณ์„ ๋ชจ๋‘ ์ฐพ์•„์„œ Moshi Factory ๋ฅผ GsonconvertFactory ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ท€์ฐฎ์Œ๊ณผ ์ฝ”๋“œ๋ฅผ ์ฐพ๋Š” ์ˆ˜๊ณ ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? Network ํ†ต์‹ ์„ ๋‹ด๋‹นํ•˜๋Š” Layer ๋งŒ ํ™•์ธ ํ›„ Network ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ณ€๊ฒฝ์ž‘์—…ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋œ๋‹ค๋ฉด, ํœด๋จผ ์—๋Ÿฌ๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ณ€๊ฒฝ ์ฒ˜๋ฆฌ๋ฅผ ๋†“์น˜๋Š” ์ผ€์ด์Šค๋„ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.


์ฃผ์š” ์›์น™

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์˜ ์ฃผ์š” ์›์น™์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.


ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ ๊ณ„์ธต

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋Š” ๋‹ค์–‘ํ•œ ๊ณ„์ธต(Layer)์œผ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ, ๊ฐ ๊ณ„์ธต์€ ํŠน์ •ํ•œ ์—ญํ• ๊ณผ ์ฑ…์ž„์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์˜ ์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

ํ”„๋ ˆ์  ํ„ฐ(Presenter) ๋˜๋Š” ๋ทฐ๋ชจ๋ธ(ViewModel) : ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค(UI)์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฐ„์˜ ์ค‘๊ฐ„ ๊ณ„์ธต์œผ๋กœ, UI์—์„œ ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์œ ์Šค์ผ€์ด์Šค(Use Case) : ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํฌํ•จํ•˜๋Š” ๋ถ€๋ถ„์œผ๋กœ, ํ”„๋ ˆ์  ํ„ฐ๋‚˜ ๋ทฐ๋ชจ๋ธ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌํฌ์ง€ํ† ๋ฆฌ(Repository) : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์›๋ณธ๊ณผ์˜ ์ƒํ˜ธ ์ž‘์šฉ์„ ๋‹ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์œผ๋กœ, ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ €์žฅํ•˜๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์œ ์Šค์ผ€์ด์Šค๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์–ป์–ด์˜ต๋‹ˆ๋‹ค. ์—”ํ‹ฐํ‹ฐ(Entity) : ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ•ต์‹ฌ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ๋„คํŠธ์›Œํฌ์—์„œ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•œ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค.


ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ์ ์€ ๋ฌด์—‡์ผ๊นŒ?

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์˜ ์ฃผ์š” ์žฅ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด์„ฑ : ๊ฐ ๊ณ„์ธต์ด ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•œ ๊ณ„์ธต์„ ๋ณ€๊ฒฝํ•ด๋„ ๋‹ค๋ฅธ ๊ณ„์ธต์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์•„ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฝ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ : ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•˜์—ฌ ์œ ๋‹› ํ…Œ์ŠคํŠธ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ ๊ฐ„์˜ ๋ถ„๋ฆฌ : ๊ฐ ๊ณ„์ธต์ด ์ž์ฒด ์—ญํ• ์„ ๊ฐ€์ง€๋ฉฐ, ์ด๋กœ ์ธํ•ด ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ UI ํ”„๋ ˆ์ž„์›Œํฌ ๋ณ€๊ฒฝ ์šฉ์ด์„ฑ : ์ค‘์š”ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ์™ธ๋ถ€ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์–ด ํ•ด๋‹น ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋ณ€๊ฒฝํ•˜๋”๋ผ๋„ ๊ธฐ์กด์— ์ž‘์„ฑํ•œ ์ „์ฒด ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.


์…€๋ ™์ƒต ํ”„๋กœ์ ํŠธ X ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜

์ €ํฌ ํŒ€์€ ์ตœ๊ทผ ์…€๋ ™์ƒต APP ๊ตฌ์ถ• ํ”„๋กœ์ ํŠธ์—์„œ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ ์šฉํ•˜์—ฌ ๋งŽ์€ ์ด์ ์„ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋Šฅ ๊ตฌํ˜„์— ์žˆ์–ด์„œ ๋‹ค์ˆ˜์˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ์—ˆ์œผ๋ฉฐ, ์˜์กด์„ฑ์ด ๋‚ฎ์•„ ๊ฐœ๋ฐœํ•œ ์†Œ์Šค์— ๋Œ€ํ•œ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋Š” ์žฅ์ ์„ ๊ฒฝํ—˜ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ณ„์ธต(Layer)์˜ ๋…๋ฆฝ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•˜์—ฌ ํ’ˆ์งˆ ์œ ์ง€๋„ ์ด์ „ ํ”„๋กœ์ ํŠธ์— ๋น„ํ•ด ํ–ฅ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์™œ ๋„์ž…ํ•˜์˜€๋‚˜?

๊ธฐ์กด ํ”„๋กœ์ ํŠธ๋Š” MVVM(Model-View-ViewModel) ์•„ํ‚คํ…์ฒ˜๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŽ์•„์ง„ ๋งŒํผ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ง€๋ฉฐ ๊ด€๋ฆฌ์ธก๋ฉด์—์„œ ์–ด๋ ค์›€์„ ๊ฒฝํ—˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

MVVM ์•„ํ‚คํ…์ฒ˜ ์ ์šฉํ•œ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋„
MVVM ์•„ํ‚คํ…์ฒ˜ ์ ์šฉํ•œ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋„

์˜์กด์„ฑ์„ ์ตœ์†Œํ™”ํ•˜์—ฌ ๋…๋ฆฝ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๊ณ ๋ฏผํ•˜์˜€๊ณ , ์ด๋ฏธ ๋งŽ์€ ๋น…ํ…ŒํฌํšŒ์‚ฌ์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” Clean Architecture ๋„์ž…์„ ๊ฒ€ํ† ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์œ„ 4๊ฐ€์ง€ ์ธก๋ฉด๊ณผ ํ•จ๊ป˜ ํ”„๋กœ์ ํŠธ ํ™•์žฅ์„ฑ์„ ๊ณ ๋ คํ–ˆ์„ ๋•Œ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์˜ ๋„์ž…์„ ์ฃผ์ €ํ•  ์ด์œ ๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.


ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ ์šฉํ•˜๋ฉฐ

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ Android ์˜ ๊ตฌ์กฐ์— ๋งž์ถฐ์„œ ๊ฐœ๋ฐœํ•˜๋Š” ๊ณผ์ •์€ ๋‚˜์นจํŒ์„ ๋“ค๊ณ  ํ–ฅํ•ดํ•˜๋Š” ๊ธฐ๋ถ„์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ์ด๋ฏธ Google Developer Document(Guilde to app architecture ์—์„œ Android ์˜ ํด๋ฆฐ์•„ํ‚คํ…์ฒ˜์— ๋Œ€ํ•ด ๊ธฐ์ˆ ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๋‹ค์ˆ˜์˜ ๋ธ”๋กœ๊ทธ์—์„œ ํด๋ฆฐ์•„ํ‚คํ…์ฒ˜ ์ ์šฉ์— ๋Œ€ํ•ด์„œ ๋‹ค๋ค„์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ €ํฌ ํŒ€์€ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ ๋„์ž…์„ ํ•˜๋ฉด์„œ View์— ๋Œ€ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ View Model์—์„œ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” MVVM ์•„ํ‚คํ…์ฒ˜ ๊ฐœ๋…์„ ์ผ๋ถ€ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ๊ณ ๋ฏผ์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ + MVVM ์•„ํ‚คํ…์ฒ˜์— ๋Œ€ํ•œ ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ
ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ + MVVM ์•„ํ‚คํ…์ฒ˜์— ๋Œ€ํ•œ ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ

๊ทธ ๊ฒฐ๊ณผ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ ๊ณ„์ธต(Layer) ์ค‘ Presentation Layer ๋ฅผ MVVM์˜ View ์™€ ViewModel๋กœ ์ •์˜ํ•˜์˜€๊ณ , Domain ๊ณ„์ธต(Layer) ๊ณผ Data ๊ณ„์ธต(Layer)์„ MVVM์˜ Model๋กœ ์ •์˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ํ‘œ๋กœ ๊ฐ„๋žตํ•˜๊ฒŒ ํ‘œํ˜„ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Clean ArchitecturePresentation LayerDomain LayerData Layer
MVVMView, View ModelModelModel

Android ๊ฐœ๋ฐœ ์‹œ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์˜ ๊ฐ ๊ณ„์ธต(Layer)๋Š” ๋ชจ๋“ˆ(Module) ๋กœ ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ณ„์ธต(Layer)์„ ๋ชจ๋“ˆ๋กœ ๊ตฌ์„ฑํ•œ ์ด์œ ๋Š” ๋ชจ๋“ˆ์˜ ๋‹ค์–‘ํ•œ ์ด์ ์„ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์„ ํƒํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ด์ ์œผ๋กœ๋Š” ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ, ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด์„ฑ, ๋ณ‘๋ ฌ ๊ฐœ๋ฐœ ๊ฐ€๋Šฅ์„ฑ, ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ, ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ ๊ด€๋ฆฌ์˜ ํšจ์œจ์„ฑ, ๊ทธ๋ฆฌ๊ณ  ์ „๋ฐ˜์ ์ธ ๊ฐœ๋ฐœ ์†๋„ ํ–ฅ์ƒ ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์…€๋ ™์ƒต ๋ชจ๋“ˆ(Module)
์…€๋ ™์ƒต ๋ชจ๋“ˆ(Module)

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์„ฑ์š”์†Œ ์—ฐ๊ฒฐ

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์—์„œ ๊ณ„์ธต(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 ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Response์— ๋”ฐ๋ฅธ ConvertFactory ๋ณ€๊ฒฝ
Response์— ๋”ฐ๋ฅธ ConvertFactory ๋ณ€๊ฒฝ

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 Plugin
Template Plugin

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ ์ ์šฉ์— ๋Œ€ํ•œ ํ”ผ๋กœ๋„๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด์„œ ์ œ๊ณตํ•œ Template์€ Android Studio Plugin ํ˜•ํƒœ๋กœ ์ œ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค. Android Studio์—์„œ Class ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ• ๋Œ€๋กœ Domain, Data ๊ณ„์ธต(Layer)์˜ ๊ตฌ์„ฑ์š”์†Œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ–ˆ์„ ๋•Œ Plugin์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐœ๋ฐœ ์‹œ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜์—ฌ Plugin์œผ๋กœ ์ œ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Template ์ƒ์„ฑ ํ™”๋ฉด
Template ์ƒ์„ฑ ํ™”๋ฉด

Template ์ƒ์„ฑ ๋ฐฉ๋ฒ•์€ ์ƒ์„ฑํ•˜๊ณ ์ž ํ•˜๋Š” ๋ชจ๋“ˆ ์˜ˆ๋ฅผ ๋“ค์–ด Domain ๋ชจ๋“ˆ(Module)์ด๋ผ๊ณ  ํ•œ๋‹ค๋ฉด Domain ๋ชจ๋“ˆ(Module)์—์„œ [New] > [Other] ์— ๋ณด๋ฉด [Clean Architecture Domain] ์ด๋ผ๋Š” Template ์„ ํด๋ฆญ ํ›„ ์ƒ์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Domain Layer ์ƒ์„ฑ ์‹œ Prefix Name ์„ค์ • ํ™”๋ฉด
Domain Layer ์ƒ์„ฑ ์‹œ Prefix Name ์„ค์ • ํ™”๋ฉด

Template ์ƒ์„ฑ ์‹œ Prefix Class Name์„ ๋ฐ›๊ฒŒ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น Prefix Name์„ ํ†ตํ•ด {Prefix Name} ๊ตฌ์„ฑ์š”์†Œ๋กœ ์ƒ์„ฑ์ด ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด Domain ๊ณ„์ธต(Layer)์—์„œ BestItem ์ด๋ผ๋Š” Prefix Name ์œผ๋กœ ํ•œ๋‹ค๋ฉด BestItemUseCaseModule, BestItemEntity, BestItemRepository, BestItemUseCase ์™€ ๊ฐ™์ด Domain Layer(๊ณ„์ธต)์— ๋Œ€ํ•œ ๊ตฌ์„ฑ์š”์†Œ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

Template ์„ ํ†ตํ•œ Domain Layer ๊ตฌ์„ฑ์š”์†Œ ์ƒ์„ฑ๋œ ๋ชจ์Šต
Template ์„ ํ†ตํ•œ Domain Layer ๊ตฌ์„ฑ์š”์†Œ ์ƒ์„ฑ๋œ ๋ชจ์Šต

๋ถˆํ•„์š”ํ•œ ๊ตฌ์„ฑ์š”์†Œ ์ œ๊ฑฐ

์ €ํฌ ํŒ€์€ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ฒ˜์Œ ๋„์ž…ํ•˜๋‹ค ๋ณด๋‹ˆ ์ตœ๋Œ€ํ•œ ์•„ํ‚คํ…์ฒ˜์˜ ๊ตฌ์„ฑ์š”์†Œ๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ •์ฑ…์œผ๋กœ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์ €ํฌ ํšŒ์‚ฌ๋Š” Open API๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋‚ด๋ถ€ API ๊ฐœ๋ฐœ์ž๊ฐ€ ํ”„๋ก ํŠธ(Front) ํ™˜๊ฒฝ์— ๋งž์ถฐ API์˜ Response Model์„ ๋‚ด๋ ค์ฃผ๊ณ  ์žˆ๊ธฐ์— Mapper ๋ฅผ ํ†ตํ•œ Model ์„ Entity๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์ž‘์—…์€ ๋ถˆํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.

Mapper์˜ ๊ธฐ๋Šฅ ์ตœ์†Œํ™”
Mapper์˜ ๊ธฐ๋Šฅ ์ตœ์†Œํ™”

UseCase์— ๋”ฐ๋ผ์„œ Mapper ์˜ ๊ธฐ๋Šฅ์„ ์ƒ๋žตํ•˜๊ณ  ์ง„ํ–‰ํ•˜๋ฉฐ ๋ถˆํ•„์š”ํ•œ ๊ตฌ์„ฑ์š”์†Œ ์ƒ์„ฑ์— ๋Œ€ํ•œ ๊ฐœ๋ฐœ์ž์˜ ํ”ผ๋กœ๋„๋ฅผ ์ค„์˜€์Šต๋‹ˆ๋‹ค.


๊ฒฐ๋ก 

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ์‹œ์Šคํ…œ์„ ํšจ์œจ์ ์ด๊ณ  ์œ ์ง€๋ณด์ˆ˜ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ์„ค๊ณ„ํ•˜๊ธฐ ์œ„ํ•œ ์ค‘์š”ํ•œ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.

์ €ํฌ ํŒ€์€ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์˜ ๋„์ž…์„ ์ €ํฌ ํ”„๋กœ์ ํŠธ์— ๋งž๊ฒŒ ๊ตฌ์„ฑํ•œ ๋ถ€๋ถ„๋„ ์žˆ๊ธฐ์— ์™„๋ฒฝํ•œ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ผ๊ณ  ๋ณผ ์ˆ˜ ์—†์ง€๋งŒ ์ตœ๋Œ€ํ•œ ํด๋ฆฐ์•„ํ‚คํ…์ฒ˜์˜ ๊ฐœ๋…์„ ์ ์šฉํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•˜์˜€์œผ๋ฉฐ ๊ทธ ๊ฒฐ๊ณผ ์„ฑ๊ณต์ ์œผ๋กœ ์˜คํ”ˆ์„ ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์˜ฌ๋ฐ”๋ฅธ ๊ณ„์ธต ๊ตฌ์กฐ์™€ ์›์น™์„ ์ค€์ˆ˜ํ•œ๋‹ค๋ฉด ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์˜ ํ’ˆ์งˆ๊ณผ ์ƒ์‚ฐ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


์ฐธ๊ณ  ์ž๋ฃŒ

์•ฑ ์•„ํ‚คํ…์ฒ˜ ๊ฐ€์ด๋“œ | Android ๊ฐœ๋ฐœ์ž | Android Developers

์ด ๊ฐ€์ด๋“œ์—๋Š” ๊ณ ํ’ˆ์งˆ์˜ ๊ฐ•๋ ฅํ•œ ์•ฑ์„ ๋นŒ๋“œํ•˜๊ธฐ ์œ„ํ•œ ๊ถŒ์žฅ์‚ฌํ•ญ ๋ฐ ๊ถŒ์žฅ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
The Clean Coder Blog | The Clean Architecture

The Clean Architecture
Hilt๋ฅผ ์‚ฌ์šฉํ•œ ์ข…์† ํ•ญ๋ชฉ ์‚ฝ์ž…

Hilt๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ์ข…์† ํ•ญ๋ชฉ ์ˆ˜๋™ ์‚ฝ์ž…์„ ์‹คํ–‰ํ•˜๋Š” ์ƒ์šฉ๊ตฌ๋ฅผ ์ค„์ด๋Š” Android์šฉ ์ข…์† ํ•ญ๋ชฉ ์‚ฝ์ž… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ์ข…์† ํ•ญ๋ชฉ ์ˆ˜๋™ ์‚ฝ์ž…์„ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋ชจ๋“  ํด๋ž˜์Šค์™€ ์ข…์† ํ•ญ๋ชฉ์„ ์ˆ˜๋™์œผ๋กœ ๊ตฌ์„ฑํ•˜๊ณ  ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ข…์† ํ•ญ๋ชฉ์„ ์žฌ์‚ฌ์šฉ ๋ฐ ๊ด€๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์›๋ฌธ

Medium(cj-onstyle): ์™œ Android ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ๋Š” ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋„์ž…ํ•˜์˜€๋Š”๊ฐ€

์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์€ ๋ณต์žกํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ ์ด์ƒ์„ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ๊ฒฐ๊ณผ๋ก ์œผ๋กœ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋งŒ ์ƒ๊ฐํ•˜๊ณ  ๊ฐœ๋ฐœํ•œ๋‹ค๋ฉด, ์ฝ”๋“œ๋Š” ์ ์ฐจ ์ปค์ง€๋ฉฐ ๋‚˜์ค‘์—๋Š” ์†๋ณผ ์ˆ˜ ์—†์„ ์ •๋„๋กœ ๋‚œํญํ•œ(?) ์ฝ”๋“œ๋กœ ๋ณ€ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ๊ฐœ๋ฐœํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์œ ์ง€๋ณด์ˆ˜์„ฑ, ํ™•์žฅ์„ฑ, ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ๋“ฑ์˜ ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ๋ฏผ์ด ํ•„์š”ํ•˜๋ฉฐ, ์ ์ ˆํ•œโ€ฆ