Проектирование переиспользуемых модулей в NestJS

Reading time: 3 minutes

При разработке современных приложений часто возникает задача повторного использования класса или модуля в другом проекте, в связанном сервисе или при добавлении новых интерфейсов. Допустим, есть HTTP API, но теперь нужен ещё WebSocket или gRPC — стандартная ситуация. Хочется быстро и просто расширять функциональность, опираясь на уже написанный код.

NestJS Flexible, reusable, scalable modules design

Задача кажется тривиальной: берёшь нужный код, убираешь зависимости, оставляешь только входные параметры. Но книжный совет не работает, когда есть интеграции с другими частями системы и связи между сущностями. В таких условиях задача становится нетривиальной.

Проблема первая — связи между сущностями

Рассмотрим пример: домен — спальня со стульями, столами и кроватями.

Схема таблиц выглядит так:

NestJS Flexible, reusable, scalable modules design

Схема разумная и привычная для разработчиков. Сущности стульев, столов и кроватей имеют связи ManyToOne с сущностью спальни. Если строить HTTP API поверх такой схемы — получим проблемы с гибкостью, масштабируемостью и переиспользованием.

Когда понадобится разделить функциональность между микросервисами, окажется, что это невозможно: микросервис для стульев неизбежно тащит за собой часть, отвечающую за спальни. Такой подход даёт абсолютно монолитную архитектуру без масштабируемости и переиспользования кода.

Проблема вторая — инфраструктура

Протоколы, базы данных, интеграции с сервисами — у каждого своя специфика, от которой не абстрагируешься. HTTP — это HTTP, WebSocket — это WebSocket. Нет универсального «интерфейса сетевого взаимодействия» для обоих протоколов. Модули пишутся явно, под конкретные случаи. Попытка сделать один модуль на все случаи жизни — путь к монолиту: сложно поддерживать, много ошибок, непредсказуемое поведение.

Решение

Все эти проблемы решаемы. Программу можно сделать гибкой, а код — переиспользуемым без правок.

Решение проблемы связей

Нет связей — нет проблем со связями. Отказываемся от такой схемы данных. Связи между сущностями модулей — неверный путь. Спальня, стул, стол, кровать — четыре разных модуля, у которых нет ничего общего (никаких ссылок). Спальня — это агрегат для стульев, столов и кроватей. Схема данных меняется и выглядит так:

NestJS Flexible, reusable, scalable modules design

При такой схеме нижние компоненты можно перемещать и разделять. Их даже можно написать на другом стеке. Главное — обеспечить интерфейс взаимодействия для верхнего компонента (Bedroom), чтобы он мог получать столы, стулья и кровати.

Истинная модульность

Модули строятся по следующим принципам:

  1. Низкоуровневые модули содержат только бизнес-логику.
  2. Высокоуровневые модули (Http, Websocket и т.д.) создаются по мере необходимости и предоставляют внешние интерфейсы для низкоуровневых.
  3. Модуль отвечает только за свой домен и не предоставляет интерфейсы вложенных модулей. Например, модуль Zoo не должен раскрывать интерфейс дочернего модуля Dog для изменения имени сущности.

Низкоуровневый модуль содержит только бизнес-логику и работает с хранилищами. Его структура:

NestJS Flexible, reusable, scalable modules design

В таком модуле нет Controller, WebSocket Gateways, gRpcHandlers — только бизнес-логика в сервисах, зависящих исключительно от входных аргументов.

Если нужно предоставить HTTP API для этого модуля? Низкоуровневый модуль уже готов. Просто создаём новый HTTP-модуль, который использует низкоуровневый и предоставляет HTTP-контроллеры, работающие с методами сервисов.

NestJS Flexible, reusable, scalable modules design

На диаграмме убрал постфикс Module у Chair.

HttpChair оборачивает Chair и предоставляет контроллер.

Описываем каждую часть приложения — архитектура принимает вид:

NestJS Flexible, reusable, scalable modules design

Любой внешний интерфейс для низкоуровневых модулей — Http, Websocket, Rpc и другие — масштабируется и гибко настраивается.

Теперь создаём модуль Bedroom на основе других модулей:

NestJS Flexible, reusable, scalable modules design

Над Bedroom можно поднять Http и Websocket модули.

Важно: HttpBedroom предоставляет интерфейс только для спальни. Создавать, редактировать и удалять стулья через HttpBedroom нельзя.

Собираем HTTP-приложение из модулей:

NestJS Flexible, reusable, scalable modules design

Аналогично строится WebSocket-приложение из WebSocket-модулей:

NestJS Flexible, reusable, scalable modules design

При таком подходе получаем гибкость, масштабируемость и переиспользование. Из модулей собирается любой вариант приложения.

Пример кода: https://gitlab.com/cimpleo/blog/nest-modules-architect-example

Table of Contents