Раздел 8 · Frameworks и Libs

Effect HTTP и выбор фреймворка

middle-senior~30 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Effect HTTP и выбор фреймворка

Весь раздел мы держали в голове два полюса: Fastify как систему плагинов и Nest как IoC-контейнер. Это финал, и он добавляет третий полюс и закрывает главный вопрос серии: как выбирать. Третья парадигма, Effect HTTP из effect/http-api, смотрит на сервер иначе обоих. Не «обработчик с побочными эффектами», а описание API, которое рантайм интерпретирует.

Здесь пригодится знать, как устроен сам пакет. Раньше платформа жила в отдельном @effect/platform со своей версией, теперь она въехала внутрь effect: одна версия на всю экосистему. HTTP-сервер, схемы API и клиент импортируются из effect/http и effect/http-api.

Глубоко Effect-подход разобран в разделе 25 (проект Pulse), здесь даём его как парадигму в сравнении и строим мост туда. Цель урока, не выучить третий фреймворк, а увидеть три модели рядом и научиться выбирать под задачу.

1. HTTP-сервер как описание, а не как процедура

В Fastify и Nest обработчик, это функция с побочными эффектами: читает запрос, дёргает сервисы, шлёт ответ, бросает исключение при ошибке. В Effect сервер описывается декларативно. Сначала ты объявляешь форму API на схемах, потом отдельно поставляешь реализацию.

import { Schema } from 'effect';
import { HttpApi, HttpApiGroup, HttpApiEndpoint, HttpApiSchema } from 'effect/http-api';

class ArticleNotFound extends Schema.TaggedError<ArticleNotFound>()('ArticleNotFound', {}) {}

const api = HttpApi.make('app').add(
  HttpApiGroup.make('articles').add(
    HttpApiEndpoint.get('byId', '/articles/:id', {
      params: Schema.Struct({ id: Schema.String }),
      success: Article,
      error: ArticleNotFound.pipe(HttpApiSchema.status(404)),
    }),
  ),
);

Это ещё не сервер, это контракт: какие есть эндпоинты, какие у них пути, схемы входа, схемы успеха и какие ошибки возможны. Схемы написаны на Schema, поэтому одна декларация даёт сразу контракт, рантайм-валидацию и TS-типы.

2. Реализация как Layer

Обработчики подключаются отдельно, через HttpApiBuilder. Каждая группа, это Layer, который зависит от нужных сервисов и поставляет реализацию эндпоинтов.

import { Effect, Layer } from 'effect';
import { HttpApiBuilder } from 'effect/http-api';

const articlesLive = HttpApiBuilder.group(api, 'articles', (handlers) =>
  handlers.handle('byId', ({ params }) =>
    Effect.gen(function* () {
      const service = yield* ArticlesService; // зависимость видна в типе Effect
      return yield* service.byId(params.id); // ошибка ArticleNotFound в канале результата
    }),
  ),
);

const apiLive = HttpApiBuilder.layer(api).pipe(Layer.provide(articlesLive));

Запуск, тоже Layer: apiLive поднимается через NodeHttpServer из @effect/platform-node. Под капотом есть и низкоуровневый слой (HttpRouter, HttpServerRequest, HttpServerResponse), это контраст: ближе к Fastify по стилю, но без декларативного контракта. Высокоуровневый HttpApi, основной способ.

Главное, что здесь видно: зависимость ArticlesService не приходит из контейнера через декоратор, она запрашивается через yield* и видна прямо в типе Effect. Layer-граф поставляет её композицией. Это DI без контейнера и без рефлексии, та самая цена которой мы считали в уроке 09.

3. Что даёт декларативность бесплатно

Раз API, это значение со схемами, из него можно вывести многое, чего в Fastify и Nest пишешь руками или генерируешь отдельным шагом:

  • OpenAPI и Swagger выводятся из той же декларации (HttpApiSwagger), без отдельных декораторов @ApiProperty (Nest) и без сборки схемы из JSON Schema роутов (Fastify).
  • Типобезопасный клиент деривится из того же api (HttpApiClient): фронт получает вызовы с теми же типами, без кодогена.
  • derive вместо синхронизации: схема, типы, валидация, документация и клиент, это проекции одной декларации. Нет ручного держания их в синхроне, главного источника рассинхрона в Fastify и Nest.

Это та же мысль, что в type providers Fastify (одна схема, типы и валидация), но доведённая до конца: из одной декларации выводится вся обвязка.

дальше после входа

Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.

Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.