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

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay