iOS ๊ฐœ๋ฐœ์ž์˜ ์›น ์ ์‘๊ธฐ - ๋‚ฏ์„  ์ฝ”๋“œ์—์„œ ์ต์ˆ™ํ•œ ๊ตฌ์กฐ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ธฐ๊นŒ์ง€

Jimin.Park
  • #๋ชจ๋ฐ”์ผ
  • #iOS
  • #Web
  • #Frontend

์•ˆ๋…•ํ•˜์„ธ์š”. Fromm ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž ๋ฐ•์ง€๋ฏผ์ž…๋‹ˆ๋‹ค.

์˜ฌํ•ด ์ดˆ iOS ๊ฐœ๋ฐœ์—์„œ ์›น ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ๋กœ ์—…๋ฌด๋ฅผ ์ „ํ™˜ํ•˜๊ฒŒ ๋˜๋ฉด์„œ ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ์ ‘ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์›น ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์—๋Š” ์–ธ์–ด์™€ ํ”„๋ ˆ์ž„์›Œํฌ๋ฟ ์•„๋‹ˆ๋ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์„ฑํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜ ์—ญ์‹œ iOS์™€๋Š” ์ƒ๋‹นํžˆ ๋‹ค๋ฅผ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. Swift์—์„œ TypeScript๋กœ, UIKit์—์„œ React๋กœ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์ด ๋ฐ”๋€Œ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด์— ์ต์ˆ™ํ–ˆ๋˜ ์„ค๊ณ„ ๋ฐฉ์‹๋„ ๋Œ€๋ถ€๋ถ„ ์ƒˆ๋กญ๊ฒŒ ์ตํ˜€์•ผ ํ•  ๊ฒƒ์ด๋ผ ์˜ˆ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์˜ ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ๋ถ„์„ํ•˜๋ฉด์„œ ์˜ˆ์ƒ๊ณผ๋Š” ๋‹ค๋ฅธ ์ ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

// queries/arti/useGetArtiTrial/index.ts
const useGetArtiTrial = () =>
  useSuspendedQuery({
    queryKey: ["getArtiTrial"],
    fn: (signal) => getArtiTrial(signal).then(ArtiTrialVo.from),
  });

์ฒ˜์Œ์—๋Š” ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” React์˜ Custom Hook ์ •๋„๋กœ ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ตฌํ˜„์„ ๋”ฐ๋ผ๊ฐ€๋‹ค ๋ณด๋‹ˆ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ , ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์‚ฌ์šฉํ•  ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•œ ๋’ค, ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜๋Š” ํ๋ฆ„์ด iOS์—์„œ ์ต์ˆ™ํ–ˆ๋˜ ๊ตฌ์กฐ์™€ ์ƒ๋‹นํžˆ ์œ ์‚ฌํ•˜๋‹ค๋Š” ์ ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

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

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” iOS ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ๊ธฐ์ค€์œผ๋กœ ์›น ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ถ„์„ํ•˜๋ฉด์„œ ๋ฐœ๊ฒฌํ•œ ๊ตฌ์กฐ์  ์œ ์‚ฌ์„ฑ๊ณผ ๋‘ ํ”Œ๋žซํผ์˜ ์ฐจ์ด์ ์„ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๋น„๊ต์˜ ๊ธฐ์ค€์ด ๋œ iOS ๊ตฌ์กฐ

์›น ํ”„๋กœ์ ํŠธ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ๋จผ์ € ์ œ๊ฐ€ ์ต์ˆ™ํ–ˆ๋˜ iOS ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋ถ€ํ„ฐ ๊ฐ„๋‹จํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Fromm iOS ์•ฑ์€ Clean Architecture๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Presentation, Domain, Data ์„ธ ๊ฐœ์˜ ๋ ˆ์ด์–ด๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • Presentation: ํ™”๋ฉด๊ณผ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌ
  • Domain: UseCase๋ฅผ ํ†ตํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰
  • Data: Repository๋ฅผ ํ†ตํ•ด API์™€ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌ

ํ•ต์‹ฌ์€ ๊ณ„์ธต ๊ฐ„ ์ฑ…์ž„์„ ๋ถ„๋ฆฌํ•˜๊ณ , ์˜์กด์„ฑ์„ ํ•œ ๋ฐฉํ–ฅ์œผ๋กœ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ViewModel์€ UseCase๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , UseCase๋Š” Repository๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.

final class FanProfileEditViewModel: BaseViewModel {
    @Inject private var loadFanProfileUseCase: LoadFanProfileUseCase
    @Inject private var updateFanProfileUseCase: UpdateFanProfileUseCase
}

์ด ๊ตฌ์กฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์›น ํ”„๋กœ์ ํŠธ์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์›น ํ”„๋กœ์ ํŠธ๋ฅผ ์ฒ˜์Œ ๋ดค์„ ๋•Œ

์ฒ˜์Œ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋ฅผ ๋ดค์„ ๋•Œ๋Š” ๊ฐ ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ์—ญํ• ์„ ๋ฐ”๋กœ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.

src/
โ”œโ”€โ”€ app/
โ”œโ”€โ”€ api/
โ”œโ”€โ”€ vo/
โ”œโ”€โ”€ queries/
โ”œโ”€โ”€ hooks/
โ””โ”€โ”€ components/

api/์™€ components/์˜ ์—ญํ• ์€ ๋น„๊ต์  ์ง๊ด€์ ์ด์—ˆ์ง€๋งŒ queries/, vo/, hooks/๋Š” ๊ฐ๊ฐ ์–ด๋–ค ๊ณ„์ธต์„ ๋‹ด๋‹นํ•˜๋Š”์ง€ ๋ฐ”๋กœ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.

์ฒ˜์Œ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถ”์ธกํ–ˆ์Šต๋‹ˆ๋‹ค.

  • queries/ โ†’ Repository
  • vo/ โ†’ DTO
  • hooks/ โ†’ UseCase

ํ•˜์ง€๋งŒ ์‹ค์ œ ๊ตฌํ˜„์€ ์ œ ์˜ˆ์ƒ๊ณผ ๋‹ฌ๋ž์Šต๋‹ˆ๋‹ค. ๋””๋ ‰ํ„ฐ๋ฆฌ ์ด๋ฆ„๋งŒ์œผ๋กœ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๊ธฐ๋ณด๋‹ค๋Š” API ์š”์ฒญ๋ถ€ํ„ฐ ํ™”๋ฉด ๋ Œ๋”๋ง๊นŒ์ง€ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ „๋‹ฌ๋˜๋Š”์ง€๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ ๊ฐ ๊ณ„์ธต์˜ ์—ญํ• ์„ ํ™•์ธํ•ด ๋ณด๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ๋ณธ ๊ตฌ์กฐ

์•„ํ‹ฐ์ŠคํŠธ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ธฐ์ค€์œผ๋กœ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

1. API Layer

export const getArti = (artiPath: string, signal?: AbortSignal) =>
  nativeFetch<GetArtiResponse>({
    url: `/arti/${artiPath}`,
    signal,
  });

๊ฐ€์žฅ ์•„๋ž˜์—์„œ๋Š” API์™€ ํ†ต์‹ ํ•˜์—ฌ ์„œ๋ฒ„ ์‘๋‹ต์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ด ์—ญํ• ์€ iOS์˜ Network Data Source์™€ ์œ ์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค.

2. VO Layer

class ArtiVo {
  readonly id: string;
  readonly name: string;

  static from(response: GetArtiTrial): ArtiVo {
    return new ArtiVo(response);
  }

  private constructor(response: GetArtiTrial) {
    this.id = response.id;
    this.name = response.name;
  }
}

์—ฌ๊ธฐ์„œ๋Š” ์„œ๋ฒ„ ์‘๋‹ต์„ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์‚ฌ์šฉํ•  ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

TypeScript๋Š” ๊ตฌ์กฐ์  ํƒ€์ž… ์‹œ์Šคํ…œ(Structural Typing)์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ตฌ์กฐ๊ฐ€ ๊ฐ™์œผ๋ฉด ๊ฐ™์€ ํƒ€์ž…์œผ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ๋ฌธ์— ์„œ๋ฒ„ ์‘๋‹ต ํƒ€์ž…๊ณผ ๋‚ด๋ถ€ ๋ชจ๋ธ์ด ์šฐ์—ฐํžˆ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์งˆ ๊ฒฝ์šฐ ๊ฒฝ๊ณ„๊ฐ€ ํ๋ฆฟํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

VO.from()๊ณผ private constructor ์กฐํ•ฉ์€ ์ด ๊ฒฝ๊ณ„๋ฅผ ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ๊ฐ•์ œํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋“œ์‹œ from()์„ ํ†ตํ•ด์„œ๋งŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•จ์œผ๋กœ์จ, Structural Typing ํ™˜๊ฒฝ์—์„œ๋„ ์„œ๋ฒ„ ์‘๋‹ต๊ณผ ํ”„๋ก ํŠธ์—”๋“œ ๋‚ด๋ถ€ ๋ชจ๋ธ์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. iOS์—์„œ๋Š” Mapper์™€ Domain Model์„ ๋ถ„๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋Š”๋ฐ, ์›น ํ”„๋กœ์ ํŠธ์—์„œ๋Š” VO๊ฐ€ ์ด๋Ÿฌํ•œ ์—ญํ• ์„ ํ•จ๊ป˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

3. Query Layer

const useGetArtiTrial = () =>
  useSuspendedQuery({
    queryKey: ["getArtiTrial"],
    fn: (signal) =>
      getArtiTrial(signal).then(ArtiTrialVo.from),
  });

Query๋Š” API๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ์‘๋‹ต์„ VO๋กœ ๋ณ€ํ™˜ํ•œ ๋’ค, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ๋ฆ„์€ ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ ๋‹จ์œ„๋ฅผ ์บก์Аํ™”ํ•œ๋‹ค๋Š” ์ ์—์„œ iOS์˜ UseCase์™€ ์—ญํ• ์ด ์ผ๋ถ€ ์œ ์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ ๋‘ ๊ตฌ์กฐ๋ฅผ ๋™์ผํ•œ ๊ฐœ๋…์œผ๋กœ ๋ณผ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. iOS์˜ UseCase๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ๋ณด๊ด€ํ•˜์ง€ ์•Š๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด React Query ๊ธฐ๋ฐ˜์˜ Query Layer๋Š” ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ์บ์‹ฑํ•˜๊ณ , ์žฌ์š”์ฒญ์„ ๊ด€๋ฆฌํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ ๊ธฐ๊ฐ„๊นŒ์ง€ ํ•จ๊ป˜ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ฆ‰, Query Layer๋Š” UseCase์™€ ์œ ์‚ฌํ•œ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ๋„ ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ(Server State Management)๋ผ๋Š” ์›น ํ™˜๊ฒฝ์˜ ์ฑ…์ž„์„ ํ•จ๊ป˜ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ตฌ์กฐ ๋น„๊ต ์š”์•ฝ

์—ญํ• iOSWeb
์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์กฐํšŒRepository / Data Sourceapi/
๋ฐ์ดํ„ฐ ๋ณ€ํ™˜MapperVO.from()
๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ ๋ชจ๋ธDomain ModelVO
๊ธฐ๋Šฅ ๋‹จ์œ„UseCaseQuery
ํ™”๋ฉด ์ƒํƒœViewModelCustom Hook
UIUIViewController / SwiftUIComponent

๋ฌผ๋ก  ์ด ํ‘œ๋Š” ๊ฐœ๋…์ ์ธ ๋Œ€์‘ ๊ด€๊ณ„์ผ ๋ฟ์ด๋ฉฐ, ๋‘ ๊ตฌ์กฐ๊ฐ€ ๋™์ผํ•œ ์•„ํ‚คํ…์ฒ˜๋ผ๋Š” ์˜๋ฏธ๋Š” ์•„๋‹™๋‹ˆ๋‹ค.

DI๋„ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์กด์žฌํ•˜๊ณ  ์žˆ์—ˆ๋‹ค

iOS์—์„œ๋Š” Protocol๊ณผ DI Container๋ฅผ ์ด์šฉํ•ด ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

@Inject private var loadFanProfileUseCase: LoadFanProfileUseCase

๋ฐ˜๋ฉด ์›น ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋Œ€๋ถ€๋ถ„ ES Module์„ ์ด์šฉํ•ด ํ•„์š”ํ•œ ๋ชจ๋“ˆ์„ ์ง์ ‘ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

import { getArtiTrial } from "api/arti";
import { ArtiTrialVo } from "vo/arti/ArtiTrial.vo";

ES Module ์ž์ฒด๋Š” DI ๋ฉ”์ปค๋‹ˆ์ฆ˜์€ ์•„๋‹™๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜์กด์„ฑ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•˜๊ณ  ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๋ ค๋Š” ๋ชฉ์ ์€ iOS์™€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ๋ฐฉ์‹์—์„œ๋„ ์ฐจ์ด๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. iOS์—์„œ๋Š” Protocol ๊ธฐ๋ฐ˜ Mock ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ์‹์ด ์ผ๋ฐ˜์ ์ด๋ผ๋ฉด, ์›น์—์„œ๋Š” MSW(Mock Service Worker) ๋“ฑ์„ ์ด์šฉํ•ด ๋„คํŠธ์›Œํฌ ์ž์ฒด๋ฅผ ๋ชจํ‚นํ•˜๋Š” ๋ฐฉ์‹์„ ๋งŽ์ด ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋‹ค๋ฅด์ง€๋งŒ ์™ธ๋ถ€ ํ™˜๊ฒฝ๊ณผ์˜ ๊ฒฐํ•ฉ์„ ์ค„์—ฌ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ์„ฑ์„ ํ™•๋ณดํ•œ๋‹ค๋Š” ๋ชฉ์ ์€ ๊ณตํ†ต์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋งˆ๋ฌด๋ฆฌ

ํ”„๋กœ์ ํŠธ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„์„ ๋”ฐ๋ผ๊ฐ€๋‹ค ๋ณด๋‹ˆ, ์ฒ˜์Œ์—๋Š” ๋‚ฏ์„ค๊ฒŒ ๋ณด์˜€๋˜ ๊ตฌ์กฐ๋“ค์ด ํ•˜๋‚˜์”ฉ ์ต์ˆ™ํ•œ ์—ญํ• ๋กœ ์ฝํžˆ๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฌผ๋ก  React Query๋ฅผ UseCase๋กœ, VO๋ฅผ Domain Model๋กœ ๋‹จ์ˆœํžˆ ๋Œ€์‘์‹œํ‚ฌ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ ๊ธฐ์ˆ ์€ ์ž์‹ ์ด ์†ํ•œ ์ƒํƒœ๊ณ„์˜ ํŠน์„ฑ๊ณผ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

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

์ด๋ฒˆ ๊ฒฝํ—˜์„ ํ†ตํ•ด ์ƒˆ๋กœ์šด ํ”Œ๋žซํผ์„ ์ดํ•ดํ•  ๋•Œ๋Š” ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ์ˆ ๋ณด๋‹ค ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ํ๋ฅด๊ณ , ๊ฐ ๊ณ„์ธต์ด ์–ด๋–ค ์—ญํ• ์„ ๋‹ด๋‹นํ•˜๋Š”์ง€๋ฅผ ๋จผ์ € ์‚ดํŽด๋ณด๋Š” ๊ฒƒ์ด ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋œ๋‹ค๋Š” ์ ์„ ๋А๊ผˆ์Šต๋‹ˆ๋‹ค.

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

Art Changes Life

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

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