Итак, продолжаем серию наших игр 😅
А продолжаем ее мы с поста про RPC 👀
У нас с вами
в прошлом посте был логичный вопрос:
А че не REST? 🤨
И вопрос, кстати, хороший.
Потому что REST - это нормальная такая взрослая рабочая история.
Я честно ребят не из лагеря, мол, рест ууумер! Срочно всё перепииисываем!!!” Не-не) REST - это вполне ок на сегодняшний день, куда не плюнь все его весело радостно используют.
Но есть нюанс ( как в том бородатом анекдоте ).
Представьте, что у вас не маленький проект, а уже большая энтерпрайз махина. У вас есть много клиентских модулей, много серверных модулей, много команд, много методов и всё это постоянно!! меняется. И внутри модулей вам нужно вызывать что-то такое:
server.user.getPermissions()
server.card.getList()
server.balance.getSummary()
server.notifications.markAsRead()
Вариант номер один - классический путь с рестом.
Под каждый метод мы что делаем? Праавильно!
Заводим отдельную ручку:
GET /permissions
GET /card/list
GET /balance/summary
POST /notifications/read
Сначала кажется: ну и чё, норм же:))
Да..
Норм))
Но пока у вас:
- мало модулей;
- мало методов;
- мало команд;
- мало изменений;
А потом время проходит и велком ту энтерпрайз)))
И чет уже не так удобненько стало))
Внезапно появляется:
- тонна ручек;
- тонна сваггеров / openapi-схем;
- куча роутинга;
- разные конвенции по наименованию;
- разные версии API;
- много бойлерплейта;
- “ой, а кто сломал контракт?”;
- “а эта ручка ещё используется?”;
- “а почему у соседнего модуля почти то же самое, но называется иначе?”
И вот тут люди начинают думать:
слушайте… а может мы не будем под каждый чих делать отдельную рестовую ручку?
И появляется идея:
генерализовать вызов.То есть вместо:
у каждого метода свой эндполинтделаем:
у нас есть один общий способ доставить вызов в нужный модульУсловно, это может выглядеть так - единая нотация написания сообщения:
{
"moduleId": "card",
"method": "getList",
"params": {
"page": 1
}
}
То есть платформа сама понимает:
- куда это доставить;
- какой модуль нужен;
- какой метод вызвать;
- какие параметры передать;
- как связать запрос и ответ;
- как вернуть результат обратно.
Тут на самом деле важно понять что мы ни в коем случае не убиваем рест. И боже упаси мы не говорим “никогда не используй рест”.
Мы просто говорим что в некоторых модульных системах удобнее генерализовать транспортный слой, чтобы разработчик работал вот так:
await server.assets.getList()
И не думал а какая там ручка? какой URL? какая версия апишки? это GET или POST? а кто владеет этим endpoint? и тд
Для разработчика остаётся нормальный, привычный я бы сказала, типизированный апи. А платформа уже сама решает как этот вызов доставить. И это даёт очень приятную штуку:
платформенную гибкость.
Сегодня внутри может быть
socket.io, завтра какой-нибудь брокер сообщений а послезавтра ещё какая-нибудь весёлая инженерная конструкция. Но апиха для разработчика не меняется - в этом и смысл генерализованного вызова:
стабильный API для разработчика + гибкая доставка внутри платформы.
Конечно и в данном случае есть своя цена этой красоте. Если мы генерализуем вызовы, нам нужно аккуратно решать:
- как описывать контракты;
- как валидировать параметры;
- как версионировать методы;
- как трассировать вызовы;
- как связывать request и response;
- как обрабатывать ошибки;
- как не превратить универсальный транспортный слой в универсальную помойку 😬🫠
И вот отсюда появляется следующая интересная тема:
а коммуникация у нас синхронная или асинхронная?Потому что снаружи разработчик пишет:
await server.card.getList() и это выглядит как обычный синхронный request/response. Однако, внутри платформы вызов уже может ехать через очередь, брокер и асинхронную доставку. И вот там начинается отдельная инженерная меджик)
#conference #holyjs #architecture #modularbff