Гибкий дизайн API: создание хуков (Hooks) для конвейера PHP API

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

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

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

Что такое Hooks/Actions?

Крюк (он же действия / фильтры) это название на события и связанные с ними обратные вызовы со стороны WordPress сообщества. Если у вас есть опыт программирования, возможно, вы знакомы с обратными вызовами и шаблоном » издатель — подписчик«. Во время обработки система может инициировать событие, которое вызывает ноль для многих функций, подписанных на это событие. Например, в ответ на загрузку страницы WordPress вызывает функции для загрузки заголовка, загрузки заголовка, вывода списка сообщений или поиска нужного шаблона. Эти задачи выполняются, не загромождая основной процесс создания страницы.

Идея хуков не нова, и она не была изобретена WordPress. Однако WordPress проделал большую работу по их реализации в течение жизненного цикла обработки страниц на стороне сервера. На мой взгляд, такое использование хуков — это, пожалуй, самая лучшая особенность платформы. С помощью этих хуков пользователи могут писать свои собственные функции — будь то плагины или темы — которые связаны с WordPress и запускают любой код, который вы хотите, когда это необходимо. Вам нужно изменить заголовок, отправляемый пользователю? Нет проблем: подключитесь к wp_headersсобытию, и вы можете изменить заголовки по своему усмотрению.

Зачем использовать хуки в API?

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

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

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

Построение основных механизмов

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

Вот hooks.php:

// Global array which will hold all of our hooks
// We will reference this array in each function to add/remove/call our hooks
// The code below should also be seen by any callbacks we write for the system later.
$hooks = [];

// Below are global functions that can be seen from our API code
// The add_hook method will allow us to attach a function (callback) to a given event name 
function add_hook($event_name, $callback) {
    global $hooks;

    if ($callback !== null) {
        if ($callback) {
          // We can set up multiple callbacks under a single event name
            $hooks[$event_name][] = $callback;
        }
    }
}

// Super easy to implement, we remove the given hook by its name
function remove_hook($event_name) {
    global $hooks;

    unset($hooks[$event_name]);
}

// When we want to trigger our callbacks, we can call this function 
// with its name and any parameters we want to pass.
function do_hook($event_name, ...$params) {
    global $hooks;

    if (isset($hooks[$event_name])) {
      // Loop through all the callbacks on this event name and call them (if defined that is)
      // As we call each callback, we given it our parameters.
        foreach ($hooks[$event_name] as $function) {
            if (function_exists($function)) {
                call_user_func($function, ...$params);
            }
        }
    }
}

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

В качестве простого примера предположим, что у нас есть API для регистрации нового пользователя в нашей системе. У нас может быть вызвана конечная точка REST API /addUser. Ради простоты давайте также предположим, что цель здесь — просто вставить имя и возраст нового пользователя в usersтаблицу нашей базы данных. Довольно прямолинейно, правда?

// POST endpoint for adding a user (part of a larger API class)
public function addUser($name, $age) {
  if ($this->request->method === 'post') {
    try {
      $this->db->insert('users', ['name' => $name, 'age' => $age]);
      return new Response(200, 'User created successfully!');
    } catch (Exception $e) {
      // Oops, something went wrong.
      // Do some logging or whatever.
    }
  }

  // If not a POST request, return http status 400
  return new Response(400, 'Bad request');
}

Приведенный выше код представляет собой чрезмерно упрощенное и обобщенное представление о том, как мы можем добавить нового пользователя. Идея состоит в том, что, если кто-то вызовет /addUserконечную точку нашего API, они в конечном итоге дойдут до этой функции, в которой имя и возраст пользователя будут извлечены из опубликованных данных. Сначала мы проверяем, публикуют ли они сообщения (как того требуют правила REST), а затем пытаемся вставить пользователя в usersтаблицу.

Затем, если пользователь был вставлен успешно, мы хотим вызвать ловушку, чтобы позволить любому коду прослушивать, что пользователь был создан (это похоже на создание события на других языках).

Что делать при изменении требований

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

Следующее, что вы знаете, эта конечная точка — это не только добавление пользователя в базу данных нашего веб-сайта, но и вызов функций для отправки электронных писем, добавление пользователей в Zendesk, Jira и облако Microsoft, а затем обработка их успешных / неудачных результатов. Все эти дополнительные вещи действительно уводят от четкой точки добавления пользователя в нашу базу данных. Мы хотели бы вызвать одно событие, а другой код просто слушать, когда создается пользователь, и делать свои собственные вещи — без необходимости вообще изменять эту конечную точку. Возможно, никакие другие службы не заботятся о добавлении нового пользователя, поэтому никому не нужно ничего делать. Конечная точка не должна заботиться. Довольно круто, правда?

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

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

public function addUser($name, $age) {
  if ($this->request->method === 'post') {
    try {
      $this->db->insert('users', ['name' => $name,  'age' => $age]);
      // Call our new hook and give it the name and age of the user. Anyone listening can then access these params.
      do_hook('added_user', $name, $age);
      return new Response(200, 'User created successfully!');
    } catch (Exception $e) {
      // Oops, something went wrong.
      // Do some logging or whatever.
    }
  }

  return new Response(400, 'Bad request');
}

Теперь у нас могут быть десятки функций обратного вызова, слушающих added_userловушку, или вообще ни одной. Возможно, у нас есть один обратный вызов, который отвечает за добавление пользователя в Zendesk, а другой, который берет имя и возраст и генерирует электронное письмо для маркетинга. Этот код «подписчика» может находиться где-то еще в кодовой базе, если он может видеть hooks.phpкод внутри проекта API. Обычно я помещаю свою функцию обратного вызова в отдельный файл и также включаю этот файл в API. Ниже приведен один пример обратного вызова, который теперь может использовать этот новый крючок, который мы создали:

function sendContactToMarketing($name, $age) {
  // Do email stuff
}

add_hook('added_user', 'sendContactToMarketing');

Где мы можем разместить эти крючки?

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

Читайте также:  Что выбрать: функциональное программирование или ООП?

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

На изображении ниже показано, как все это выглядит архитектурно.

На изображении ниже показано, как все это выглядит архитектурно

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

Лучшие практики

Давайте теперь рассмотрим некоторые передовые практики, которым вы и ваши разработчики должны следовать.

Совет 1: держите крючки поджарыми и скупыми

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

Совет 2: сделайте каждый обратный вызов изолированным и простым для отладки

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

Совет 3. Думайте о производительности и не злоупотребляйте крючком.

Также важно помнить о том, сколько обратных вызовов вы прикрепляете к своим хукам. Несколько быстрых обратных вызовов — это нормально, но 100 обратных вызовов на одном хуке, выполнение каждого из которых занимает одну секунду, действительно могут стать тормозом для вашего API. Нам нужны быстрые вызовы API, и каждый обратный вызов может легко замедлить время ответа. Опять же, если вы обнаружите, что обратный вызов медленный, переместите задачу в фоновый процесс или в систему очередей, чтобы позже ее подхватила другая служба. Я часто использую рабочие места в таких системах, как Laravel, для решения таких задач. Возможно, добавьте пользовательскую задачу в очередь заданий Laravel и продолжите обработку API.

Совет 4. Оставайтесь на связи со своим сообществом разработчиков

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

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

Заключение

В этой статье мы обсудили, что такое хуки / действия и как их можно использовать. Мы привели пример кода PHP, который вы можете использовать в своем PHP API для реализации «перехвата», и как можно использовать обратный вызов для привязки к этому перехвату. Мы также обсудили добавление хуков на общем уровне API (более глобально для всех запросов), а также в конечных точках. В дополнение к этому мы также немного поговорили о некоторых недостатках этой системы и о том, что лучше делать обратные вызовы лаконичными и скупыми. Но если у вас есть длительный обратный вызов, мы упомянули несколько стратегий работы с такими процессами, чтобы они не влияли на ваш конвейер API.

Если вы реализуете эту систему, вы также сможете получить некоторые из самых больших функциональных возможностей, которыми сообщество WordPress (и программисты в целом) наслаждались годами. Вы также сэкономите много времени и избавитесь от головной боли от необходимости напрямую изменять свой код API, и вместо этого вы сможете сосредоточиться на небольшом коде обратного вызова. Вы также можете добавлять и удалять обратные вызовы, которые могут быть полной интеграцией с другими системами. Вся эта функциональность — и ни разу не придется повторно публиковать код конвейера API! Это неплохая сделка, правда? С помощью этой системы мне удалось выполнить несколько простых интеграций за день, и теперь вы тоже можете это сделать.

Оцените статью
bestprogrammer.ru
Добавить комментарий