Dependencies between services

We can start to see how we can compose a more organized app using our services:

  • BuildPokeApiUrl depends on PokeApiUrl to build the request url
  • PokeApi depends on both PokemonCollection and BuildPokeApiUrl to 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:

PokemonCollection.ts
export class PokemonCollection extends Context.Tag("PokemonCollection")<
  PokemonCollection,
  Array.NonEmptyArray<string>
>() {
  static readonly Live = PokemonCollection.of(["staryu", "perrserker", "flaaffy"]);
}
PokeApiUrl.ts
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:

BuildPokeApiUrl.ts
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:

PokeApi.ts
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 getPokemon function that has a dependency on PokemonCollection and BuildPokeApiUrl, not the PokeApi service itself.

That's an important distinction. We will raise the dependency to PokeApi later when needed.