构建和组合层
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中提取服务。
在我们的例子中,PokeApiUrlLive 和 BuildPokeApiUrlLive 是使用 Effect.gen 定义的,因此我们需要使用 provideServiceEffect 从 Effect 中提取服务。
这种策略很快就变得难以维护和阅读。此外,如果一个服务依赖于另一个服务(相互依赖),这会变得更糟。
IMPORTANTLayer 的优势
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.of和BuildPokeApiUrl.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);我们还没有完成。有一个类型错误!

我们仍然无法运行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在那里,但我们遗漏了其他东西:层之间的依赖关系。让我们在下一课中修复这个问题 👇