DEV Community

Cover image for Как избавиться от постоянной кодогенерации API слоя в веб-приложениях при изменениях на сервере
Вадим Бударин
Вадим Бударин

Posted on

Как избавиться от постоянной кодогенерации API слоя в веб-приложениях при изменениях на сервере

Как избавиться от постоянной кодогенерации API слоя в веб-приложениях при изменениях на сервере

Большинство компаний регулярно сталкиваются с проблемой постоянной модификации слоя 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(methodName);
                    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();
                };
            },
        },
    );
Enter fullscreen mode Exit fullscreen mode

В коде для примера представлена упрощенная реализация логики прокси-объекта 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', ... }))
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

const getAPI = (apiUrl) => new Proxy( ... ) as API;
Enter fullscreen mode Exit fullscreen mode

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

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

api.putLalalalala('lololololo');
Enter fullscreen mode Exit fullscreen mode

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

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

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

Заключение

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

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

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

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

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

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

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

Top comments (0)