We can start to see how we can compose a more organized app using our services:
BuildPokeApiUrl
depends onPokeApiUrl
to build the request urlPokeApi
depends on bothPokemonCollection
andBuildPokeApiUrl
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
:
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
getPokemon
function that has a dependency onPokemonCollection
andBuildPokeApiUrl
, not thePokeApi
service itself.That's an important distinction. We will raise the dependency to
PokeApi
later when needed.