Как стать автором
Обновить

Написание слоя API в приложении — это прошлый век! Встречайте универсальный прокси

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров25K

Большинство компаний регулярно сталкиваются с проблемой постоянной модификации слоя API в своих веб-приложениях в ответ на изменения API сервера. Некоторые из них прибегают к автогенерации кода на основе схем Swagger, другие переписывают код вручную. Однако эти подходы требуют значительных ресурсов и времени, а также часто приводят к избыточному коду, который растет в объеме вместе с количеством задействованных в коде методов API, увеличивая размер бандловI.

В данной статье я хочу поделиться методом, который позволяет избежать этих сложностей.

Давайте начнем с определения сути и назначения API слоя в веб-приложениях. Этот слой представляет собой интерфейс между приложением и бэкэндом, его основные задачи:

  • предоставление списка доступных методов API и их описание

  • выполнение запроса к серверу и возврат ответа сервера приложению

Этот слой не должен содержать логику, кроме логики транспортировки данных между фронтендом и бэкэндом. В нем не должно быть никаких дополнительных вычислений или обработки данных - это задача ядра приложения.

Если мы взглянем на ситуацию объективно, мы увидим, что для разработчика изменяются только семантика вызовов методов и их количество в API - остальные аспекты не так важны.

Вопрос, который мучает многих разработчиков: "Почему мне приходится возиться с этим прокси-слоем каждый раз, когда меняется API на бэкэнде?"
Ответ, возможно, уже скрывается в самом вопросе.

Идея очень проста и в то же время гениальна: прокси-объект + TypeScript!

Прокси-объект позволяет нам делать практически все, что мы захотим, а TypeScript не даст нам сделать лишнего - то, чего нет в интерфейсе API!

Свобода действий при ограничениях - это ключ к гармонии!

Прокси-объект позволяет нам "схлопнуть" весь тот однотипный код десятков, сотен методов в слое АПИ, который генерируют инструменты или пишет человек (код по передаче и получению данных) в один метод.

Проще говоря: прокси конструирует и вызывает метод во время его вызова из кода приложения.

Давайте я вам покажу, как это работает на примере:

const getAPI = (apiUrl) =>
    new Proxy(
        {},
        {
            get(_, method_name) {
                return async (props) => {
                    const apiMethod = camelToSnake(method_name);
                    const httpMethod = apiMethod.split('_')[0].toUpperCase();
                    const isGetMethod = httpMethod === 'GET';
                    const url = new URL(`${apiUrl}/${apiMethod}`);
                    const options = {
                        method: httpMethod,
                        headers: { 'Content-Type': 'application/json' },
                    };

                    if (isGetMethod) {
                        url.search = new URLSearchParams(props).toString();
                    } else {
                        options.body = JSON.stringify(props);
                    }

                    const response = await fetch(url, options);
                    return response.json();
                };
            },
        },
    );

В коде для примера представлена упрощенная реализация логики прокси-объекта API. Допустим мы предполагаем, что имена методов API всегда начинаются с названия HTTP-метода, а реальные имена методов на бэкэнде имеют snake формат записи ( на практике у вас могут быть любые другие условия и соглашения ).

Пример использования прокси-объекта:

const api = getAPI('http://localhost:3000/api');

// С Proxy мы можем писать реальные вызовы методов
api.getTodos(); 
// --> fetch('http://localhost:3000/api/get_todos?...', { method: 'GET', ... })

api.postTodo({ title: 'test' }); 
// --> fetch('http://localhost:3000/api/post_todo', { method: 'POST', ... }))

api.deleteTodo({ id: 1 }); 
// --> fetch('http://localhost:3000/api/delete_todo', { method: 'DELETE', ... }))

// С Proxy никто не запретит нам писать всякую билеберду
api.putLalalalala('lololololo'); 
// --> fetch('http://localhost:3000/api/put_lalalalala', { method: 'PUT', ... }))

Однако, чтобы ограничить нашу фантазию рамками реальной реализации API, нам необходимо описание API в виде интерфейса на TypeScript.

type Todo = {
    id: number;
    title: string;
};

interface API {
    getTodos: async () => Todo[];
    postTodo: async ({ title: string }) => Todo;
    deleteTodo: async ({ id: number }) => Todo;
}

Давайте дополним реализацию прокси-объекта типами:

import type { API } from '@companyName/api';

const getAPI = (apiUrl) => new Proxy( ... ) as API;

Теперь api будет содержать описания методов API из интерфейса TypeScript, что обеспечивает автоподсказки в IDE и не позволяет нам выходить за рамки реальной реализации. Typescript предоставляет нам описания методов API, производит валидацию параметров и обеспечивает информацией о возвращаемом результате.

Попытка написания вызова не существующего метода:

api.putLalalalala('lololololo');

или любого другого вызова, не соответствующего интерфейсу, приведет к ошибке.

Если на сервере изменится интерфейс API, разработчики бэкэнда должны будут изменить описание интерфейса (сгенерировать новую версию при помощи инструментов для swagger или изменить руками - для тех кто АПИ пишет руками) и опубликовать его новую версию. Веб-приложению при этом лишь нужно обновить пакет с описанием интерфейса.

Данный подход предполагает, что API имеет строгую систему: ограничена одним протоколом и имеет четкие ограничения, которые можно описать и реализовать в proxy-объекте. Если никакой системы в проектируемом API нет - данный подход ничем ему уже не поможет.

В нашем проекте используется протокол JSON RPC - протокол предназначенный для общения всех внутренних сервисов компаний. Данный подход наилучшим способом ложится на его имплементацию и позволяет реализовать практически любые вызовы с любыми параметрами.

Заключение

Применение связки прокси-объекта и TypeScript для реализации API слоя позволяет существенно упростить процесс разработки и поддержки приложения, избегая постоянного переписывания или генерации кода слоя API в веб-приложении при изменении API на бэкэнде.

Размер вашего слоя API будет постоянно минимальным вне зависимости используете вы десятки или десятки тысячи методов API в коде (сравните это с постоянно растущим размером текущие реализации API на базе сгенерированного или написанного вручную кода).

Более того - данный код может быть единым для многих веб приложений, Его можно оформить в отдельный пакет и использовать во множестве проектов компании.

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

Проекты, которые использовали кодогенерацию из схем OpenAPI могут для данного подхода генерировать интерфейс API на typescript, благо таких инструментов достаточно.

Перестаньте писать и генерировать и переписывать API слой на каждое изменение сервера API, не тратьте деньги инвесторов и владельцев бизнеса на нагрев планеты - это давно уже не модно !

Демо проект: https://github.com/budarin/proxy-api-demo

ЗЫ: из комментариев стало известно - мое решение не единичное - в промышленной разработке аналогичный подход с Proxy используется в одной из ERP систем и думаю еще не в одной (там ребята уж точно знают толк и цену сэкономленному объему кода, ресурсам и времени), так что если вы не разобрались в сути и/или боитесь выйти из зоны комфорта делая "как все" - эта информация придаст вам немного смелости 😊

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

Смелее! Скоро такой подход будет на подавляющем большинстве проектов - он экономит и деньги и время разработчиков!

Теги:
Хабы:
Всего голосов 29: ↑13 и ↓160
Комментарии134

Публикации

Истории

Работа

Ближайшие события

12 – 13 июля
Геймтон DatsDefense
Онлайн
14 июля
Фестиваль Selectel Day Off
Санкт-ПетербургОнлайн
19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн