Все части уже лежат на столе. Книга башен по проводу (урок 90), договор окошек (урок 91), сервер и тест в памяти (урок 92), служебная линия (урок 93), книги на SQL (урок 94), подвал с архивом (урок 95) и терминал ночной смены (урок 96). Сегодня нового модуля Effect не будет. Будет одно новое окошко и сборка: всё вместе, одним значением Layer, которому снаружи нужны только провод, база, файлы и пути.
Что нужно Почтамту
Тесты накопительные: идут все прошлые этапы, а сверху новый модуль src/main.ts.
- Окошко выдачи. Новая группа
desk:deliver(POST/letters/:id/deliver) отправляет письмо и отдаёт его с новым статусом,dead(GET/dead) отдаёт мёртвые письма из подвала. Нет такого письма, и выдача отвечает 404, как окошкоbyId. - Договор растёт, старый не переписывают. Новый договор
PostOfficeApiэто старыйPochtamtApiплюс группаdesk. Окошкиlettersиsystemиз урока 92 работают в нём без изменений. - Отправка. Письмо со статусом
acceptedспрашивает книгу башен про свой адрес. Башня открыта: статусsent, в журналеотправлено. Башня закрыта: статусdead, в журналемёртвое: башня T-3 закрыта(с кодом башни), и письмо ложится в архив подвала. Статус и журнал меняются одной транзакцией. - Второй раз не отправляют. Письмо, которое уже
sentилиdead, выдача отдаёт как есть: журнал не растёт, в подвал второй раз не ложится. - Один слой.
PostOfficeLive({ towersUrl, archiveDir })собирает все три группы обработчиков, книги на SQL с миграциями и книгу башен по адресуtowersUrl. Снаружи ему нужны толькоHttpClient,SqlClient,FileSystemиPath.
Чек-лист перед сдачей
- Тесты всех прошлых этапов зелёные.
- Письмо на открытую башню:
sent, журнал['принято', 'отправлено'], окошкоbyIdвидит новый статус. - Письмо на закрытую башню:
dead, журнал с кодом башни, строка JSON в<archiveDir>/dead/letters.jsonl, окошко/deadотдаёт его. - Повторная выдача ничего не меняет.
- Чужой адрес у окошка это 422, нет письма у выдачи это 404,
healthотвечает. - В типе
PostOfficeLiveсреди требований нетLetters,TowerBookи ничего сHttpRouter.Request.
Почтамт, этап 7
Подумай до кода
Код выдачи короткий. Трудно здесь не написать, а сложить: кто кому что даёт и в каком порядке.
- Чьи это обработчики. Группа обработчиков в слое называется по договору и группе:
HttpApiGroup.Service<'pochtamt', 'letters'>. У старого и нового договора одно имя,pochtamt. Что это значит дляPochtamtHandlersиз урока 92: их надо писать заново дляPostOfficeApiили нет? - Пять сервисов у одного обработчика. Выдаче нужны книги, письма, книга башен, файлы и пути. Вспомни урок 92: сервис, который обработчик берёт сам внутри
handle, оставляет в слоеHttpRouter.Request<"Requires", ...>, и слой не закрывается. Брать пять сервисов по одному при сборке можно. А можно снять их все разом. - Порядок без сервиса. Миграции это слой, который ничего не даёт:
Layer.Layer<never, ...>. Он нужен не ради значения, а ради порядка: таблицы должны появиться раньше первого письма. Каким сочетаниемLayer.provideиLayer.provideMergeсказать «сначала этот, потом все остальные»? - Что внутри транзакции. Обновить статус и записать журнал нужно вместе: письмо
sentбез записи в журнале это ложь в книгах. А запись в подвал? Если она внутри транзакции и упала, откатятся и статус, и журнал. Если снаружи, письмо может стать мёртвым в книгах и не попасть в подвал. - Повтор у окошка выдачи. Горожанин нажал «отправить» дважды, или прокси повторил запрос, как Магистраль повторяла кадры в уроке 89. Выдача, которая отправляет письмо второй раз, это дубль уже от нас.
Тест собирает Почтамт так же, как его соберёт Гекс, только слои снаружи другие: провод в книгу башен подменён, база это PGlite в памяти, подвал это карта строк.
const App = PostOfficeLive({ towersUrl: 'http://towers.ankmor', archiveDir: '/srv/pochtamt' }).pipe(
Layer.provideMerge(Layer.mergeAll(Towers, PgliteClient.layer(), MemoryFs, Path.layer)),
);
const day = Effect.gen(function* () {
const api = yield* HttpApiTest.groups(PostOfficeApi, ['letters', 'desk']);
const letter = yield* api.letters.send({ payload: { from: 'Липвиг', to: 'T-2', text: 'на мост' } });
return yield* api.desk.deliver({ params: { id: letter.id } });
}).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(App, HttpServer.layerServices)));
На настоящем сервере Towers станет FetchHttpClient.layer, PGlite станет @effect/sql-pg, карта строк станет NodeServices.layer. Сам PostOfficeLive не изменится ни на строку. Это и есть то, ради чего вся граница с миром была сервисами с урока 90.
Нестабильно. Всё, на чём стоит Почтамт, в 4.1 помечено
@stability unstable:effect/http,effect/http-apiсHttpApiTest,effect/sqlсMigrator,FileSystemиPath. Перед обновлениемeffectпрогони тесты всех семи этапов. Урок написан на 4.1.0.
Практика
Глубже в архиве
Как раскладывать сервер на Effect по слоям и сервисам, разобрано на v3 в уроке про архитектуру бэкенда старого раздела.
Итог
Утром дня открытия Липвиг сам снимает замок с двери Почтамта. На двери вывеска: список окошек, что каждое принимает и чем отказывает. Первое письмо приносит Тикка, ровно в восемь ноль-ноль. Окошко отвечает за миг, выдача отправляет, Сундук лязгает крышкой у входа.
Ранга за этот заказ нет: следующий ждёт в конце EF12.
Дальше
К одиннадцати очередь доходит до реки. Каждое письмо у окошка спрашивает книгу башен Магистрали, каждое у выдачи спрашивает её ещё раз. К полудню книга перестаёт отвечать. Не отказывает, не говорит «нет такой башни», а молчит. Окошки одно за другим начинают отвечать 500. Горожане стоят с письмами в руках. Рыдкулл открывает журнал Почтамта: в нём пусто. Ни строки о том, что случилось, где и почему.
В дверях стоит гонец в цветах дворца. Он не смотрит ни на очередь, ни на окошки, только на Рыдкулла: «Аркканцлера ждут во дворце. Сейчас».
Следующий раздел курса это EF12 «Свет в окнах»: логи, трассы и метрики, по которым видно, что пошло не так. Начинается он с урока 98.