- Все статьи цикла:
- Дизайн данных (часть 1). Что и зачем?
- Дизайн данных (часть 2). Как?
- Дизайн данных (часть 3). Меняемся?
- Дизайн данных (часть 4). Матчасть: основы.
- Дизайн данных (часть 5). Матчасть: базы данных.
- Дизайн данных (часть 6). Матчасть: API. (вы здесь)
- Дизайн данных (часть 7). Прототипирование.
Это шестой, предпоследний, пост цикла. И он же третий про матчасть дизайна данных.
В этой статье (как и в третьей, «Меняемся?») речь снова пойдёт об обмене информацией между компонентами системы. Однако на этот раз мы коснёмся более глубоких тем: рассмотрим, как создаётся философия API и даже пощупаем реальные примеры запросов.
Предупреждаю, дальше вас ожидает полный хардкор — просьба убрать от мониторов несовершеннолетних детей, иллюстраторов и прочих убеждённых юайщиков.
Погнали.
Виды API
Как я уже упоминал, видов API огромное множество, ведь API — это лишь общее название всех программных интерфейсов. Например, есть API, позволяющий умному холодильнику коннектиться к Амазону и заказывать продукты, если они вдруг у вас кончились. При том у этого же холодильника есть ПО, которое с помощью уже другого API получает данные с внутренних сканеров (чтобы понять, каких продуктов не хватает). А также еще один API для доступа к микрофону, чтобы вы могли велеть своему холодильнику купить молока (и там же рядом API, передающий ваши слова на сервер для обработки). И еще один, чтобы синхронизироваться с вашими умными весами, и на основании их данных составлять примерное меню. И так до бесконечности, насколько хватило фантазии у дизайнеров упомянутого холодильника.
Суть в том, что в примере выше есть «физические» API, которые дают возможность взаимодействовать с различным «железом» (типа сканеров или микрофона), а есть другие, которые позволяют обмениваться данными с различными внешними источниками (вроде серверов Амазона). Мы будем рассматривать исключительно вторые, так как именно с ними чаще всего придётся работать продуктовому дизайнеру.
Немножко лирики
Догадливый читатель уже смекнул, что без понимания того, как эти API работают (и есть ли они вообще), невозможно спроектировать полноценную функциональность нашего холодильника (это я уже молчу о пользовательском опыте).
Ну и разумеется, сей тезис касается не только физических предметов: большинству дизайнеров даже не придёт в голову изучить вопрос внешних интеграций перед тем, как приступить к созданию нового цифрового продукта. Редкий проектировщик гуглит сервисы пуш-уведомлений (например), чтобы понять, какие у них есть ограничения.
Подавляющее большинство создаваемых нами сайтов и приложений обменивается информацией с серверами. Сейчас это просто данность, и дизайнер не имеет права игнорировать этот аспект своей работы. Хотя бы просто потому, что именно он (а не программный архитектор или, тем более, рядовой программист) знает создаваемый продукт лучше всех. Дизайнер может не разбираться в деталях, постоянно обращаться за помощью к технарям — но он должен понимать, как данные будут жить внутри его приложения, как они будут обмениваться с сервером и какие потенциальные риски могут при этом образоваться.
В одной статье я не научу вас создавать пуленепробиваемый API — но это и не нужно. Для начала хватит самого базового погружения. А дальше всё сделает опыт.
Философия API
На самом деле, термин «Философия API» на проектном уровне употребляется довольно редко. Однако, по моему субъективному мнению, именно он как нельзя лучше передаёт суть подхода. По факту, упомянутая философия — это набор основополагающих правил, по которым создаётся и развивается конкретный API.
Философия API описывает то, как именно будут обмениваться между собой стороны (например, мобильное приложение и сервер). Это некие верхнеуровневые правила, которые задают тон всему остальному. Без них развивать и поддерживать API довольно сложно: любой программист, добавляя тот или иной метод, руководствуется собственными представлениями о валидации (например). Единая философия позволяет унифицировать запросы/ответы, не ограничивая возможностей разработки.
Чем разжёвывать дальше, лучше приведу фрагмент документации одного из моих последних проектов:
Общие принципы
Описываемый в настоящем документе API является общим для всех сторон, если иное не указано отдельно. Это сделано в целях унификации обмена данными и упрощения развития API впоследствии.
Обмен информацией между Сторонами осуществляется только по защищенному SSL протоколу.
Именование методов/параметров
При обмене данными действует правило именования параметров: имена всех служебных параметров, которые касаются непосредственно сторон (параметры авторизации, названия запросов и т.д.), имеют префикс dt_
, все прочие параметры его не имеют.
Все названия методов и параметров начинаются с маленькой буквы.
В названиях методов, состоящих из нескольких слов, определяющих сущность и действие, в качестве разделителя используется точка (например, user.registration
).
В документации вложенность элементов (свойства сущностей) также разделяется точкой (например, user.id
).
В названиях параметров, состоящих из нескольких слов, используется «CamelCase»: например, secretKey
(исключением являются аббревиатуры, состоящие из заглавных букв — например, SMS). Это не относится только к префиксу dt_
.
Форматы значений
В значениях всех параметров:
Параметр | Тип | Формат | Пример | Примечание |
---|---|---|---|---|
Номер телефона | integer | XXXXXXXXXXX | 79554447788 | 11 цифр международном формате без + |
Дата и время | string | YYYY-MM-DDThh:mm:ss±hh | 2005-08-09T18:31:42-03 | ISO Date |
Булевы значения | string | true | Всегда true|false |
Допустима передача пустых массивов в случае возвращения функцией списка, состоящего из 0 сущностей. Во всех остальных случаях следует возвращать false
.
Не допускаются значения null
, undefined
и подобные.
Формат сообщений
Все запросы и ответы передаются в кодировке UTF-8 методом POST в формате JSON.
Сервер всегда отвечает HTTP-кодом 200, даже в случае попытки неавторизованного доступа (в этом случае сервер возвращает код ошибки из словаря ошибок).
Обязательные параметры
В запросах и ответах есть ряд обязательных параметров, без которых сообщение не обрабатывается.
Запрос
Во всех запросах обязательно должны присутствовать параметры:
Поле | Тип | Пример | Описание |
---|---|---|---|
dt_request | string | user.logout | Параметр, отвечающий за тип запроса. В рамках одной области типов может быть несколько. |
Ответ
Во всех ответах обязательно должны присутствовать параметры:
Поле | Тип | Пример | Описание |
---|---|---|---|
dt_status | string | success | Статус выполнения запроса, допустимые значения success|error. |
dt_errorCode | array | [1800] | Является обязательным только в случае, если status==error. Принимает значения соответствующего кода ошибки из словаря ошибок. |
Обычно я дополняю философию еще несколькими примерами запросов, чтобы архитектор и разработчики точно представляли, о чём речь. И в самом конце статьи я как раз представлю один такой.
Назад к тюленям
Это была теория. Давайте теперь вернёмся в нашему восхитительному приложению, позволяющему отслеживать миграцию тюленей на берегу Охотского моря.
В одной из предыдущих статей мы создали графическую схему запросов нашего выдуманного приложения. Если кто запамятовал, то вот она:
Учитывая, что у нас здесь только схема запросов, а не ответов, последние будем додумывать на ходу. Благо, во втором посте цикла мы создали диаграмму со структурой данных нашего приложения:
Теперь, руководствуясь описанной выше философией, проименуем сущности, их свойства и методы API.
Сущности
Итак, наши сущности, их всего четыре:
- пользователь —
user
; - мобильное приложение —
app
; - точка —
point
; - список точек —
pointList
.
Свойства
Тут посложнее. Будем отталкиваться от сущностей:
Пользователь
У пользователя пять свойств:
- номер телефона —
phone
; - внутренний идентификатор —
id
(не участвует в обмене данными); - глобальный идентификатор —
guid
; - секретный ключ —
secretKey
; - дата и время регистрации —
regTime
.
Мобильное приложение
Здесь всего три свойства:
- версия —
version
; - идентификатор устройства —
deviceId
; - платформа —
platform
.
Точка
У точки, как и у пользователя, пять свойств, но три из них сложные:
- внутренний идентификатор —
id
(не участвует в обмене данными); - дата и время —
dateTime
; - координаты —
coords
, которые состоят из двух параметров:- долгота —
long
; - широта —
lat
;
- долгота —
- миниатюра изображения —
thumb
, состоящая также из двух параметров:- ссылка —
url
; - хэш —
hash
;
- ссылка —
- полноразмерное изображение —
picture
, аналогично предыдущему свойству:- ссылка —
url
; - хэш —
hash
.
- ссылка —
Те, кто помнит, как мы проектировали кэширование, понимает, зачем нужны такие сложности с последними двумя свойствами.
Список точек
У списка всего два свойства, используемых для адаптации под возможности конкретного девайса:
- отступ —
offset
; - область видимости —
scope
.
Методы API
Методов у нас всего три:
- регистрация —
registration
; - подтверждение номера телефона —
confirm
; - получение списка точек —
getPoints
.
Единая таблица свойств
Теперь нам надо определиться с типом данных каждого свойства (я рассказывал про типы в четвёртом посте цикла про дизайн данных). Здесь всё просто: выбираем наиболее подходящий тип и не забываем поглядывать в философию нашего API:
Название параметра | Тип данных | Пример |
---|---|---|
user.phone | integer | 79554447788 |
user.id | integer | 3 |
user.guid | string | c0deaca4-db8a-4178-8863-eaf2494833b9 |
user.secretKey | string | 4af0744c305991c098753117a6a19690 |
user.regTime | string | 2019-06-09T16:28:19-03 |
app.version | string | 1.0.1 |
app.deviceId | string | 2fc4b5912826ad1 |
app.platform | string | iOS |
point.id | integer | 32 |
point.dateTime | string | 2019-09-12T19:05:12-03 |
point.coords.long | integer | 59.5194488 |
point.coords.lat | integer | 150.4879161 |
point.thumb.url | string | /thumbs/uat12ia.jpg |
point.thumb.hash | string | 05991c09f0744a4c38756903117a6a19 |
point.picture.url | string | /picture/hrt6q7.jpg |
point.picture.hash | string | 305991c094af0744c876a1969053117a |
pointList.offset | integer | 20 |
pointList.scope | integer | 60 |
В общем-то, такой таблицы нам достаточно для того, чтобы приступить к описанию самого обмена данными. Однако есть еще пара нюансов, на которые я хотел бы обратить внимание.
Типы указания метода
В философии выше указано, что одним из обязательных параметров запроса является его тип. Так мы однозначно говорим серверу, что нужно сделать с данными, которые он получил (например, если этот параметр — registration
, сервер попробует на основании остальных параметров зарегистрировать пользователя, отправить ему СМС и так далее).
Однако описанное — лишь один из способов указать серверу на конкретный набор действий. В современной разработке чаще всего используется два варианта:
- Указание типа запроса в теле самого запроса (параметром, наш вариант).
- Указание типа запроса в URL, по которому идёт обращение.
Если с первым всё более или менее понятно (в нашем случае параметр dt_request
может равен либо registration
, либо confirm
, либо getPoints
), то во втором варианте мы в запросах не указываем dt_request
вообще, передавая только параметры, нужные серверу для обработки информации. Здесь клиент (мобильное приложение или сайт) будут добавлять к основному URL конкретный адрес, указывающий на тип запроса. Например:
- запросы с регистрацией будут прилетать на sitename.ru/api/v1/registration/;
- подтверждение номера — на sitename.ru/api/v1/confirm/;
- а для получения точек нужно будет постучаться на sitename.ru/api/v1/get-points/.
Вне зависимости от того, какой вы выбрали вариант, я крайне рекомендую именно такой порядок формирования URL вашего API — с указанием версии. Проще будет поддерживать.
Тут, в общем-то, всё. Каких-то особенных преимуществ нет ни у одного из вариантов (хотя кто-то говорит, что второй более наглядный; фиг знает, я люблю первый).
Форматы передачи
Еще одним аспектом является формат передаваемых сообщений (я вкратце об этом писал в третьей части цикла про дизайн данных). Форматов лютая тьма, но самыми популярными на сегодняшний день являются JSON и XML (хотя последний — это целый язык разметки, а не просто формат).
Если упростить, то в данном контексте формат — это то, как именно структурируются данные: как указываются параметры, а как — значения этих параметров.
Вот пример простейшего запроса в XML:
<request>
<dt_request>registration</dt_request>
<phone>79558887722</phone>
</request>
А вот — он же, но в JSON:
{
"dt_request":"registration",
"phone":79558887722
}
Я стараюсь использовать JSON везде, где это не противоречит здравому смыслу — он проще и нагляднее (когда твои запросы не становятся совсем уж монструозными). Однако у XML есть некоторые преимущества: например, его проще валидировать в автоматическом режиме.
HTTP-методы
Ну и напоследок я расскажу о различиях между POST и GET-запросами. Вообще, с терминологией в IT так себе. Есть методы у HTTP-запросов (как раз эти вот POST и GET), а есть методы у конкретного API (в нашем случае, например, registration
, confirm
и getPoints
). Тут главное не перепутать.
Если совсем упростить, то POST содержит параметры в теле запроса (вон примеры чуть выше), тогда как GET — в URL (с помощью знака вопроса и амперсандов).
Вот пример GET-запроса:
sitename.ru/api/v1/?dt_request=registration&phone=79558887722
Параметры в GET начинаются после знака вопроса (?) и разделяются амперсандами (&): dt_request
и phone
. Хороший и наглядный пример реализации API через GET можно глянуть на моём сайте рыбатекста.
Просто, да? Нет. Потому что помимо GET и POST есть еще PUT, DELETE, CONNECT и ещё некоторое количество методов. Но истинное их предназначение ныне используется довольно редко. Указанных POST и GET чаще всего хватает за глаза.
Итого
Если кто-то ожидал, что в этой статье я представлю полное описание API нашего мегакрутого приложения про тюленей, то — сорри — нет. Это мы с вами сделаем в следующей, последней статье цикла. Не переключайтесь:
- Все статьи цикла:
- Дизайн данных (часть 1). Что и зачем?
- Дизайн данных (часть 2). Как?
- Дизайн данных (часть 3). Меняемся?
- Дизайн данных (часть 4). Матчасть: основы.
- Дизайн данных (часть 5). Матчасть: базы данных.
- Дизайн данных (часть 6). Матчасть: API. (вы здесь)
- Дизайн данных (часть 7). Прототипирование.