๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

IT/Android

๐Ÿ”„ Kotlin Flow์—์„œ ์ค‘๋ณต๋˜๋Š” DTO-Domain ๋งคํ•‘ ์ค„์ด๊ธฐ

728x90
๋ฐ˜์‘ํ˜•

 

๐Ÿ”„ Kotlin Flow์—์„œ ์ค‘๋ณต๋˜๋Š” DTO-Domain ๋งคํ•‘ ์ค„์ด๊ธฐ

Kotlin Flow๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, API๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ DTO๋กœ ํŒŒ์‹ฑํ•œ ํ›„ ์ด๋ฅผ Domain ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ณ„์ธต ๋ถ„๋ฆฌ๋Š” ์ฝ”๋“œ์˜ ํ’ˆ์งˆ์„ ๋†’์ด๊ณ  ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋™์ž‘์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ Flow ๋‚ด์—์„œ ๋งค๋ฒˆ map ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DTO๋ฅผ Domain์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋ฐ˜๋ณต์ ์ด๊ณ  ์ค‘๋ณต๋œ ์ž‘์—…์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.


๐Ÿงช ์‹œ๋„ #1: ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™œ์šฉํ•œ ์ผ๋ฐ˜ํ™”

๋จผ์ €, DTO ํด๋ž˜์Šค์— asDomain ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์ด๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ถ”์ƒํ™”ํ•ฉ๋‹ˆ๋‹ค.

interface DomainConvertible<Domain> {
    fun asDomain(): Domain
}

๊ทธ๋Ÿฐ ๋‹ค์Œ, Flow์— ๋Œ€ํ•œ ํ™•์žฅ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ๋ชจ๋“  DTO-Domain ๋ณ€ํ™˜์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

fun <T : DomainConvertible<R>, R> Flow<T>.mapToDomain(): Flow<R> = transform { emit(it.asDomain()) }

์žฅ์ :

  • ๋ชจ๋“  DTO ํด๋ž˜์Šค์— asDomain ๊ตฌํ˜„์„ ๊ฐ•์ œํ•˜์—ฌ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ผ๋ฐ˜ํ™”๋œ ์—ฐ์‚ฐ์ž๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ ์ค‘๋ณต์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ :

  • ๋ชจ๋“  DTO ํด๋ž˜์Šค์— ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋งคํ•‘ ํ•จ์ˆ˜๊ฐ€ DTO ํด๋ž˜์Šค ๋‚ด๋ถ€์— ์œ„์น˜ํ•˜์—ฌ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงช ์‹œ๋„ #2: ํ™•์žฅ ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•œ ๋ถ„๋ฆฌ

์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , DTO์™€ Flow์— ๋Œ€ํ•œ ํ™•์žฅ ํ•จ์ˆ˜๋ฅผ ๋ณ„๋„์˜ ํŒŒ์ผ์— ์ •์˜ํ•˜์—ฌ ๋งคํ•‘ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

fun UserDto.asDomain(): User {
    return User(id = id ?: 0, name = name ?: "")
}

fun Flow<UserDto>.mapToDomain(): Flow<User> = map { it.asDomain() }

์žฅ์ :

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

๋‹จ์ :

  • ๋ชจ๋“  DTO์— ๋Œ€ํ•ด ๋ณ„๋„์˜ ํ™•์žฅ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๊ด€๋ฆฌ๊ฐ€ ๋ฒˆ๊ฑฐ๋กœ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงช ์‹œ๋„ #3: ์ œ๋„ค๋ฆญ์„ ํ™œ์šฉํ•œ ๋‹จ์ผ ํ™•์žฅ ํ•จ์ˆ˜

๋ชจ๋“  DTO-Domain ๋ณ€ํ™˜์„ ํ•˜๋‚˜์˜ ์ œ๋„ค๋ฆญ ํ™•์žฅ ํ•จ์ˆ˜๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

inline fun <reified T, R> Flow<T>.mapToDomain(): Flow<R> = map {
    when (T::class) {
        UserDto::class -> (it as UserDto).asDomain() as R
        // ๋‹ค๋ฅธ DTO-Domain ๋งคํ•‘ ์ถ”๊ฐ€
        else -> throw IllegalArgumentException("Unknown type")
    }
}

์žฅ์ :

  • ๋‹จ์ผ ํ™•์žฅ ํ•จ์ˆ˜๋กœ ๋ชจ๋“  DTO-Domain ๋ณ€ํ™˜์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ ์ค‘๋ณต์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ :

  • ํด๋ž˜์Šค ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ํ™•์ธํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ƒˆ๋กœ์šด DTO-Domain ๋งคํ•‘์„ ์ถ”๊ฐ€ํ•  ๋•Œ๋งˆ๋‹ค when ์ ˆ์„ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

โœ… ๊ฒฐ๋ก 

๊ฐ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์žฅ๋‹จ์ ์ด ์žˆ์œผ๋ฉฐ, ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ์™€ ํŒ€์˜ ์„ ํ˜ธ๋„์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ ํŒ€์€ ๊ฒฐ๊ตญ ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ map ์—ฐ์‚ฐ์ž์™€ DTO์— ๋Œ€ํ•œ ํ™•์žฅ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๋ช…์‹œ์ ์ด๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šฐ๋ฉฐ, ์œ ์ง€๋ณด์ˆ˜ ์ธก๋ฉด์—์„œ๋„ ์œ ๋ฆฌํ•˜๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“š ์ถœ์ฒ˜

Florent Blot, Redundant DTO-Domain Mapping in Kotlin Flow (Medium)

728x90
๋ฐ˜์‘ํ˜•