We can start to see how we can compose a more organized app using our services:
BuildPokeApiUrldepends onPokeApiUrlto build the request urlPokeApidepends on bothPokemonCollectionandBuildPokeApiUrlto build the url with the Pokémon name
These dependencies are not created when defining each service. Every service definition is independent and can be understood or updated without reading the full codebase.
Dependencies are created when implementing each service. It's easier to start from services with no dependencies, in this case PokemonCollection and PokeApiUrl:
export class PokemonCollection extends Context.Tag("PokemonCollection")<
PokemonCollection,
Array.NonEmptyArray<string>
>() {
static readonly Live = PokemonCollection.of(["staryu", "perrserker", "flaaffy"]);
}export class PokeApiUrl extends Context.Tag("PokeApiUrl")<
PokeApiUrl,
string
>() {
static readonly Live = Effect.gen(function* () {
const baseUrl = yield* Config.string("BASE_URL");
return PokeApiUrl.of(`${baseUrl}/api/v2/pokemon`);
});
}BuildPokeApiUrl uses PokeApiUrl to build the url:
export class BuildPokeApiUrl extends Context.Tag("BuildPokeApiUrl")<
BuildPokeApiUrl,
({ name }: { name: string }) => string
>() {
static readonly Live = Effect.gen(function* () {
const pokeApiUrl = yield* PokeApiUrl; // 👈 Create dependency
return BuildPokeApiUrl.of(({ name }) => `${pokeApiUrl}/${name}`);
});
}Finally, PokeApi uses both PokemonCollection and BuildPokeApiUrl:
export class PokeApi extends Context.Tag("PokeApi")<PokeApi, PokeApiImpl>() {
static readonly Live = PokeApi.of({
getPokemon: Effect.gen(function* () {
const pokemonCollection = yield* PokemonCollection; // 👈 Create dependency
const buildPokeApiUrl = yield* BuildPokeApiUrl; // 👈 Create dependency
// 👇 `buildPokeApiUrl` is the function from `BuildPokeApiUrl`
const requestUrl = buildPokeApiUrl({
/// 👇 `pokemonCollection` is a `NonEmpty` list of `string`
name: pokemonCollection[0],
});
const response = yield* Effect.tryPromise({
try: () => fetch(requestUrl),
catch: () => new FetchError(),
});
if (!response.ok) {
return yield* new FetchError();
}
const json = yield* Effect.tryPromise({
try: () => response.json(),
catch: () => new JsonError(),
});
return yield* Schema.decodeUnknown(Pokemon)(json);
}),
});
}With this implementation it's the
getPokemonfunction that has a dependency onPokemonCollectionandBuildPokeApiUrl, not thePokeApiservice itself.That's an important distinction. We will raise the dependency to
PokeApilater when needed.
