โ† ๋ชฉ๋ก์œผ๋กœ
Effect functional programming

์ฃผ๋ณ€์˜ ๊ฐœ๋ฐœ์ž ๋ถ„๋“ค๊ณผ ๋ฆฌ์•กํŠธ์™€ DI์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ํ•œ ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
Angular, Nest.js, Spring๊ณผ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์˜์กด์„ฑ ์ฃผ์ž…์„ ํ†ตํ•ด ํ™•์žฅ์„ฑ ์žˆ๊ฒŒ ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅํ•œ๋ฐ ๋น„ํ•ด
๋ฆฌ์•กํŠธ์™€ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ๋Š” ์˜์กด์„ฑ ์ฃผ์ž…์„ ๋„์ž…ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ด์•ผ๊ธฐ๋ฅผ ํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๊ฒŒ ๊ด€๋ จ ์ž๋ฃŒ๋ฅผ ์ฐพ๋‹ค๊ฐ€ Effect๋ผ๋Š” ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋ฐ˜์˜ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์•Œ๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ
์ด๊ฒƒ์€ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์˜์กด์„ฑ ์ฃผ์ž… ๊ฐœ๋…์„ ๋„์ž…ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ Effect๋กœ ๊ตฌํ˜„ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์ €์žฅ์†Œ ๊ตฌํ˜„ํ•˜๊ธฐ

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ €์žฅ์†Œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ด…์‹œ๋‹ค.

  1. ํ”„๋กœ๊ทธ๋žจ์€ ์ €์žฅ์†Œ์—์„œ ์œ ์ € ์ •๋ณด๋ฅผ ํš๋“ํ•œ๋‹ค.
  2. ์ €์žฅ์†Œ๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ๊ธฐ๋ฐ˜์˜ ์ €์žฅ์†Œ์™€ ์ธ ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

์ธํ„ฐํŽ˜์ด์Šค ์ž‘์„ฑ

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

class UserRepositoryError extends Error {
  readonly _tag = "UserRepositoryError"
  constructor(message: string) {
    super(`UserRepositoryError: ${message}`)
    this.name = "UserRepositoryError"
  }

  static readonly NotFound = (id: number) => {
    return new UserRepositoryError(`User with id ${id} not found`)
  }
}

class LocalStorageError extends Error {
  readonly _tag = "LocalStorageError"
  constructor(message: string) {
    super(`LocalStorageError: ${message}`)
    this.name = "LocalStorageError"
  }

  static readonly parseError = (error: unknown) => {
    if (error instanceof Error) {
      return new LocalStorageError(error.message)
    }

    return new LocalStorageError("Unknown error")
  }

  static readonly stringifyError = (error: unknown) => {
    if (error instanceof Error) {
      return new LocalStorageError(error.message)
    }

    return new LocalStorageError("Unknown error")
  }

  static readonly NotFound = (key: string) => {
    return new LocalStorageError(`LocalStorage key ${key} not found`)
  }
}

class LocalStorageService extends Context.Tag("LocalStorageService")<
  LocalStorageService,
  {
    getItem<T>(key: string): Effect.Effect<T | undefined, LocalStorageError>
    setItem<T>(key: string, value: T): Effect.Effect<void, LocalStorageError>
    removeItem(key: string): Effect.Effect<void, LocalStorageError>
  }
>() {}

class UserRepository extends Context.Tag("UserRepository")<
  UserRepository,
  {
    update: (user: User) => Effect.Effect<void, UserRepositoryError>
    findById: (id: number) => Effect.Effect<User | undefined, UserRepositoryError>
    findAll: () => Effect.Effect<Array<User>, UserRepositoryError>
  }
>() {}

Effect๋Š” ์ •์ƒ์ ์œผ๋กœ ๋ฐ˜ํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ, ์—๋Ÿฌ ํƒ€์ž…, ์š”๊ตฌ์‚ฌํ•ญ์œผ๋กœ ๊ตฌ๋ถ„๋˜๋Š” ๊ธฐ๋ณธ์ ์ธ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
์•„๋ž˜์˜ ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜๊ฐ’์„ ์˜ˆ์‹œ๋กœ ์„ฑ๊ณต์ผ ๋•Œ์—๋Š” T ๋˜๋Š” undefined๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์—๋Ÿฌ์ผ ๋•Œ์—๋Š” ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์—๋Ÿฌ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

    getItem<T>(key: string): Effect.Effect<T | undefined, LocalStorageError>
    update: (user: User) => Effect.Effect<void, UserRepositoryError>

๊ตฌํ˜„์ฒด ์ž‘์„ฑ

์ด์ œ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ด์šฉํ•ด ๊ฐ ๊ตฌํ˜„์ฒด๋ฅผ ์ž‘์„ฑํ•ด๋ด…์‹œ๋‹ค.

๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์„œ๋น„์Šค ๊ตฌํ˜„์ฒด

LocalStorageService ์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ์‹ค์ œ ๊ตฌํ˜„์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
์ž์ฒด์ ์ธ try/catch ํ•จ์ˆ˜๊ฐ€ ์žˆ์–ด ๊ธฐ์กด์˜ try/catch ๋ฐฉ์‹์„ ๋Œ€์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. try/catch ๋ฌธ์„ ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•˜๋ฉด ์ข‹์€ ์ ์€ ๋ฌด์—‡์ผ๊นŒ์š”?

๊ธฐ์กด์˜ ๋ฌธ๋ฒ•์€ ์—๋Ÿฌ๊ฐ€ ์ „ํŒŒ๋  ๋•Œ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด try/catch์—์„œ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค.
์ด๊ฒƒ์€ ์•”์‹œ์ ์œผ๋กœ ํ•ด๊ฒฐ๋˜๋ฉฐ ์–ด๋””์„œ ์–ด๋–ค ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ ํŒŒ์•…ํ•  ์ˆ˜ ์—†๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ Effect.try ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ํ•ด๋‹น ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ ์—๋Ÿฌ๊ฐ€ ๋ฐ˜ํ™˜๋  ์ˆ˜ ์žˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๊ธฐ ๋•Œ๋ฌธ์—
๋ช…์‹œ์ ์œผ๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

const localStorageServiceLive = LocalStorageService.of({
  getItem: <T>(key: string) =>
    Effect.try<T | undefined, LocalStorageError>({
      try: () => {
        const item = localStorage.getItem(key)
        if (!item) {
          return undefined
        }

        return JSON.parse(item) as T
      },
      catch: (error) => LocalStorageError.parseError(error)
    }),
  setItem: <T>(key: string, value: T) =>
    Effect.try<void, LocalStorageError>({
      try: () => {
        const stringifiedValue = JSON.stringify(value)
        localStorage.setItem(key, stringifiedValue)
      },
      catch: (error) => LocalStorageError.stringifyError(error)
    }),
  removeItem: (key: string) =>
    Effect.try<void, LocalStorageError>({
      try: () => localStorage.removeItem(key),
      catch: (error) => LocalStorageError.parseError(error)
    })
})

๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด

yield* LocalStorageService ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ ๊ตฌํ˜„์ฒด๋ฅผ ์ด์šฉํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ์•„๋‹Œ ์˜์กด์„ฑ์„ ์ฃผ์ž…๋ฐ›์•„ ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ๋ฐ˜์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

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

const userLocalStorageRepository = Effect.gen(function* () {
  const localStorageService = yield* LocalStorageService

  return UserRepository.of({
    findById: (id: number) => {
      return Effect.gen(function* () {
        return yield* localStorageService.getItem<Array<User>>("users").pipe(
          Effect.map((users) => users || []),
          Effect.catchAll(() => Effect.succeed<Array<User>>([])),
          Effect.map((users) => users.find((user) => user.id === id))
        )
      })
    },
    findAll: () => {
      return Effect.gen(function* () {
        return yield* localStorageService.getItem<Array<User>>("users").pipe(
          Effect.map((users) => users || []),
          Effect.catchAll(() => Effect.succeed<Array<User>>([]))
        )
      })
    },
    update: (user: User) => {
      return Effect.gen(function* () {
        return yield* localStorageService.getItem<Array<User>>("users").pipe(
          Effect.map((users) => users || []),
          Effect.map((users) => {
            const index = users.findIndex((u) => u.id === user.id)
            if (index === -1) {
              return users
            }

            return [...users.slice(0, index), { ...user }, ...users.slice(index + 1)]
          }),
          Effect.flatMap((users) => localStorageService.setItem("users", users)),
          Effect.catchAll(() => Effect.succeed(void 0))
        )
      })
    }
  })
})

์ธ ๋ฉ”๋ชจ๋ฆฌ ๊ตฌํ˜„์ฒด

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

const inMemoryUserRepository = Effect.gen(function* () {
  return UserRepository.of({
    findById: (id: number) => Effect.succeed(users.find((user) => user.id === id)),
    findAll: () => Effect.succeed(users),
    update: (user: User) => Effect.succeed((users[users.findIndex((u) => u.id === user.id)] = user))
  })
})

์„œ๋น„์Šค ์กฐํ•ฉํ•˜๊ธฐ

Effect์—์„œ๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ์‹์œผ๋กœ ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ๊ฐ€์žฅ ์‰ฝ๊ฒŒ ๋ช…์‹œ์ ์ธ Effect.Layer๋ฅผ ์ด์šฉํ•ด์„œ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.
Layer๋ผ๋Š” ํŒจํ‚ค์ง€์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ์ธํ„ฐํŽ˜์ด์Šค์™€ ์‹ค์ œ ๊ตฌํ˜„์ฒด๋ฅผ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค์™€ ์‹ค์ œ ๊ตฌํ˜„์ฒด๋ฅผ ๊ฒฐํ•ฉํ•ด์„œ localStorageLayer๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
์ด์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ธ ๋ฉ”๋ชจ๋ฆฌ์™€ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์ €์žฅ์†Œ์˜ ์˜์กด์„ฑ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

์ด๋•Œ์— ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์ €์žฅ์†Œ๋Š” ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์„œ๋น„์Šค๋ฅผ ์˜์กดํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ userLocalStorageRepository.pipe(Effect.provide(localStorageLayer))์™€ ๊ฐ™์ด ๋ณ„๋„์˜ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ ˆ์ด์–ด๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  main ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์‹œ์ ์— ์ธ ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ ๋ ˆ์ด์–ด์™€ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์ €์žฅ์†Œ ๋ ˆ์ด์–ด ์ค‘์— ์–ด๋–ค ๋ ˆ์ด์–ด๋ฅผ ์ฃผ์ž…ํ• ์ง€ ๊ฒฐ์ •ํ•˜๊ฒŒ ๋˜๋ฉด main ํ•จ์ˆ˜๋Š” ์™ธ๋ถ€์— ์–ด๋–ค ์ €์žฅ์†Œ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ์•Œ์ง€ ๋ชปํ•œ ์ฑ„ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ค์ง ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์ €์žฅ์†Œ ๋ ˆ์ด์–ด์—์„œ๋งŒ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์„œ๋น„์Šค๊ฐ€ ํ•„์š”ํ•จ์„ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ์ธํ„ฐํŽ˜์ด์Šค์™€ ์‹ค์ œ ๊ตฌํ˜„์ฒด๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ํ•˜๋‚˜์˜ ๋ ˆ์ด์–ด๋ฅผ ๋งŒ๋“ค๊ณ 
์ด๋Ÿฐ ๋ ˆ์ด์–ด๋“ค์„ ์กฐํ•ฉํ•˜์—ฌ ์‹ค์ œ ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰ํ•˜๋Š” ์‹œ์ ์— ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

const localStorageLayer = Layer.succeed(LocalStorageService, localStorageServiceLive)
const userLocalStorageRepositoryLayer = Layer.effect(
  UserRepository,
  userLocalStorageRepository.pipe(Effect.provide(localStorageLayer))
)
const inMemoryUserRepositoryLayer = Layer.effect(UserRepository, inMemoryUserRepository)

const selectUserRepositoryLayer = (isDevelopment: boolean) =>
  isDevelopment ? inMemoryUserRepositoryLayer : userLocalStorageRepositoryLayer

const main = Effect.gen(function* () {
  const userRepository = yield* UserRepository

  const user1 = yield* userRepository.findById(1)
  const user2 = yield* userRepository.findById(2)
  const all = yield* userRepository.findAll()

  console.log("user1", user1)
  console.log("user2", user2)
  console.log("all", all)
})

Effect.runSync(main.pipe(Effect.provide(selectUserRepositoryLayer(process.env.BUN_ENV === "development"))))

๊ธฐ์กด ์„œ๋น„์Šค์™€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๊ธฐ

์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•œ ํ”„๋กœ๊ทธ๋žจ์„ ๊ธฐ์กด ์ผ๋ฐ˜์ ์ธ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ์™€ ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐํ• ๊นŒ์š”?
์‚ฌ์‹ค Effect๋Š” ์‹คํ–‰ํ•˜๋Š” ์‹œ์ ์—๋Š” ์ผ๋ฐ˜ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์™€ ๋‹ค๋ฅผ ๋ฐ” ์—†์œผ๋ฏ€๋กœ ๊ธฐ์กด์˜ ํ•จ์ˆ˜์ฒ˜๋Ÿผ ์ด์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

getUser๋ผ๋Š” ๊ธฐ์กด์˜ ํ•จ์ˆ˜์— ๋‹จ์ง€ Effect.run ๋ฉ”์„œ๋“œ๋“ค์„ ์‹คํ–‰ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
์ด์ฒ˜๋Ÿผ ๊ธฐ์กด ๋กœ์ง๊ณผ ๊ฒฐํ•ฉํ•˜๊ธฐ๋„ ์•„์ฃผ ์‰ฝ์Šต๋‹ˆ๋‹ค.

const getUser = (id: number) => {
	// return oldFindById(id);
	return Effect.runSync(main.pipe(Effect.provide(selectUserRepositoryLayer(process.env.BUN_ENV === "development"))))
}

๊ผญ ํ•„์š”ํ•œ๊ฐ€

์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ๊ธฐ์กด์˜ ์ฝ”๋“œ๋“ค๋ณด๋‹ค๋Š” ํ›จ์”ฌ ์ฝ”๋“œ๋Ÿ‰์ด ๋งŽ์Šต๋‹ˆ๋‹ค.
์ด๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์„ ์–ธํ•ด์•ผ ํ•˜๊ณ  ์—๋Ÿฌ ํƒ€์ž…์„ ๋Œ€๋ถ€๋ถ„ ๋งŒ๋“ค์–ด์„œ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์„ ์ง€ํ–ฅํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•œ ๋กœ์ง์—์„œ๋Š” ๋ถˆํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๊ทธ ์™ธ์—๋„ Effect์—์„œ๋Š” stream๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์„œ ๋ฐœํ–‰๊ณผ ๊ตฌ๋… ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๊ณ  ๋” ๋งŽ์€ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

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

๊ตฌํ˜„ ์˜ˆ์ œ - https://github.com/load28/effect