Универсальность данных: API геттер и сеттер

Источник: «Data portability: API getter and setter methods»
Когда я консультирую клиентов по работе с API, то рекомендую никогда не обращаться к API напрямую.

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

Пример

Представим, что есть приложение todo.

Пользователи могут просматривать список дел, добавлять новые и отмечать их как завершённые. Каждое из перечисленных действий вызывает API, который получает, устанавливает или изменяет данные.

Допустим, конечная точка API — /todos.

Запрос GET возвращает все данные, запрос POST создаёт новый элемент todo, а запрос PUT обновляет существующий элемент todo.

// Данные полученные из GET
let data = [{
id: 1234
todo: 'Play D&D',
completed: false
},
{
id: 5678
todo: 'Buy milk',
complete: true
}];

// Формат данных для методов POST и PUT
let update = {
id: 1234
todo: 'Play D&D',
completed: true
};

Прямое использование конечных точек

Иногда я вижу, что конечные точки API вызываются непосредственно в коде, использующем их данные.

async function renderTodos () {

// Получение данных API
let request = await fetch('/todos');
let todos = await request.json();

// Рендер данных
let app = document.querySelector('#app');
app.innerHTML =
`<ul>
${todos.map((todo) => {
return `<li>${todo}</li>`;
}).join('')}

</ul>
`
;

}

Всё работает отлично… пока не перестаёт.

Изменения в API

Вполне вероятно, что приложение обращается к API в разных местах для решения различных задач. Когда API изменяется, это означает переписывание большого количества кода.

Возможно, меняется конечная точка с /todos на /api/v2/todos. Достаточно простое изменение, но потенциально разбросанное по разным файлам.

Возможно, изменился способ обработки аутентификации. Теперь необходимо обновить запрос fetch() везде, где он используется.

Иногда изменяются возвращаемые данные.

// Исходный GET ответ
let data = [{
id: 1234
todo: 'Play D&D',
completed: false
},
{
id: 5678
todo: 'Buy milk',
complete: true
}];

// Новый GET ответ
let newData = {
1234: {
todo: 'Play D&D',
completed: false
},
5678: {
todo: 'Buy milk',
complete: true
}
};

Теперь, везде, где вызывается API, нужно не просто обновить вызов API, но и рефакторить то, как вы с ним работаете.

Но есть и более простой способ.

Методы геттер и сеттер

Я рекомендую клиентам использовать специальные методы для вызова API и обработки полученных данных.

// Получение данных API
async function getTodos () {
let request = await fetch('/todos');
let todos = await request.json();
return todos;
}

// Рендер данных todo
async function renderTodos () {
let todos = await getTodos();
let app = document.querySelector('#app');
app.innerHTML =
`<ul>
${todos.map((todo) => {
return `<li>${todo}</li>`;
}).join('')}

</ul>
`
;
}

Если API изменится, у вас будет один файл, в который нужно внести изменения.

А при более серьёзных изменениях, например, при значительном изменении способа возврата данных, можно преобразовать их перед возвратом и вообще не обновлять использующие их функции.

// Получение данных API
async function getTodos () {

// Получение данных API
let request = await fetch('/todos');
let todos = await request.json();

// Преобразование данных в старый формат
let transformed = Object.entries(todos).map(([id, item]) => {
let {todo, completed} = item;
return {id, todo, completed};
});

// Возвращение преобразованных данных
return transformed;

}

При таком подходе renderTodos() и любые другие функции, использующие вывод getTodos(), не нуждаются в изменениях.

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

Дополнительные материалы

Предыдущая Статья

Парсинг декларативного shadow DOM

Следующая Статья

Якорные ссылки и как их сделать потрясающими