Effect CommunityEC
Effect Community2y ago
1907 replies
addamsson

Composing Functionality within Services in Effect

Is there a built-in way in Effect that I use to compose the functionality that I have within services (as opposed to composing the services themselves)? Let's say that I have a service like this:
export type Operation<R, E, A, B> = {
    tag: Context.Tag<Operation<R, E, A, B>, Operation<R, E, A, B>>;
    readonly execute: (input: A) => Effect.Effect<R, E, B>;
};

export const GetUserById = Context.Tag<
    Operation<never, Error, string, User>
>("Operation/GetUserById");

export const GetUserByIdLive = Layer.effect(
    GetUserById,
    Effect.gen(function* (_) {
        const userService = yield* _(UserService);

        return GetUserById.of({
            tag: GetUserById,
            execute: (userId: string) => {
                return userService.findById(userId);
            },
        });
    })
);

when executed this will return the user using the userService which is a dependency of this service.

What if I have an operation like this too:
export const DeleteUser = Context.Tag<
    Operation<Transaction, Error, User, void>
>("Operation/DeleteUser");

export const DeleteUserLive = Layer.effect(
    DeleteUser,
    Effect.gen(function* (_) {
        const userService = yield* _(UserService);

        return DeleteUser.of({
            tag: DeleteUser,
            execute: (user: User) => {
                return userService.delete(user.id);
            },
        });
    })
);

and I want to combine them:
export const DeleteUserById = Context.Tag<
    Operation<Transaction, Error, string, void>
>("Operation/DeleteUserById");

export const DeleteUserByIdLive = Layer.effect(
    DeleteUserById,
    Effect.gen(function* (_) {
        const getUser = yield* _(GetUserById);
        const deleteUser = yield* _(DeleteUser);

        return DeleteUserById.of({
            tag: DeleteUserById,
            execute: (userId: string) => {
                return pipe(
                    getUser.execute(userId),
                    Effect.flatMap(deleteUser.execute)
                );
            },
        });
    })
);

here all I do is pipe the output of GetUserById into DeleteUserById since the signatures align nicely.
Is there some built-in way to compose services that have a shape like this (eg: functions that naturally compose, and only their dependencies are different)?

*I'm asking this because I come from fp-ts and in fp-ts this is very easy to do as I can do the piping:
const deleteUserById = pipe(
    getUser.execute(userId),
    RTE.flatMap(deleteUser.execute)
)

and then supply the dependencies in the end like:
const result = deleteUserById(deps)();
Was this page helpful?