As your progress your journey in learning effect
you will encounter some common patterns.
These are not your "best way of doing things". but more some general guidelines.
These patterns come from my personal experience in working in effect production codebases, as well as my time spent reviewing effect code and questions on the Discord channel.
Some code will contain APIs that are not covered in the course.
This is your chance to start exploring more of the effect API by searching the API reference by yourself.
You will need to do that often in your effect journey.
Start from schemas
The quality (and safety) of your app depends on the quality of your data.
For any medium to large size project having a strong schema layer is key. Ideally, all the data coming from external sources should be filtered by a schema.
An example is the code for this website (all based on effect
of course).
At the beginning I started by defining schemas for all entities (courses, workshops, snippets, lessons):
import { Schema } from "effect";
import { Language, Library } from "./shared";
export class MetadataCourse extends Schema.Class<MetadataCourse>(
"MetadataCourse"
)({
title: Schema.NonEmpty,
description: Schema.String,
githubUrl: Schema.TemplateLiteral("https://github.com/", Schema.String),
languages: Schema.NonEmptyArray(Language),
libraries: Schema.Array(Library),
updatedAt: Schema.DateFromString,
}) {}
You want to be as strict as possible when defining a schema. Notice for example how githubUrl
requires the "https://github.com/"
prefix to be valid.
These types will propagate everywhere in the app. If they are valid and correct implementing the rest of the app will be way easier.
Organize (nearly) everything in services
The second key to success is the quality of your services.
Think of an effect app as a composition of services. The Layer
model makes composition a breeze. Therefore, the key is designing the correct API for your services.
I usually have a specific
services
folder that contains all the services: definition, implementations, errors and layers.
For example, all the content of this course is written in mdx
. I implemented an Mdx
service for this:
/// 👇 Error(s)
export class MdxError extends Data.TaggedError("MdxError")<
Readonly<{
error: unknown;
}>
> {}
/// 👇 Implementation
const make = /// ...
/// 👇 Service definition
export class Mdx extends Context.Tag("Mdx")<
Mdx,
Effect.Effect.Success<typeof make>
>() {
/// 👇 Layers
static readonly Live = Layer.effect(this, make);
}
Use a custom runtime from the beginning
At the beginning you may be tempted to just use Effect.provide
and Effect.runPromise
. After all, initially you have just a few layers and effect executions.
Nonetheless, my suggestion is to use at least ManagedRuntime
from the start.
In fact, I define a Runtime
even when I don't have any layers yet:
import { Layer, ManagedRuntime } from "effect";
const MainLayer = Layer.empty;
export const RuntimeClient = ManagedRuntime.make(MainLayer);
You will be glad once you start adding services and layers. Suddenly all you have to do is updating MainLayer
. That's all!
You app is already using RuntimeClient.runPromise
everywhere, so all the layers are immediately available!