Следуйте за мной в Twitter, с радостью приму ваши предложения по темам или улучшениям / Крис

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

В первой части Часть I - Долговечные функции мы попытались изучить различные основные концепции, такие как:

  • Функция оркестратора, это функция, содержащая ваш бизнес-поток.
  • Функция активности, функции, выполняющие фактическую работу.
  • Клиентская функция, точка входа

В той первой статье мы создали приложение, которое просто выполняло ряд функций деятельности, одну за другой.
Выполнение функций таким образом имеет имя связывание и является известным шаблоном приложения.

В этой статье мы рассмотрим:

  • Шаблоны приложений, давайте подробнее рассмотрим наиболее распространенные шаблоны архитектуры, используемые с Durable Functions.
  • Разветвление / разветвление, мы увеличим масштаб, в частности, для шаблона Разветвление / разветвление.
  • Лабораторная работа: в рамках нашего изучения шаблона Fan-out / Fan-in мы создадим что-то с его помощью, чтобы убедиться, что мы понимаем основные концепции.

Ресурсы

- Бесплатная учетная запись Azure. Вам нужно будет зарегистрироваться в Azure, чтобы использовать долговечные функции
- Создание вашей первой надежной функции с помощью JavaScript Краткое руководство, которое проведет вас через создание надежной функции
- Долговечные функции концепции Подробнее о концепциях и шаблонах и о том, как их реализовать.
- Ограничения функций оркестратора Ограничения, о которых необходимо знать.
- Обработка событий с помощью функций Durable
Как вызывать и обрабатывать события.
- Шаблоны приложений
Описание различных шаблонов приложений, которые вы можете реализовать

Шаблоны приложений

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

  • Цепочка, последовательность функций, выполняемых в определенном порядке, это шаблон, который мы использовали в первой статье нашей серии о надежных функциях.
  • Разветвление / разветвление: параллельное выполнение нескольких функций и ожидание завершения всех функций.
  • Асинхронные HTTP API. Распространенным способом реализации этого шаблона является запуск HTTP-вызова для длительного действия. Затем перенаправьте клиента на конечную точку состояния, которую клиент опрашивает, чтобы узнать, когда операция будет завершена.
  • Мониторинг - это гибкий повторяющийся процесс в рабочем процессе. Пример - опрос до тех пор, пока не будут выполнены определенные условия.
  • Взаимодействие с людьми. Многие автоматизированные процессы предполагают взаимодействие человека. Вовлечь людей в автоматизированный процесс сложно, потому что люди не так высокодоступны и не так отзывчивы, как облачные сервисы. Автоматизированный процесс может позволить это за счет использования тайм-аутов и логики компенсации.

Разветвление / разветвление

Эту закономерность лучше всего пояснить на картинке ниже:

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

Здесь много вопросов, например:

  1. Когда бы я когда-нибудь использовал этот шаблон
  2. Если я воспользуюсь этим шаблоном, как мне его реализовать?

Попробуем ответить на каждый вопрос по очереди. Когда вы бы его использовали? Существует довольно много рабочих процессов, в которых проявляется такое поведение. Довольно распространенным доменом может быть сборочная линия на заводе. Допустим, у нас есть завод игрушечных автомобилей. Представьте, что вы начинаете со сборки шасси, это будет шаг F1. Затем он переходит на станцию, где 3 разных сборщика каждый должен добавить свою вещь к этому продукту. Один добавляет колеса, второй - двери, а третий - двигатель. Затем, когда они закончили, он переходит к последней станции F3, на которой игрушечная машинка покрывается слоем краски.

Вот и все. Отлично выглядящая машина и где-то счастливый ребенок. :)

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

А теперь давайте попробуем ответить на другой вопрос, как.

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

Вы спросите, что это за конструкции? Всего их три:

  1. Поднять внешнее событие
  2. Подождите внешнего события
  3. Решающая логика

Первый здесь выглядит так:

await client.raiseEvent(instanceId, 'EventName', <value>);

Вторая конструкция выглядит так:

yield context.df.waitForExternalEvent("EventName");

Третья конструкция выглядит так:

yield context.df.Task.all([gate, gate2, gate3]);

Это требует дополнительных объяснений. Он отвечает на вопрос, как нам ждать. Вышесказанное говорит о том, что я gate, gate2 и gate3 разрешу все, прежде чем сделаю следующий шаг. При совместном использовании в функции Orchestrator это может выглядеть так:

const gate = context.df.waitForExternalEvent("WheelsAddedEvent");

const gate2 = context.df.waitForExternalEvent("DoorsAddedEvent");

const gate3 = context.df.waitForExternalEvent("SteeringAddedEvent");

yield context.df.Task.all([gate, gate2, gate3]);

const result = yield context.df.callActivity("Send_Car_To_Be_Painted");

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

Я могу представить, о чем вы сейчас думаете. А как насчет того, чтобы поднять событие ?. Когда я это сделаю? Представьте себе, что в каждой точке сборки вы выполняете фактическую работу, добавляя колеса, двери или рулевое управление, а после того, как вы закончите, вы вызываете конечную точку REST, которая в конечном итоге вызывает соответствующее событие. Позвольте мне показать это на картинке:

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

Лаборатория - встреча за ужином в Шире

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

Тема этой демонстрации - LOTR или «Властелин колец», а точнее начало фильма «Хоббит». Так что же там происходит? Ну, все начинается с званого ужина в Шире, где все члены квестовой группы встречаются, вместе обедают, а затем отправляются в путь. Конечно, они съедают всю еду, которую Бильбо съедал, но это уже другая история.

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

Хорошо, нам нужно организовать это в Durable Functions, так с чего же начать?

Начнем с определения, какие события у нас есть. Если вы правильно помните свой фильм «Хоббит», то гости к ужину приходят один за другим. Итак, гость прибытие - это событие. У нас также есть некоторая логика принятия решений. Как только все гости ужина собрались, они начинают обсуждать свой большой план. Поэтому нам нужно дождаться прибытия всех, прежде чем мы сможем продолжить. Обладая этими знаниями, мы можем начать вырабатывать логику оркестратора, например:

const gate = context.df.waitForExternalEvent("BilboArrived");

const gate2 = context.df.waitForExternalEvent("DvalinArrived");

const gate3 = context.df.waitForExternalEvent("GandalfArrived");

yield context.df.Task.all([gate, gate2, gate3]);

const result = yield context.df.callActivity("Talk_Shop");

Выше мы создали три разных события BilboArrived, DvalinArrived, GandalfArrived и, наконец, у нас есть действие, которое мы начинаем Talk_Shop, как только все гости ужина будут на своих местах.

Да, да, я знаю, что гостей было на тонну больше, но приведенное выше иллюстрирует принцип :)

Из того, что мы узнали из нашего обзорного изображения, мы можем создавать обычные функции Azure с триггерами HTTP, которые по завершении могут вызывать события, так что это почти все, давайте теперь превратим это в реальный код.

Строим наш проект

Мы начинаем с вызова нашей палитры команд, CMD + SHIFT + P или View > Command Palette, и выбираем ниже

Далее нужно создать функцию HttpStart. Мы снова вызываем палитру команд, выбираем создание функции Azure и выбираем Durable Functions HTTP Starter.

После этого мы решили создать функцию оркестратора. Мы выполняем те же шаги, что и выше, но выбираем тот, который называется Durable Functions Orchestrator.

Затем мы выбираем создание обычного Azure Function, выбираем его как HTTP-триггер и называем его QuestParty.

Наконец, мы создаем Durable Functions activity и называем его Talk_Shop.

Ваш каталог должен выглядеть примерно так

Настроить оркестратор

Хорошо, мы уже набросали, как это может выглядеть, но вот снова:

const taskGandalf = context.df.waitForExternalEvent("Gandalf");
const taskBilbo = context.df.waitForExternalEvent("Bilbo");
const taskDvalin = context.df.waitForExternalEvent("Dvalin");

yield context.df.Task.all([taskGandalf, taskBilbo, taskDvalin]);

const result = yield context.df.callActivity("Talk_Shop");

return result;

В приведенном выше коде говорится, что мы ждем событий Bilbo, Dvalin, Gandalf в произвольном порядке, а в следующей строке говорится, что все три должны произойти, прежде чем мы сможем продолжить:

yield context.df.Task.all([taskGandalf, taskBilbo, taskDvalin]);

и да, наше последнее действие - вызвать действие Talk_Shop:

const result = yield context.df.callActivity("Talk_Shop");

Это все, что касается оркестровки.

Настроить QuestParty функцию срабатывания HTTP

Итак, эта функция запускается по HTTP. Мы можем увидеть это, если перейдем к QuestParty/function.json и, в частности, к этой записи привязки:

{
  "authLevel": "anonymous",
  "type": "httpTrigger",
  "direction": "in",
  "name": "req",
  "methods": [
    "get",
    "post"
  ]
}

НО, нам пришлось добавить еще кое-что, чтобы сделать этот мяч для игры в мяч, а именно эту запись:

{
  "name": "starter",
  "type": "orchestrationClient",
  "direction": "in"
}

Это позволяет нам общаться с экземпляром клиента оркестрации, и это нам понадобится для создания события. Теперь посмотрим на следующий код QuestParty/index.js:

const df = require("durable-functions");

module.exports = async function (context, req) {
  context.log('Quest party member arrival');

  const { who, instanceId }  = req.query;

  const client = df.getClient(context);

  const fellowshipMembers = ['Gandalf', 'Bilbo', 'Dvalin'];
  const found = fellowshipMembers.find(m => who);

  if(!found)  {
      context.res = {
          status: 400,
          body: `Someone unknown called ${who} just entered Bilbos house, crap!`
      };
  } else {
      await client.raiseEvent(instanceId, who, true);
      context.res = {
          // status: 200, /* Defaults to 200 */
          body: `Another hungry member ${who} entered Bilbos house`
      };
  }
};

Теперь есть две части очень важной информации, которую мы получаем из параметров запроса, а именно who и instanceId. who - это передача аргумента типа Gandalf, Dvalin или Bilbo. instanceId - это ссылка на этот конкретный экземпляр вызова функции. Поэтому, если мы хотим повлиять на конкретный экземпляр выполнения, нам нужно знать этот конкретный идентификатор. Но откуда это взялось? Когда вы впервые звоните HttpStart/index.js, мы получаем instanceId:

module.exports = async function (context, req) {
    const client = df.getClient(context);
    const instanceId = await client.startNew(
      req.params.functionName, 
      undefined, req.body
    );

    context.log(`Started orchestration with ID = '${instanceId}'.`);

    return client.createCheckStatusResponse(
      context.bindingData.req, 
      instanceId
    );
};

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

На этом хватит теории. Давайте запустим это и отладим.

Отлаживать

Итак, лучший способ понять, как что-то работает, - это просто отладить это. Мы сделаем это, нажав Debug > Start Debugging.

Это должно дать нам следующее:

Выше мы видим, что у нас есть две конечные точки, которые мы можем поразить:

  1. Http: // localhost: 7071 / api / orchestrators / {functionName} Это попадет в нашу точку входа и запустит оркестровку
  2. Http: // localhost: 7071 / api / QuestParty

Давайте начнем с первого и начнем нашу оркестровку, назвав ее так:

http://http://localhost:7071/api/orchestrators/Orchestrator

Проходим через все и получаем в браузере следующее:

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

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

http://localhost:7071/api/QuestParty?instanceId={the id we saw in the browser}&who=Gandalf

Учитывая приведенный выше пример URL-адреса, мы вызовем событие Gandalf, учитывая, как код написан в QuestParty/index.js. Итак, давайте скопируем id из браузера и нажмем QuestParty URL в браузере и посмотрим, что произойдет:

Затем мы должны нажать VS Code и наш QuestParty код следующим образом:

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

Давайте сделаем еще один вызов браузера QuestParty конечной точке:

http://localhost:7071/api/QuestParty?instanceId={the id we saw in the browser}&who={Dvalin, Gandalf or Bilbo}

с Dvalin и Bilbo в качестве аргументов соответственно для параметра who. После того, как мы продолжим работу отладчика при каждом вызове, мы окажемся здесь в оркестраторе:

Как вы можете видеть выше, наша логика принятия решения была выполнена, все три события Gandalf, Bilbo и Dvalin были вызваны, что означает:

yield context.df.Task.all(taskGandalf, taskBilbo, taskDvalin)

и это означает, что мы больше не останавливаемся на приведенной выше строке, но передаем ее, и наша последняя задача - вызвать действие Talk_Shop.

Вот и все, все готовы выполнить квест.

Логика принятия решения

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

const taskSignedCEO = context.df.waitForExternalEvent("InvoiceSignedCEO");
const taskSignedCFO = context.df.waitForExternalEvent("InvoiceSignedCFO");
const taskSignedManager = context.df.waitForExternalEvent("InvoiceSignedManager");

yield context.df.Task.any([taskSignedCEO, taskSignedCFO, taskSignedManager]);

const result = yield context.df.callActivity("Set_Invoice_As_Processed");

return result;

Резюме

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

Кроме того, мы поговорили о конкретном шаблоне Fan-out / Fan-in и продемонстрировали, как его можно использовать на сборочной линии, а также для ожидания прибытия гномов / хоббитов / колдунов на званый обед. Независимо от типа вашего бизнеса, функции Durable могут значительно помочь в организации ваших бизнес-процессов.

Благодарности

Спасибо Anthony за поддержку в написании этой статьи :)

Первоначально опубликовано на https://dev.to 18 июня 2019 г.