745 words
4 minutes
[Effect Layers] 04. 构建和组合层
2025-08-30 18:20:11
2025-12-24 23:45:46

构建和组合层#

https://github.com/typeonce-dev/effect-getting-started-course

我们现在有多个运行最终程序所需的服务。

没有 Layer 的话,我们需要逐个提供每个服务:

index.ts

export const program = Effect.gen(function* () {
  const pokeApi = yield* PokeApi;
  return yield* pokeApi.getPokemon;
});

const runnable = program.pipe(
  Effect.provideService(PokeApi, PokeApi.Live),
  Effect.provideService(PokemonCollection, PokemonCollection.Live),
  Effect.provideServiceEffect(BuildPokeApiUrl, BuildPokeApiUrl.Live),
  Effect.provideServiceEffect(PokeApiUrl, PokeApiUrl.Live)
);
NOTE

provideServiceEffect 用于从 Effect 中提取服务。

在我们的例子中,PokeApiUrlLiveBuildPokeApiUrlLive 是使用 Effect.gen 定义的,因此我们需要使用 provideServiceEffectEffect 中提取服务。

这种策略很快就变得难以维护和阅读。此外,如果一个服务依赖于另一个服务(相互依赖),这会变得更糟。

IMPORTANT

Layer 的优势

Layer 允许我们做的是组织所有依赖关系并只提供一次

服务的 Layer 可以使用 Layer.succeed 创建:

PokemonCollection.ts

import { Context, Layer, type Array } from "effect";

export class PokemonCollection extends Context.Tag("PokemonCollection")<
  PokemonCollection,
  Array.NonEmptyArray<string>
>() {
  static readonly Live = Layer.succeed(this, [
    "staryu",
    "perrserker",
    "flaaffy",
  ]);
}

PokeApi.ts

export class PokeApi extends Context.Tag("PokeApi")<PokeApi, typeof make>() {
  static readonly Live = Layer.succeed(this, make);
}

当服务是从 Effect 派生时,我们使用 Layer.effect

TIP

注意我们不再需要 PokeApiUrl.ofBuildPokeApiUrl.of,类型是从 Layer 推断出来的。

PokeApiUrl.ts

export class PokeApiUrl extends Context.Tag("PokeApiUrl")<
  PokeApiUrl,
  string
>() {
  static readonly Live = Layer.effect(
    this,
    Effect.gen(function* () {
      const baseUrl = yield* Config.string("BASE_URL");
      return `${baseUrl}/api/v2/pokemon`;
    })
  );
}

BuildPokeApiUrl.ts

export class BuildPokeApiUrl extends Context.Tag("BuildPokeApiUrl")<
  BuildPokeApiUrl,
  ({ name }: { name: string }) => string
>() {
  static readonly Live = Layer.effect(
    this,
    Effect.gen(function* () {
      const pokeApiUrl = yield* PokeApiUrl;
      return ({ name }) => `${pokeApiUrl}/${name}`;
    })
  );
}

然后使用 Layer.mergeAll 组合每个 Layer

index.ts

const MainLayer = Layer.mergeAll(
  PokeApi.Live,
  PokemonCollection.Live,
  BuildPokeApiUrl.Live,
  PokeApiUrl.Live
);
TIP

注意通过将 Live 实现作为 class 上的 static 属性,引用它们是多么容易

MainLayer 现在是一个新的 Layer,包含了 mergeAll 内定义的所有依赖关系。

最后,我们可以使用 Effect.provide 提供这个单一层来运行最终程序:

index.ts

const MainLayer = Layer.mergeAll(
  PokeApi.Live,
  PokemonCollection.Live,
  BuildPokeApiUrl.Live,
  PokeApiUrl.Live
);

export const program = Effect.gen(function* () {
  const pokeApi = yield* PokeApi;
  return yield* pokeApi.getPokemon;
});

const runnable = program.pipe(Effect.provide(MainLayer));
IMPORTANT

关注点分离

这允许我们将如何组织服务(Layer)与如何提供它们(Effect.provide)分离开来。

我们可以使用 Layer 来管理每个依赖关系,同时在 index.ts 中保持单一的 Effect.provide

index.ts 现在不包含任何实现细节,而是定义如何组合层和运行应用:

index.ts

import { Effect, Layer } from "effect";
import { BuildPokeApiUrl } from "./BuildPokeApiUrl";
import { PokeApi } from "./PokeApi";
import { PokeApiUrl } from "./PokeApiUrl";
import { PokemonCollection } from "./PokemonCollection";

const MainLayer = Layer.mergeAll(
  PokeApi.Live,
  PokemonCollection.Live,
  BuildPokeApiUrl.Live,
  PokeApiUrl.Live
);

export const program = Effect.gen(function* () {
  const pokeApi = yield* PokeApi;
  return yield* pokeApi.getPokemon;
});

const runnable = program.pipe(Effect.provide(MainLayer));

const main = runnable.pipe(
  Effect.catchTags({
    FetchError: () => Effect.succeed("Fetch error"),
    JsonError: () => Effect.succeed("Json error"),
    ParseError: () => Effect.succeed("Parse error"),
  })
);

Effect.runPromise(main).then(console.log);

我们还没有完成。有一个类型错误!

https://vmfiooakcvcmnormoutn.supabase.co/storage/v1/object/public/sandromaglione-com/blog-images/missing-dependency-issue-effect-layers

我们仍然无法运行main,因为似乎缺少一个依赖关系!

Type 'PokeApiUrl' is not assignable to type 'never'.

这是一个缺失的依赖关系。这里发生了什么?我们不是已经向 Layer.mergeAll 提供了 PokeApiUrl 吗?

const MainLayer = Layer.mergeAll(
  PokeApi.Live,
  PokemonCollection.Live,
  BuildPokeApiUrl.Live,
  PokeApiUrl.Live
);
WARNING

层间依赖

PokeApiUrl 在那里,但我们遗漏了其他东西:层之间的依赖关系。让我们在下一课中修复这个问题 👇

[Effect Layers] 04. 构建和组合层
https://0bipinnata0.my/posts/course/effect-beginners-complete-getting-started/layers/04-building-and-composing-layers/
Author
0bipinnata0
Published at
2025-08-30 18:20:11