Каждый — дизайнер API: мои мысли о RESTful API, GraphQL, RPC API

Java API
Каждый — дизайнер API: мои мысли о RESTful API, GraphQL, RPC API

Оригинальный адрес:Блог Лян Гуйчжао

адрес блога:blog.720ui.com

Добро пожаловать на официальный аккаунт: «Серверное мышление». Группа людей с одинаковой частотой растет вместе, вместе совершенствуется и ломает ограничения познания.

Я давно не писал статей, поэтому сегодня я пишу о своих мыслях о дизайне API. Во-первых, зачем писать на эту тему? Во-первых, я получил много пользы от прочтения статьи "Али исследователь Гу Пу: Мысли о лучших практиках в API-дизайне". Статья, перепечатанная два дня назад, также вызвала интерес читателей. Думаю, стоит изложить свою Мысли собраны в документ, чтобы поделиться и столкнуться со всеми. Во-вторых, я думаю, что смогу решить эту тему в течение получаса, и попытаться выключить свет и лечь спать до часа, ха-ха.

Теперь давайте поговорим о API Design. Я выброшу несколько очков, добро пожаловать, чтобы исследовать.

Во-первых, определение хорошего стандарта имело большой успех.

Обычно нормой является стандарт, согласованный всеми.Если все будут соблюдать этот стандарт, естественные затраты на общение будут значительно снижены. Например, каждый надеется извлечь уроки из спецификаций Али и определить несколько доменных моделей в своем бизнесе: VO, BO, DO и DTO. Среди них DO (объект данных) соответствует структуре таблицы базы данных один за другим, а объект источника данных передается вверх через уровень DAO. DTO (объект передачи данных) — это объект удаленного вызова, представляющий собой модель предметной области, предоставляемую службой RPC. Для BO (бизнес-объект) это объект, который инкапсулирует бизнес-логику на уровне бизнес-логики.В общем, это составной объект, объединяющий несколько источников данных. Затем VO (View Object) обычно является объектом, передаваемым уровнем обработки запросов.После того, как он преобразуется платформой Spring, он часто является объектом JSON.

image.png

На самом деле, если доменные модели DO, BO, DTO и VO четко не определены в таком сложном бизнесе, как Alibaba, его внутренний код легко запутается.Внутренний RPC добавляет уровень менеджера поверх уровня обслуживания. Добиться внутренней унификации норм. Однако, если это просто отдельный домен и не имеет большого количества внешних зависимостей, то вообще не проектируйте его таким сложным, если только не ожидается, что он может стать большим и сложным. В связи с этим особенно важно адаптироваться к местным условиям в процессе проектирования.

Другой канонический пример — RESTful API. В архитектурном стиле REST каждый URI представляет ресурс. Следовательно, URI — это уникальный локатор ресурса для адреса каждого ресурса. Так называемый ресурс — это фактически информационная сущность, которая может быть фрагментом текста, файлом, картинкой, песней или сервисом на сервере. RESTful API определяет операции с ресурсами на стороне сервера через GET, POST, PUT, PATCH, DELETE и т. д.

【GET】          /users                 # 查询用户信息列表
【GET】          /users/1001            # 查看某个用户信息
【POST】         /users                 # 新建用户信息
【PUT】          /users/1001            # 更新用户信息(全部字段)
【PATCH】        /users/1001            # 更新用户信息(部分字段)
【DELETE】       /users/1001            # 删除用户信息

На самом деле существует четыре уровня реализации RESTful API. Служба веб-API первого уровня (уровень 0) просто использует HTTP в качестве транспорта. Второй уровень (уровень 1) служб веб-API знакомит с концепцией ресурсов. Каждый ресурс имеет соответствующий идентификатор и выражение. Службы веб-API уровня 2 используют разные методы HTTP для выполнения разных операций и используют коды состояния HTTP для указания разных результатов. Служба веб-API четвертого уровня (уровень 3) использует HATEOAS. Информация о ссылке включена в представление ресурса. Клиенты могут обнаруживать действия, которые могут быть выполнены на основе ссылки. Обычно псевдо-RESTful API разрабатываются на основе первого и второго уровней. Например, мы используем различные глаголы в нашем веб-API, такие какget_menu иsave_menu, а настоящий RESTful API должен соответствовать третьему уровню и выше. Если мы будем следовать этому набору спецификаций, мы, скорее всего, создадим простой для понимания API.

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

Подводя итог, цель четко определенной спецификации — снизить стоимость обучения и сделать API как можно более простым для понимания. Конечно, есть и другие способы разработки простого для понимания API.Например, имя определяемого нами API легко понять, а реализация API является максимально общей.

2. Изучите совместимость интерфейсов API

Интерфейсы API постоянно развиваются. Поэтому нам нужно в какой-то степени адаптироваться к изменениям. В RESTful API интерфейс API должен быть максимально совместим с предыдущей версией. Однако в реальном сценарии развития бизнеса с непрерывным повторением бизнес-требований существующий интерфейс API не может поддерживать адаптацию старой версии.В настоящее время, если интерфейс API сервера принудительно обновляется, старая функция клиент выйдет из строя. На самом деле, веб-часть развертывается на сервере, поэтому ее можно легко обновить, чтобы адаптировать к новому интерфейсу API серверной стороны.Однако другие клиенты, такие как сторона Android, сторона IOS, сторона ПК и т. д., работают на стороне компьютер пользователя. Поэтому текущему продукту трудно адаптироваться к API-интерфейсу нового сервера, что приводит к функциональному сбою. В этом случае пользователь должен обновить продукт до последней версии, чтобы использовать его в обычном режиме. Чтобы устранить эту несовместимость версий, практический подход к разработке RESTful API заключается в использовании номеров версий. Как правило, мы сохраним номер версии в URL-адресе и будем совместимы с несколькими версиями одновременно.

【GET】  /v1/users/{user_id}  // 版本 v1 的查询用户列表的 API 接口
【GET】  /v2/users/{user_id}  // 版本 v2 的查询用户列表的 API 接口

Теперь, не меняя интерфейс API для запроса списка пользователей версии v1, мы можем добавить интерфейс API для запроса списка пользователей версии v2 для удовлетворения новых потребностей бизнеса.В это время новая функция продукта клиента будет запрашивать новые Адрес интерфейса API сервера. Хотя сервер будет совместим с несколькими версиями одновременно, поддержка слишком большого количества версий одновременно является большой нагрузкой для сервера, поскольку серверу необходимо поддерживать несколько наборов кодов. В этом случае обычной практикой является не поддерживать все совместимые версии, а поддерживать только несколько последних совместимых версий, например, поддерживать три последние совместимые версии. Через некоторое время, когда подавляющее большинство пользователей переходит на более новую версию, старые версии интерфейса API некоторых серверов, которые используются реже, отбрасываются, и пользователи, использующие очень старые версии продукта, вынуждены Обновить. Обратите внимание, что «не изменять интерфейс API для запроса списка пользователей в версии v1» в основном означает, что он кажется неизменным для вызывающей стороны клиента. На самом деле, если бизнес слишком сильно меняется, разработчику сервера необходимо использовать режим адаптера для старой версии интерфейса API, чтобы адаптировать запрос к новому интерфейсу API.

Интересно, что GraphQL предлагает другую идею. Чтобы решить проблему интерфейса API Service API и совокупность нескольких бесплатных запросов HTTP в один запрос, GraphQL предлагает выставить только один интерфейс API сервиса, а несколько запросов могут выполняться в одном запросе. GraphQL Определяет интерфейс API, который мы можем позвонить более гибко на переднем конце. Например, мы можем выбрать и загружать поля, которые необходимо отображать в соответствии с различными предприятиями. Следовательно, полное количество полей, предоставляемых сервером, может быть получено передней частью по запросу. GraphQL может добавлять новые функции, добавив новые типы и новые поля на основе этих типов, не вызывая проблемы совместимости.

image.png

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

Для канонического случая мы можем посмотреть на k8s и github, где k8s использует RESTful API, а часть github использует GraphQL.

3. Предоставьте четкую ментальную модель

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

image.png

4. Защищайте реализацию бизнеса абстрактно

Я думаю, что хороший интерфейс API абстрактен, поэтому он должен максимально защищать бизнес-реализацию. Итак, вопрос в том, как мы понимаем абстракцию? В связи с этим можно подумать о дизайне java.sql.Driver. Здесь java.sql.Driver — канонический интерфейс, а com.mysql.jdbc.Driver — канонический интерфейс. Это интерфейс реализации mysql-connector-java-xxx.jar для этой спецификации. Тогда стоимость перехода на Oracle будет очень низкой.

В общем, мы будем предоставлять услуги из API. Здесь логика интерфейса API предоставляет фиксированную услугу, другими словами, она универсальна. Однако, когда мы сталкиваемся с похожей бизнес-логикой, ядро ​​у ядра одинаковое, а реализация деталей немного отличается, то что нам делать? Много раз мы будем предоставлять несколько интерфейсов API для разных бизнес-частей. На самом деле мы можем добиться большей элегантности с помощью точки расширения SPI. Что такое СПИ? Английское полное название SPI — Server Provider Interface, интерфейс поставщика услуг.Это механизм динамического обнаружения, который можно динамически обнаруживать в процессе выполнения класса реализации точки расширения. Следовательно, конкретный метод реализации SPI динамически загружается и вызывается при вызове API.

На данный момент вы подумали о шаблоне метода шаблона? Основная идея шаблона метода шаблона состоит в том, чтобы определить скелет и передать реализацию, Другими словами, он откладывает конкретную реализацию некоторых шагов на подклассы, определяя структуру процесса. На самом деле, в процессе посадки микросервисов такое мышление также дает нам очень хорошую теоретическую базу.

image.png

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

SPI扩展案例-未发货仅退款.png

Кроме того, мы часто используем паттерн «фабричный метод + стратегия», чтобы скрыть внешнюю сложность. Например, если мы предоставляем API-интерфейс getTask(int operation), мы можем создавать экземпляры с помощью фабричных методов и определять различные реализации с помощью методов стратегии.

@Component
public class TaskManager {

    private static final Logger logger = LoggerFactory.getLogger(TaskManager.class);
    
    private static TaskManager instance;

    public MapInteger, ITask> taskMap = new HashMap<Integer, ITask>();

    public static TaskManager getInstance() {
        return instance;
    }

    public ITask getTask(int operation) {
        return taskMap.get(operation);
    }

    /**
     * 初始化处理过程
     */
    @PostConstruct
    private void init() {
        logger.info("init task manager");
        instance = new TaskManager();
        // 单聊消息任务
        instance.taskMap.put(EventEnum.CHAT_REQ.getValue(), new ChatTask());
        // 群聊消息任务
        instance.taskMap.put(EventEnum.GROUP_CHAT_REQ.getValue(), new GroupChatTask());
        // 心跳任务
        instance.taskMap.put(EventEnum.HEART_BEAT_REQ.getValue(), new HeatBeatTask());
        
    }
}

Еще одна конструкция для защиты внутренней сложности — внешний интерфейс, который инкапсулирует и интегрирует интерфейсы нескольких служб и предоставляет клиентам простой интерфейс вызова. Преимущество такого дизайна в том, что клиенту больше не нужно знать интерфейс такого количества сервисов, а нужно только вызывать этот фасадный интерфейс. Однако недостатки тоже очевидны, то есть повышена бизнес-сложность сервера, невысокая производительность интерфейса, невысокая возможность повторного использования. Поэтому, в соответствии с местными условиями, максимально обеспечивайте единую ответственность и выполняйте сборку в стиле «Лего» на стороне клиента. Если есть SEO-оптимизированные продукты, они должны быть включены поисковыми системами, такими как Baidu.При отображении первого экрана HTML может быть сгенерирован посредством рендеринга на стороне сервера, чтобы позволить поисковым системам включить его.Вызвать RESTful API на стороне сервера. интерфейс для рендеринга страницы.

Кроме того, с ростом популярности микросервисов у нас появляется все больше и больше сервисов, и многие более мелкие сервисы имеют больше межсервисных вызовов. Поэтому архитектура микросервисов делает эту проблему более распространенной. Чтобы решить эту проблему, мы можем рассмотреть возможность введения «службы агрегации», которая представляет собой службу композиции, объединяющую данные из нескольких микрослужб. Преимущество этой схемы заключается в том, что некоторая информация интегрируется через «службу агрегации», а затем возвращается вызывающей стороне. Обратите внимание, что «служба агрегации» также может иметь собственный кэш и базу данных. На самом деле идея сервисов агрегации есть везде, например, в бессерверных архитектурах. На практике мы можем использовать AWS Lambda в качестве вычислительного движка для бессерверной службы, а AWS Lambda — это вычислительная служба «функция как услуга» (Function-as-a-Service, FaaS), которую мы напрямую пишем и запускаем в облаке. , функция. Затем эта функция может собрать существующие возможности для агрегирования услуг.

image.png

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

Пять, рассмотрим производительность позади

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

ResultVoid> agree(Long taskId, Long caseId, Configger configger);

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

Кроме того, хотя GraphQL решает проблему агрегирования нескольких HTTP-запросов в один запрос, схема будет рекурсивно получать все данные путем послойного анализа. Например, общее количество запросов на подкачку, исходный запрос, который можно выполнить один раз, превратилось в N + 1 запросов к базе данных. Кроме того, необоснованное написание может привести к проблемам с низкой производительностью, поэтому нам нужно уделять особое внимание процессу проектирования.

6. Реакция на исключение и механизм ошибки

В отрасли было много споров о том, выдают ли RPC API исключения или коды ошибок. «Руководство по разработке Java для Alibaba» рекомендует предпочтительно использовать метод isSuccess(), «код ошибки» и «краткое сообщение об ошибке» для вызовов RPC между приложениями. Причины использования метода Result для метода возврата метода RPC: 1) При использовании метода возврата генерирующего исключения, если вызывающая сторона не перехватывает его, возникает ошибка времени выполнения. 2) Если вы не добавляете информацию о стеке, просто создаете новое пользовательское исключение и добавляете сообщение об ошибке, которое вы понимаете, это не слишком поможет вызывающему абоненту решить проблему. Если добавляется информация о стеке, в случае частых ошибок вызовов потеря производительности сериализации и передачи данных также является проблемой. Конечно, я также поддерживаю практических сторонников этого аргумента.

public ResultXxxDTO> getXxx(String param) {
    try {
        // ...
        return Result.create(xxxDTO);
    } catch (BizException e) {
        log.error("...", e);
        return Result.createErrorResult(e.getErrorCode(), e.getErrorInfo(), true);
    }
}

В процессе разработки веб-API мы используем ControllerAdvice для единообразной упаковки сообщений об ошибках. В сложных цепочках вызовов микросервисов нам сложнее отследить и локализовать проблему, чем в монолитной архитектуре. Поэтому при проектировании требуется особое внимание. Лучшим решением является использование информации об ответе глобальной структуры исключения, когда в интерфейсе RESTful API возникает ответ с кодом ошибки HTTP, отличным от 2xx. Среди них поле кода используется для указания кода ошибки определенного типа, а префикс «{biz_name}/» должен быть добавлен к микросервису, чтобы облегчить определение того, в какой бизнес-системе произошла ошибка. Рассмотрим кейс. Предположим, что у интерфейса в "Центре пользователей" нет разрешения на получение ресурсов и возникает ошибка. Наша бизнес-система может ответить на "UC/AUTH_DENIED" и получить его в лог-системе через поле request_id автоматически сгенерированного значения UUID Подробности об ошибке.

HTTP/1.1 400 Bad Request
Content-Type: application/json
{
   "code": "INVALID_ARGUMENT",
   "message": "{error message}",
   "cause": "{cause message}",
   "request_id": "01234567-89ab-cdef-0123-456789abcdef",
   "host_id": "{server identity}",
   "server_time": "2014-01-01T12:00:00Z"
}

7. Думая о идемпотентности API

Мощность ядра и другие механизмы гарантированно будут уникальными ресурсами, например, отправка дубликатов или тестовых весов нескольких клиент-серверов даст результат. Сценарии оплаты, сцена возврата, с участием денежных сумм не может появиться несколько сборов и других вопросов. На самом деле, интерфейс запроса на доступ к ресурсам, потому что это просто запрос данных, не влияющий на изменения ресурсов, поэтому сколько бы раз интерфейс передачи ресурсов не менялся, так что он идемпотентный. Новые интерфейсы не являются идемпотентными, поскольку при многократном вызове интерфейса он будет производить изменения ресурсов. Таким образом, мы должны иметь дело с в случае возникновения такой власти, как дублирование представления. Итак, как обеспечить питание и другой механизм? На самом деле у нас много реализаций. Среди них распространенная схема создания уникального индекса. Создайте уникальный индекс для полей, которые нам нужны, ограничения ресурсов в базе данных, это позволяет предотвратить вставку повторяющихся данных. Тем не менее, ситуация, возникающая в подтаблице подбиблиотеки, представляет собой уникальный индекс, не так Haoshi, на этот раз мы можем сначала запросить базу данных, а затем определить, есть ли повторение поля ограничения ресурсов, повторить снова, когда операция вставки завершена. не . Обратите внимание, что во избежание сценариев параллелизма мы можем блокировать такие механизмы, как пессимистическая блокировка и оптимистичная блокировка, чтобы обеспечить уникальность данных. Здесь часто используется схема распределенной блокировки, обычно для достижения пессимистической блокировки. Тем не менее, многие люди часто пессимистичный механизм блокировки, оптимистичная блокировка, решения с распределенным питанием, такие как замки, это не так. Кроме того, мы также можем ввести конечный автомат, состояние ограничения и состояние с помощью скачков конечного автомата, чтобы гарантировать, что процесс выполнения одного и того же дела для достижения мощности и других данных. На самом деле не все интерфейсы должны обеспечивать питание и так далее, иными словами, потребность в питании и других механизмах можно считать необходимыми для обеспечения уникальности ресурсов, такое поведение не может учитывать идемпотентность лога. Конечно, существует дизайн, не учитывающий такие механизмы интерфейса, как питание, но когда реализация сервиса обеспечивается на операционном уровне, например, допуская наличие нескольких копий данных, но для самой последней версии на момент обработка бизнес-процессов.

(Конец, перепечатка с указанием автора и источника.)

напиши в конце

[Серверное мышление]: Давайте поговорим об основных технологиях сервера, обсудим структуру проекта и практический опыт первой линии Интернета. В то же время большая семья «бэкэнд-круга» со многими техническими экспертами с нетерпением ждет вашего присоединения, группа людей с одинаковой частотой, вместе растут, вместе совершенствуются и ломают ограничения познания.

Еще больше интересных статей в разделе «Серверное мышление»!