Most common effect patterns

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:

services/Mdx.ts
/// 👇 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:

RuntimeClient.ts
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!