Вступление

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

Можно утверждать, что данное веб-приложение настолько хорошо, насколько хороши данные, к которым оно может получить доступ и отобразить. Хотя нам повезло жить в мире, полном источников данных, мы в конечном итоге используем только те источники данных, к которым у нас есть доступ (так что математически, вероятно, работает очень небольшой процент мировых данных ). Обычно каждый источник данных имеет свои собственные уникальные требования к API, и это делает его полным перетаскиванием всякий раз, когда должен использоваться новый источник данных. Обычно требуется достаточно времени для чтения длинных документов API, перебора кода, который является столь же надежным, как API, и уводит разработчика от других задач в очереди. Это время и затраты на разработку могут возникать при каждом новом подключении поставщика данных.

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

Давайте проанализируем, что я считаю наиболее часто используемой моделью API: (значительно упрощено)

В этой модели я это вижу так:

  • сервер владеет API, разработчик на стороне клиента должен постоянно обновлять обширную документацию по API.
  • Клиент делает запросы, сервер отвечает
  • Клиент ожидает одного ответа, поэтому, если что-то происходит в то время, когда сервер выполняет запрошенную услугу, он не будет возвращен клиенту. Уведомлений в этой модели нет, только ответ.
  • Общение однонаправленное; запросы идут в одну сторону, ответы - в другую.
  • Когда API сервера изменяется, все клиенты блокируются от связи с сервером до тех пор, пока они не обновят свои методы запроса , если сервер не предоставляет доступ к предыдущим версиям. . Это ужасная модель, потому что она ненадежна или, если она надежна, дорогостоящая, потому что сервер должен поддерживать все версии кода только для того, чтобы старые клиенты могли ее использовать. Новые версии кода включают исправления ошибок и другие улучшения, поэтому для клиента может быть контрпродуктивным настаивать на использовании старого ошибочного кода в любом случае.

Возможно, будет гораздо полезнее сделать шаг назад и по-настоящему подумать о том, как выглядят наши точки общения в Интернете. Это показано на следующей диаграмме. На диаграмме я все еще использую термины «сервер» и «клиент», потому что это то, с чем все еще знакомы, но я бы предпочел термины «узел ввода-вывода» для каждой точки.

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

  • Каждая линия представляет двунаправленный ввод-вывод.
  • Каждый клиент и сервер можно рассматривать как узлы ввода-вывода
  • Каждый узел ввода-вывода node может генерировать или прослушивать события в любой момент времени. Следовательно, каждый узел может иметь свой собственный API, который он желает предоставить в любой момент времени. Да, у клиента может быть API.
  • Поскольку эти события известны во время выполнения, каждая сторона может сообщать события, которые она может генерировать и прослушивать; т.е. каждый узел может передавать свой API. Это означает, что если появляется внешний узел ввода-вывода, обозначенный «сервером 3», он может передавать свой API любому или всем узлам, и эти узлы будут знать, как взаимодействовать с этим новым узлом, все без предварительного знания его API.
  • Что еще более важно, каждый узел может сообщать свой тип узла, так что, если два узла идентичны, их можно считать одноранговыми, и можно сделать вывод, что одноранговые узлы должны уже знают API друг друга.
  • Эта модель только столь же надежна, как и формат API, с которым должны согласиться все стороны, но если формат простой, он может работать. !

Небольшое отступление

Мне нравится думать о клиенте и сервере как о большом физическом расстоянии. Действительно, это уже верно, поскольку связь должна проходить по длинным кабелям, спутникам приходится сталкиваться и т. Д. Ожидается, что ответ, который клиент может получить от сервера, займет некоторое время. Однако я предпочитаю придерживаться более радикального взгляда. Мне нравится думать о клиенте как о ком-то, кто путешествует на совершенно другую планету, например, на Марс или Плутон. Этот клиент будет еще дальше, и чтобы она выжила, она должна постоянно общаться с серверами ввода-вывода на Земле.

За годы ее астрономических путешествий более чем вероятно, что обе стороны этого общения каким-то образом трансформируются, и обеим сторонам придется приспосабливаться к общению друг с другом. У нашего любимого космонавта не будет роскоши ознакомиться с последними документами API, ей просто придется довольствоваться тем, что ей посылает сервер. То, что она наблюдает как «последний API», с точки зрения Земли будет уже на несколько старых версий (физика), поэтому, возможно, если сервер сможет поддерживать только несколько предыдущих версий, у нее будет шанс выжить. .

Это может быть экстремальная модель, но ее можно применить к постоянно меняющимся потребностям и API-интерфейсам нашего Интернета. А когда придет время путешествовать на далекие планеты, мы будем готовы.

Формат динамического API KISS

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

Это цель разработки того, что я разработал как «Формат динамического API KISS». Если высокоуровневое описание формата не может поместиться на заметке Post-it®, это не соответствует принципу KISS. На высоком уровне формат KISS выглядит так:

На самом высоком уровне формат прост: каждый узел ввода-вывода указывает свою метку и версию. Если данный узел, обменивающийся данными, представляет ту же метку и версию, что и другой узел, он может считаться одноранговым, после чего этому узлу не потребуется дополнительная информация. Сверстники уже знают способности друг друга. Однако для узлов, которые не являются одноранговыми узлами, потребуется дополнительная информация: поддерживаемые события и методы. (ПРИМЕЧАНИЕ: в центре внимания этого обсуждения находится модель ввода-вывода. Возможно, может быть реализована отдельная модель безопасности, чтобы помочь проверить, что узлы ввода-вывода являются теми, кем они себя называют)

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

Если метка не указана, клиенту просто нужно будет полагаться на собственный псевдоним для использования для этого API. Поскольку клиент уже знает домен, порт и пространство имен, с которыми он взаимодействует, для него может быть простой способ создать любые псевдонимы, которые он хочет (например, apis['localhost:8080/chatRoom']). Если версия не указана, клиент всегда должен будет предполагать несоответствие версии и запрашивать полную полезную нагрузку API в начале каждого нового соединения; то есть клиент не сможет полагаться на кеш API или использовать его преимущества. Поэтому, хотя управление версиями не является обязательным, настоятельно рекомендуется.

У каждого узла может быть свой набор событий и методов. «Evts» означает, что узел будет генерировать эти события, а «методы» означает, что узел будет прослушивать эти события (и запускать свои собственные методы с такими же именами, соответственно) .

KISS: формат «евтс»

Давайте перейдем к формату «evts», чтобы увидеть, как он может выглядеть: (опять же, он должен уместиться на Post-it®)

Здесь «evts» будут иметь следующую форму: объект JSON, где свойства объекта - это имена событий, соответствующие значения которых также являются необязательными объектами JSON, но настоятельно рекомендуется. Это позволяет легко записывать несколько событий и упорядочивать их по событиям.

Каждое имя события указывает на объект JSON, содержащий следующие необязательные, но настоятельно рекомендуемые свойства:

  • методы: массив строк, каждая строка представляет имя метода, генерирующего это событие. Это упрощает для получателя организацию данных о событиях по имени метода в случае, если разные методы генерируют одно и то же событие. Если он не указан, получатель должен будет кэшировать переданные данные более общим, менее организованным способом.
  • данные: схема, которую клиент может ожидать получить и использовать для проверки входящих данных. Рекомендуется использовать в схеме значения по умолчанию, поскольку эти значения также указывают на тип данных (в Javascript typeof (variable) сообщает нам тип примитивов). На мой взгляд, это делает код более простым и читаемым.
  • ack: логическое значение, указывающее, ожидает ли отправленное событие подтверждения. (Это может потребоваться, а может и не потребоваться, что будет объяснено в следующей статье. Однако может быть полезно знать, блокируется ли код во время ожидания подтверждения, когда подтверждение никогда не будет отправлено).

KISS: пример использования формата «evts»

В этом примере этот API имеет метку mainServer и имеет версию 1.02. Он выдаст события «itemRxd» и «msgRxd». Клиент может ожидать, что методы, генерирующие «itemRxd», будут либо «getItems», «toBeAdded», либо ни одним из них. Сервер по-прежнему должен указать метод, который вызвал это событие, чтобы клиент мог правильно организовать свои данные. Когда сервер выдает «itemRxd», клиент может ожидать, что данные JSON будут содержать «прогресс», который указан как тип Number (по умолчанию 0), и «элемент», который указан как тип Any (и по умолчанию пустой объект). Таким образом, и тип, и значение по умолчанию представлены просто и компактно. Со временем сервер может пожелать создать «элемент» типа «Item» вместо «Any», чтобы помочь клиенту проверить каждый элемент (например: Item = {name: '', description: '', unitCost: ''}).

Вот пример:

Другое событие - «msgRxd». В этой записи не указывается какой-либо метод, только схема данных. Клиент может ожидать получения «даты» и «сообщения». Поскольку методы не указаны, клиент может ожидать, что событие будет исходить от любого или всех методов на сервере.

KISS: формат «методы»

В то время как контейнер «evts» описывает вывод данного узла, «методы * описывают ввод для этого узла и то, каким может быть соответствующий ответ. Вот как может выглядеть формат:

Формат представляет собой объект JSON, где свойства представляют имена поддерживаемых методов. Каждое имя метода указывает на соответствующий объект JSON, который описывает:

  • msg: схема сообщения, которую ожидает принимающий узел (объект JSON «msg»).
  • соответственно: схема ответа, которой узел ожидает ответить, если таковая имеется. Если в ответе указана схема, заключенная в квадратные скобки, это указывает на массив этой схемы.

Одним из потенциальных преимуществ предоставления этих схем в реальном времени может быть автоматическое создание пользовательского интерфейса; то есть определенные типы могут помочь определить, какие элементы пользовательского интерфейса лучше всего подходят для этих типов, особенно если типы являются примитивами. Например, если данная схема msg определяет типы String и Number, типы String могут преобразовываться в <input type="text" />, а типы Number могут преобразовываться в <input type="number" />. Таким образом, вероятно, можно будет создавать элементы управления всей формы «на лету». Точно так же текстовые ответы, вероятно, могут быть прикреплены к <div class="resp"></div> элементам. Стилизацией по-прежнему можно было управлять с помощью CSS.

KISS: пример использования формата «методы»

В этом примере API определяет два метода: «getItems» и «getItem». «GetItems» не определяет схему «msg», поэтому «msg» может быть чем угодно (или ничего), потому что оно будет проигнорировано. Метод вернет только массив типа «Item». Схема Item определяется как объект JSON, состоящий из «id», «name» и «desc», всех пустых строк (тип String). Однако метод «getItem» определяет схему «msg», объект JSON со свойством «id» и форматом String (по умолчанию используется пустая строка). Когда клиент вызывает этот метод, сервер ожидает, что клиент предоставит идентификатор правильного типа (String). Он ответит типом Item.

Заключение

Здесь было представлено длинное, но, надеюсь, не слишком запутанное обсуждение того, как API-интерфейсы можно сделать динамическими, чтобы они могли адаптироваться к изменениям, вносимым обеими сторонами канала связи. Скорее всего, это будет совершенно новая концепция для многих, поэтому в моей следующей статье будет описана ее точная реализация, которая будет выпущена с nuxt-socket-io v1.0.22. В этой статье мы попытаемся подробно осветить преимущества на конкретных примерах. Сначала ожидайте болевых точек, потому что это кривая обучения, но я надеюсь, что мы оба будем рады после того, как поднимемся по кривой (да, мы поднимаемся по кривой вместе).