Введение
Что такое LiteMW
LiteMW это набор связанных библиотек для разработки серверных приложений на Typescript/Javascript.
Ключевой концепцией LiteMW является построение процессов обработки запросов
на основе промежуточных обработчиков (middleware). Этот же механизм используют такие библиотеки как: express, koa, fastify и множество других, в том числе в других языках и средах.
Промежуточные обработчики
Мидлвейры предоставляют гибкие возможности по интеграции различных подготовленных функций в разрабатываемые приложения, это могут быть: аутентификация и авторизация, валидация, журналирование.
Скорее всего, разрабатывая на express и подобных фреймворках, вы могли сталкиваться с библиотеками bodyparser, passport, helmet, multer. Все они представляют собой мидлвейр, который встраивается в цепочку обработки запроса до того как очередь дойдёт до ваших конечных обработчиков. Тем самым "наполняя" контекст запроса данными, среди которых например могут быть тело запроса, информация о пользователе, файлы, cookie.
Достоинства и недостатки
Подход с использованием мидлвейров (он же реализация паттерна "Chain of Responsibility" ) упрощает разработку, позволяя разбить обработку запроса на более простые блоки, выполняющие определённую функцию. Однако, когда в программе появляется много мидлвейров, которые записывают свои данные в состояние контекста обработки (хранилищем этого состояния могут быть req, res, res.locals, ctx и тд.), становится крайне трудно понять какие данные должны быть в контексте, а какие нет, даже используя типизацию Typescript .
В случае большинства фреймворков (таких, как express и koa), одном из решений может быть аугментация типа их контекста (или состояния res.locals / ctx.state). Такой вариант подходит, если ваш мидлвейр работает глобально, и соответственно записываемые им объекты будут во всех цепочках обработки (например в случае passport и bodyparser).
Что, конечно, происходит не всегда: зачастую определённые мидлвейры требуются на конкретные маршруты или контроллеры - в таком случае данное решение тоже подходит, но в типе контекста появится множество опциональных параметров, например следующий код добавит в объект req опциональные поля rights, lessons и students:
declare global {
namespace Express {
interface Request {
rights?: Right[]
lessons?: Lesson[]
students?: Student[]
...
}
}
}
Вторым вариантом является указание типов напрямую в дженериках вашей библиотеки, для express это будет выглядеть следующим образом:
app.get<Params,ResBody,ReqBody,ReqQuery,Locals>(
'/api/v1/path', (req,res) => {}
)
Соответственно, вместо Params, ResBody, ReqBody, ReqQuery и Locals вы можете указать ожидаемый тип.
Итого для типизации обычных мидлвейров требуется либо писать множество аугментаций, либо в месте использования писать множество boilerplate-кода; это всё, конечно же, замедляет процесс разработки, либо поощряет отсутствие типизации вообще.
MVC фреймворки
С решением описанной проблемы прекрасно справляются тяжеловесные фреймворки, вроде Nest или Adonis.
В большинстве случаев подобные фреймворки предоставляют Dependency Injection контейнер для управления провайдерами и контроллерами; а многие функции, такие как извлечение тела и параметров запроса, валидация, авторизация, представлены декораторами. Например в случае Nest:
@Controller('lessons')
export class LessonsController {
@Roles(Role.Admin)
@UseGuards(AuthGuard)
@UseGuards(RolesGuard)
@Post('change/:id')
changeLesson(
@Param('id', ParseIntPipe) id: number,
@Body() lessonChangeDto: LessonChangeDto
) {
// return something
}
}
Данный код создаёт контроллер с префиксом lessons и обработчиком post запроса по маршруту change/:id, он доступен определённой роли авторизованному пользователю, и в аргументах функции представлены параметр запроса и тело с некоторой схемой LessonsChangeDto (объекты Role, AuthGuard, RolesGuard и LessonChangeDto создаёт разработчик).
Подобное использование декораторов позволяет декларативно описывать контроллеры и обработчики, но зачастую приводит к излишнему усложнению логики. Например, в Nest существуют:
- Мидлвейры
- Пайпы (pipes)
- Гарды (guards)
- Интерсепторы (interceptor)
И зачастую сложно сразу и точно определить порядок, в котором они применяются, либо же приходится изворачиваться для того, чтобы этот порядок обойти, например, так как сначала выполнится гард, валидацию тела запроса (если оно требуется в гарде) придётся выносить в аналогичный гард.
На данном этапе можно заметить, что все представленные выше сущности технически выполняются одинаково: они получают контекст запроса, обрабатывают его, и передают управление дальше, или заканчивают обработку.
Решение
LiteMW в свою очередь предоставляет простые и прозрачные инструменты на основе мидлвейров, при этом решает проблему типизации контекста ---> Почему LiteMW