fromm ์ฑ„ํŒ… ๋ฆฌ๋‰ด์–ผ: MVI ๋„์ž… ๋ฐ ์„ฑ๋Šฅ ๊ฐœ์„ 

sana
  • #Android
  • #MVI
  • #MVVM

๋“ค์–ด๊ฐ€๋ฉฐ

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

์ด์— ๋”ฐ๋ผ 2024๋…„ ํ•˜๋ฐ˜๊ธฐ์—๋Š” ๊ธฐ์กด ๊ตฌ์กฐ์˜ ๋ณต์žก์„ฑ์„ ๊ฐœ์„ ํ•˜๊ณ  ์ง€์†์ ์ธ ๊ธฐ๋Šฅ ํ™•์žฅ๊ณผ ์›ํ™œํ•œ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์œ„ํ•ด ์ฑ„ํŒ… ์‹œ์Šคํ…œ์˜ ๊ทผ๋ณธ์ ์ธ ๊ฐœ์„  ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ API ๋ณ€๊ฒฝ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ์˜ ์ „์ฒด์ ์ธ ์•„ํ‚คํ…์ฒ˜ ๋ณ€๊ฒฝ, ๋ชจ๋“  UI๋ฅผ Jetpack Compose๋กœ ์žฌ๊ตฌ์„ฑํ•˜๋Š” ๋“ฑ ๋งŽ์€ ๋ณ€ํ™”๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๋Œ€๊ทœ๋ชจ ๊ฐœ์„  ์ž‘์—… ์ค‘์—์„œ๋„, ํŠนํžˆ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์˜ ์ฑ„ํŒ…๋ฐฉ ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ ๋ฐฉ์‹์—์„œ์˜ ๊ตฌ์กฐ์ , ์„ฑ๋Šฅ์  ๋ฌธ์ œ๋“ค์„ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ–ˆ๋Š”์ง€์— ๋Œ€ํ•ด ์†Œ๊ฐœํ•ด๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ์กด ์ฑ„ํŒ… ์‹œ์Šคํ…œ์˜ ๊ตฌ์กฐ์  ๋ฌธ์ œ์ ๊ณผ ์„ฑ๋Šฅ ์ €ํ•˜

1. God Class ๋ฌธ์ œ: ChatManager์˜ ๊ณผ๋„ํ•œ ์ฑ…์ž„

๊ธฐ์กด ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ์˜ ๊ฐ€์žฅ ํฐ ๋ฌธ์ œ๋Š” ChatManager๋ผ๋Š” ๊ฑฐ๋Œ€ํ•œ Singleton ๊ฐ์ฒด์˜€์Šต๋‹ˆ๋‹ค.

ChatManager๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์—ญํ• ์„ ๋งก๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • ์ฑ„ํŒ…๋ฐฉ ๋ชฉ๋ก ๊ด€๋ฆฌ
  • ๋ชจ๋“  ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€ ๋กœ๋“œ
  • ์ˆ˜์‹ ๋œ ๋ฉ”์‹œ์ง€ ์—…๋ฐ์ดํŠธ
  • ๋ฉ”์‹œ์ง€ ์‚ญ์ œ ๋ฐ ์ˆจ๊น€ ์ฒ˜๋ฆฌ
  • ๋ฒˆ์—ญ ๊ธฐ๋Šฅ ๊ด€๋ฆฌ
  • ์ฑ„ํŒ… ์—ฐ๊ฒฐ ๋ฐ ํ•ด์ œ ๊ด€๋ฆฌ

์ด์ฒ˜๋Ÿผ ChatManager๋Š” ์ฑ„ํŒ…๊ณผ ๊ด€๋ จ๋œ ๊ฑฐ์˜ ๋ชจ๋“  ๋กœ์ง์„ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ChatManager๊ฐ€ Singleton ๊ฐ์ฒด๋กœ ๊ตฌํ˜„๋˜์–ด ์ „์—ญ์ ์œผ๋กœ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ฝ”๋“œ ๊ณณ๊ณณ์—์„œ ChatManager์— ์˜์กดํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ChatManager์˜ ์ฑ…์ž„์ด ๋”์šฑ ๋น„๋Œ€ํ•ด์ง€๋Š” ๊ฒฐ๊ณผ๋ฅผ ์ดˆ๋ž˜ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ, ChatManager๋Š” โ€˜God Classโ€™๊ฐ€ ๋˜์–ด, ์ž‘์€ ๋ณ€๊ฒฝ์—๋„ ์ „์ฒด ์‹œ์Šคํ…œ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๋Š” ์œ„ํ—˜์„ ๋‚ดํฌํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ฐ ์—ญํ• ์ด ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌ๋˜์ง€ ๋ชปํ•˜๊ณ  ChatManager์— ์ข…์†๋˜์–ด, ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ๋„ ํฌ๊ฒŒ ๋–จ์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋Š” ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ์ฝ”๋“œ์˜ ๋ณต์žก์„ฑ์„ ์ฆ๊ฐ€์‹œ์ผœ, ์œ ์ง€๋ณด์ˆ˜์™€ ๊ธฐ๋Šฅ ํ™•์žฅ์„ ์ ์  ๋” ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

2. ๋‹จ์ผ Map ๊ธฐ๋ฐ˜์˜ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ๋ฉ”๋ชจ๋ฆฌ ๋น„ํšจ์œจ์„ฑ

ํŠนํžˆ ChatManager ๋Š” ๋ชจ๋“  ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์€ chats ๋ผ๋Š” ๋ณ€์ˆ˜์— ๋‹ด์•„ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ณ€์ˆ˜๋Š” ์ฑ„ํŒ…๋ฐฉ ID(String)๋ฅผ ํ‚ค๋กœ, ํ•ด๋‹น ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ(List<ChatDto>)๋ฅผ ๊ฐ’์œผ๋กœ ๊ฐ€์ง€๋Š” Map ํ˜•ํƒœ์˜€์Šต๋‹ˆ๋‹ค.

private val _chats = MutableStateFlow<Map<String, List<ChatDto>>>(mapOf())
val chats = _chats.asStateFlow()

์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋Š” ๊ณผ๋„ํ•œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๊ณผ ์„ฑ๋Šฅ ์ €ํ•˜๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ €, ๋ชจ๋“  ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ•˜๋‚˜์˜ Map์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€๋กœ ์ด์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์ฑ„ํŒ…๋ฐฉ ์ˆ˜์™€ ๊ฐ ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ• ์ˆ˜๋ก, chats์— ์ €์žฅ๋˜๋Š” ๋ฐ์ดํ„ฐ์˜ ์–‘๋„ ๋Š˜์–ด๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ 10๊ฐœ์˜ ์ฑ„ํŒ…๋ฐฉ์— ์ฐธ์—ฌํ•˜๊ณ , ๊ฐ ์ฑ„ํŒ…๋ฐฉ์— 10,000๊ฐœ์˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด, chats์—๋Š” 10๋งŒ ๊ฐœ์˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ChatManager๋Š” Singleton ๊ฐ์ฒด๋กœ, ์•ฑ์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ๊ณ„์† ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ผ๊ฐ€๊ณ , chats ๋˜ํ•œ ์•ฑ์ด ์ข…๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ฑ„ํŒ…๋ฐฉ์„ ๋‚˜๊ฐ„ ํ›„์—๋„, ํ•ด๋‹น ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€ ๋ฐ์ดํ„ฐ๋Š” chats์— ๊ณ„์† ๋‚จ์•„์žˆ๊ฒŒ ๋˜๊ณ , ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ฐจ์ง€ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ˜„์ƒ์ด ๋ฐ˜๋ณต๋˜๋ฉด, ์•ฑ์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์€ ๊ณ„์†ํ•ด์„œ ์ฆ๊ฐ€ํ•˜๊ฒŒ ๋˜๊ณ , ๊ฒฐ๊ตญ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜(Memory Leak)๋กœ ์ด์–ด์งˆ ์œ„ํ—˜์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

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

๊ฒฐ๊ณผ์ ์œผ๋กœ, ์ด๋Ÿฌํ•œ ๋น„ํšจ์œจ์ ์ธ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ํŽ˜์ด์ง• ๋ฐฉ์‹์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ €ํ•˜์‹œํ‚ค๋Š” ์ฃผ์š” ์›์ธ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

3. ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ํ๋ฆ„๊ณผ ๋ถˆํ•„์š”ํ•œ ์—ฐ์‚ฐ์˜ ๋ฐ˜๋ณต

ChatManager์™€ ChatViewModel ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๋˜ํ•œ ๋ณต์žกํ–ˆ์Šต๋‹ˆ๋‹ค.

(ํŽธ์˜๋ฅผ ์œ„ํ•ด usecase, repository ๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ์ง์ ‘ DB ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์œผ๋กœ ํ‘œํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.)

์œ„์˜ ๋‹ค์ด์–ด๊ทธ๋žจ์€ ์ฑ„ํŒ…๋ฐฉ์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ณผ์ •์„ ๊ฐ„๋žตํžˆ ๋ณด์—ฌ์ฃผ๋Š”๋ฐ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๋ฉฐ ๋น„ํšจ์œจ์ ์œผ๋กœ ํ๋ฅด๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ViewModel์€ View์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์•ก์…˜์„ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ  ChatManager์— ์œ„์ž„ํ•˜๋Š” ๋‹จ์ˆœ ์ค‘๊ฐœ ์—ญํ• ๋งŒ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ChatManager ๊ฐ€ StateFlow ๋กœ ๋ฐฉ์ถœํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ View ๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋ฐ›์•„ ๋งคํ•‘ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ฒ˜๋Ÿผ ChatManager์™€ ViewModel์˜ ์—ญํ• ์ด ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ๋˜์ง€ ์•Š์•„ ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ๋ณต์žกํ•ด์ง€๊ณ , ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ์ฃผ์ฒด๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋ถ„์‚ฐ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด์— ๋”ํ•ด, ๋ฐ์ดํ„ฐ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋  ๋•Œ๋งˆ๋‹ค ChatManager ์˜ List<ChatDto> ๋ฅผ ChatViewModel ์˜ UI ๋ชจ๋ธ์ธ List<ChatMessage> ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ถˆํ•„์š”ํ•œ ์—ฐ์‚ฐ์ด ๋ฐ˜๋ณต์ ์œผ๋กœ ์ˆ˜ํ–‰๋˜๋Š” ๋ฌธ์ œ๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ ๋ฉ”์‹œ์ง€๋งŒ ์ถ”๊ฐ€, ์‚ญ์ œ, ์—…๋ฐ์ดํŠธ๋˜๋”๋ผ๋„ ๋ฆฌ์ŠคํŠธ ์ „์ฒด๋ฅผ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋กœ ๋‹ค์‹œ ์ƒ์„ฑํ•˜์—ฌ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ค์‹œ ๋งคํ•‘ํ•˜๋Š” ๊ตฌ์กฐ์˜€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค๋ณด๋‹ˆ ๋‚ ์งœ ๊ตฌ๋ถ„์„  ์ถ”๊ฐ€์™€ ๊ฐ™์€ UI ๊ด€๋ จ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•  ๋•Œ๋„ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฏธ ์ˆ˜ํ–‰ํ•œ ์—ฐ์‚ฐ์„ ๋˜๋‹ค์‹œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ด์„œ ๋น„ํšจ์œจ์„ฑ์€ ๋”์šฑ ๋Š˜์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค.

์ƒˆ๋กœ์šด ์•„ํ‚คํ…์ฒ˜ ๋„์ž… ๋ฐ ์„ฑ๋Šฅ ๊ฐœ์„ 

1. MVI ํŒจํ„ด ๋ฐ Reducer ๋„์ž…

์ฑ„ํŒ… ๋ฆฌ๋‰ด์–ผ์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•˜๊ฒŒ ์ƒ๊ฐํ•œ ์ ์€ ์ฑ…์ž„์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜๊ณ , ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด์—๋Š” ChatManager์™€ ChatViewModel์ด ๋ณต์žกํ•˜๊ฒŒ ์–ฝํ˜€ ์žˆ์–ด ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์› ๊ณ , ์ƒํƒœ ๋ณ€๊ฒฝ์ด ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ๋ฐœ์ƒํ•˜์—ฌ ์˜ˆ์ธกํ•˜๊ธฐ ํž˜๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด MVI(Model-View-Intent) ํŒจํ„ด๊ณผ Reducer๋ฅผ ๋„์ž…ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. MVI ํŒจํ„ด์€ ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ํ†ตํ•ด ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ , Reducer๋Š” ์ด์ „ ์ƒํƒœ์™€ ์ƒˆ๋กœ์šด ์•ก์…˜์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋กœ, ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.

(ํŽธ์˜๋ฅผ ์œ„ํ•ด usecase, repository ๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ์ง์ ‘ DB ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์œผ๋กœ ํ‘œํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.)

๊ฐœ์„ ๋œ ์•„ํ‚คํ…์ฒ˜์˜ ๋‹ค์ด์–ด๊ทธ๋žจ์„ ์‚ดํŽด๋ณด๋ฉด, ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ์ด์ „๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•ด์ง„ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. View๋Š” ๋” ์ด์ƒ ์—ฌ๋Ÿฌ ๊ฐœ์˜ StateFlow๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋Œ€์‹ , ์˜ค์ง ํ•˜๋‚˜์˜ UiState๋งŒ์„ ๊ด€์ฐฐํ•˜์—ฌ UI๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ์—ญํ• ์— ์ง‘์ค‘ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ธฐ์กด์—๋Š” ViewModel์ด ChatManager์— ์˜์กดํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ๋ณต์žกํ•˜๊ณ  ChatManager์˜ ์—ญํ• ์ด ๊ณผ์ค‘๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐœ์„ ๋œ ๊ตฌ์กฐ์—์„œ๋Š” ViewModel์ด ๋” ์ด์ƒ ChatManager๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋„๋ก ๋ณ€๊ฒฝ๋˜์–ด, ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ๋Œ€ํญ ๋‹จ์ˆœํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ UI ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋กœ์ง์€ Reducer๊ฐ€ ๋‹ด๋‹นํ•˜๋„๋ก ๋ณ€๊ฒฝ๋˜์–ด, ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๊ฐ€ ๋„์ฐฉํ•˜๋ฉด Reducer๋Š” ๊ธฐ์กด ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก๊ณผ ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๋ฅผ ๊ฒฐํ•ฉํ•ด ์ƒˆ๋กœ์šด UiState๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด์ฒ˜๋Ÿผ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋กœ์ง์ด Reducer๋ผ๋Š” ํ•˜๋‚˜์˜ ์žฅ์†Œ๋กœ ์ง‘์ค‘๋˜๋ฉด์„œ, ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์˜ˆ์ธกํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ๊ฐ€ ํ›จ์”ฌ ์šฉ์ดํ•ด์กŒ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ณ€ํ™”๋ฅผ ํ†ตํ•ด, ๊ธฐ์กด์— ๊ฑฐ๋Œ€ํ•˜๊ณ  ๋ณต์žกํ–ˆ๋˜ ChatManager๋Š” ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ œ๊ฑฐ๋˜์—ˆ๊ณ , ๋ณต์žกํ–ˆ๋˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์€ ๋‹จ๋ฐฉํ–ฅ ์ˆœํ™˜ ๊ตฌ์กฐ๋กœ ๋‹จ์ˆœํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

2. ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์ตœ์ ํ™”: ๋ฆฌ์ŠคํŠธ ํฌ๊ธฐ ์ œํ•œ

๋‚˜์•„๊ฐ€, ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์„ ๊ทผ๋ณธ์ ์œผ๋กœ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด์—๋Š” ๋ชจ๋“  ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€๋ฅผ chats๋ผ๋Š” ํ•˜๋‚˜์˜ Map ๋ณ€์ˆ˜์— ๋‹ด์•„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ฐพ๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐ ์–ด๋ ค์›€์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ฑ„ํŒ…๋ฐฉ๊ณผ ๋ฉ”์‹œ์ง€๊ฐ€ ์Œ“์ผ์ˆ˜๋ก ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ฆ๊ฐ€ํ•˜๋Š” ๋ฌธ์ œ๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

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

data class ChatRoomState(
    val poolMessages: List<ChatMessage>,
    val recentMessages: List<ChatMessage>,
    val sendingMessages: List<SendingChatMessage>,
    ...
) : State

๊ฐ๊ฐ์˜ ์—ญํ• ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • poolMessages: ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์ด์ „์˜ ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๋“ค์„ ์ €์žฅํ•˜๋Š” ๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ์ž์œ ๋กญ๊ฒŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์‹œ์ง€ ํ’€ ์—ญํ• ์„ ํ•˜๋ฉฐ, ํฌ๊ธฐ๋Š” 2000๊ฐœ๋กœ ์ œํ•œ๋ฉ๋‹ˆ๋‹ค.
  • recentMessages: ์‚ฌ์šฉ์ž๊ฐ€ ์•„์ง ์ฝ์ง€ ์•Š์€ ๋ฉ”์‹œ์ง€๋‚˜, ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋œ ๋ฉ”์‹œ์ง€๋“ค์„ ์ €์žฅํ•˜๋Š” ๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ์ด ๋ฆฌ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋Š” ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๋ฅผ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • sendingMessages: ์‚ฌ์šฉ์ž๊ฐ€ ํ˜„์žฌ ์ „์†ก ์ค‘์ธ ๋ฉ”์‹œ์ง€๋“ค์„ ์ €์žฅํ•˜๋Š” ๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ์ด ๋ฆฌ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ๋ฉ”์‹œ์ง€ ์ „์†ก ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , ์ „์†ก ์‹คํŒจ ์‹œ ์žฌ์ „์†ก ๋“ฑ์˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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


    data class AddPoolMessages(val messages: List<ChatMessage>, val isPrepend: Boolean) : ChatRoomReducer(
        reducer = { state ->
            val dropSize = messages.size - POOL_MESSAGES_LIMIT
            val poolMessages = messages
                .takeIf { it.size <= POOL_MESSAGES_LIMIT }
                ?: if (isPrepend) messages.dropLast(dropSize) else messages.drop(dropSize)

            state.copy(
                poolMessages = poolMessages
            )
        }
    )

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

3. ํšจ์œจ์ ์ธ ์—ฐ์‚ฐ ์ฒ˜๋ฆฌ ๋ฐ ์—ญํ•  ๋ถ„๋ฆฌ

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

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

๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

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

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ