Platform V: микросервис за 15 минут

Вступление

В статье речь пойдет о быстрой разработке приложения (микросервисном подходе). Основной идея в том, чтобы по итогам прочтения можно было бы начать быстро и удобно писать полноценные микросервисы (front + back + хранилище данных).
Рассматриваемый пример будет написан на TypeScript/JavaScript, но это требование не обязательно. Код функций может быть также написан на Java или Python. Обращение к DataSpace через GraphQL не накладывает каких-либо ограничений на клиента.

Статья разбита на две части:

  • В первой познакомимся с продуктом Platform V DataSpace, напишем frontend-приложение, используя DataSpace как сервис (Backend-as-a-Service).
  • Во второй познакомимся с сервисом Platform V Functions, напишем backend-приложение как облачную функцию и разместим наше frontend-приложение также, как функцию (Function-as-a-Services).

Работа в SmartMarket Studio

Для того, чтобы начать работу в SmartMarket Studio:

  • Заходим на сайт https://developers.sber.ru/studio/login.
  • Регистрируемся или входим по Сбер ID (можно через QR-код в приложении СберБанк Онлайн).
  • Создаем «Личное пространство».
  • В созданном пространстве нажимаем Создать проект, где выбираем Platform V DataSpace.
  • После создания проекта попадаем в визуальный редактор конструирования модели данных.

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

Можно сразу же загрузить готовую модель данных для приложения «Промоакция»: model.xml и нажать кнопку «Выпустить».
После выпуска сервиса по данному адресу https://smapi.pv-api.sbc.space/fn_d2527eab_2999_4a9a_99b1_4f90bf815b54 можно начать работу в приложении в роли администратора, где в качестве хранилища выступит ранее выпущенный вами сервис DataSpace.
Нажав кнопку Login page необходимо указать данные авторизации. Адрес, логин, пароль будут доступны в настройках вашего проекта DataSpace: адрес проекта, appkey, appsecret соответственно.

По факту пройдя по ранее указанному адресу вы уже воспользовались сервисом Platform V Functions, получив статику web-приложения, работающего напрямую уже с вашим DataSpace.
Более детально поговорим об этом во второй части статьи в разделе Знакомство с Functions.

Приложение «Промоакция»

Для наглядности примера использования платформенных сервисов будем разрабатывать небольшое приложение «Промоакция».

Схема сервиса:

Видим два вида пользователей системы: Администратор и Клиент. Для разработки сервиса воспользуемся двумя сервисами Platform V (уже доступными разработчику в SmartMarket): DataSpace и Functions.

Часть 1

Знакомство с DataSpace

Для работы с данными мы будем использовать платформенный сервис DataSpace.

Материалы по этапам:

  1. Проектирование модели предметной области приложения в визуальном редакторе:

  2. Работа с данными через GraphQL API после выпуска сервиса DataSpace, в том числе через конструктор в визуальном редакторе: документация.

Подписание запросов (авторизация)

Итак, ранее мы с вами уже создали модель предметной области и выпустили соответствующий сервис DataSpace, предоставляющий GraphQL API для работы с данными.
Но для вызова извне необходимо отправлять HTTP-запросы на соответствующий сервис, предварительно подписывая их при помощи ключа.

Можно делать такие запросы, воспользовавшись соответствующим JavaScript SDK.

Также SDK предоставляет возможность ручного формирования запросов через визуальную форму:

Данные для подписи запроса доступны в настройках соответствующего проекта в SmartMarket:

В настройках доступны ak/sk, а также адрес сервиса для вызова:

Архитектура приложения «Промоакция»

У разных типов пользователей разные каналы для работы:

  • «Администратор» (Admin) работает с DataSpace через авторизованные запросы на API Gateway,
    зная адрес сервиса, appKey (логин) и appSecret (пароль), далее ak/sk. Необходимая для работы статика появляется посредством функции Function 1 Admin Frontend.
  • «Клиент» (Client) через общедоступный сервис вводит промокод и выбирает подарок. Воспользоваться промокодом можно только один раз. Необходимая для работы статика появляется посредством функции Function 2 Client Frontend. В функции Function 3 Client Backend реализуем серверную логику обработки запросов от «Клиентов».

Function 1 Admin Frontend

Теперь перейдем к написанию frontend-приложения, когда в качестве backend используется DataSpace.

Вооружимся следующим технологическим стеком:

  • TypeScript — язык программирования.
  • GraphQL — язык запросов к серверной части (DataSpace).
  • GraphQL Code Generator (TypeScript) — очень полезная утилита для преобразования GraphQL-запросов в конструкции на TypeScript.
  • ReactJS — ReactJS.
  • Apollo Client v3 — JS-библиотека для работы через GraphQL: кэширование, react-хуки и другое.
  • Ant Design — готовые экранные компоненты.
  • Webpack 5 — средство для сборки.

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

Исходники приложения доступны по ссылке на GitHub: https://github.com/VictorBiryukov/promo-action.git.

Разработка

Начнем с режима разработки: webpack.dev.config.js.

Из интересного в конфигурации сборки — только настройки прокси у сервера разработки:

...    
  devServer: {
       hot: true,
       port: 3000,
       before: (app) => {
           app.use(createProxyMiddleware("/graphql",
               {
                   target: process.env.DS_ENDPOINT,
                   changeOrigin: true,
                   secure: false,
                   pathRewrite: { '/graphql': '' }
               }));
       },
       watchOptions: {
           poll: true,
           ignored: "/node_modules/"
       }
   },
...

Таким образом, мы обходим проверку запрета на CORS со стороны сервиса при работе через браузер в режиме локальной разработки.
Для корректной работы прокси-сервера необходимо в файле .env указать адрес вашего сервиса DataSpace:

DS_ENDPOINT=[Enter your dataspace graphql endpoint here]

Перейдем непосредственно к написанию приложения.
В файле src/index.tsx ничего необычного: подключаем React, указываем корневой компонент App.

Прежде чем начать работать с DataSpace, необходимо научить наш провайдер GraphQL-запросов правильно их подписывать.
Для этого даем возможность пользователю ввести адрес DataSpace + ak/sk, сохраняем данные в localStorage:

  <Form>
      <Form.Item>
          <Input placeholder="Service address"
              value = {appAddress}
              onChange={e => setAppAddress(e.target.value)}
          />
      </Form.Item>
      <Form.Item>
          <Input placeholder="Service key"
              value = {appKey}
              onChange={e => setAppKey(e.target.value)}
          />
      </Form.Item>
      <Form.Item>
          <Input.Password placeholder="Service secret"
              value = {appSecret}
              onChange={e => setAppSecret(e.target.value)}
          />
      </Form.Item>
  </Form>

Заполнив параметры, передаем их далее в AppProvider, где уже при помощи ранее представленного JavaScript SDK определяем правило подписания, после чего инициализируем ApolloClient:

  const authFetch = (uri: any, options: any) => {

    let sig = new signer.Signer()
    sig.Key = appKey
    sig.Secret = appSecret
    let request = new signer.HttpRequest(options.method, appAddress, options.headers, options.body)
    sig.Sign(request)

    return fetch(uri, options);
  };

  console.log(process.env.NODE_ENV);



  return new ApolloClient({
    cache: cache,
    link: new HttpLink({
      uri: process.env.NODE_ENV === 'production' ? appAddress : 'graphql',
      fetch: authFetch,
    })
  })

Apollo Client призван значительным образом упростить работу с сервисом DataSpace через GraphQL API:

С одной стороны он хорошо интегрируется с React через хуки (hooks), с другой — обеспечивает бесшовную интеграцию с GraphQL-сервером DataSpace, решая вопросы кэширования и нормализации данных на стороне клиента.

Вернемся к нашему приложению «Промоакция».
В UI-консоли администратора нам необходимо обеспечить следующие возможности:

  • запрос списка компаний-спонсоров;
  • создание/удаление компании-спонсора;
  • запрос списка подарков компании-спонсора;
  • создание/удаление подарка.

Для написания соответствующих запросов воспользуемся GraphQL-конструктором внутри визуального редактора.
В примере ниже мы одним запросом создаем компанию-спонсора (GiftVendor) и два ее первых подарка (Gift):

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

Давайте уделим внимание двум моментам:

  • Указывая ключ SberBankAndTwoFirstGifts в параметре idempotencePacketId в мутации packet, мы делаем этот запрос идемпотентным.
    При повторном выполнении данного запроса (если первый был успешным) DataSpace вернет результат выполнения, но не будет повторять саму операцию создания (изменения состояния БД).
    Эта функциональность DataSpace призвана значительно упросить жизнь разработчику клиентской части, когда необходимо повысить надежность взаимодействия с сервисом: можно не беспокоится о лишних данных, делая повтор команды в случае, например, получения ошибки тайм-аута выполнения при первоначальном вызове.
  • Также обратите внимание на лексему ref:createGiftVendor, передаваемую в поле vendor создаваемых подарков. Таким образом обеспечена связь между подарками и компанией-спонсором, создаваемой на первом шаге выполнения пакета.

Детальное описание формата GraphQL-запросов DataSpace доступно в документации documentation/graphql.

Для нашего же приложения понадобится набор совсем простых GraphQL-запросов:

query searchGiftVendor{
  searchGiftVendor{
    elems{
      id
      __typename
      name
    }
  }
}
mutation createGiftVendor($name:String!){
  packet{
    createGiftVendor(input:{
      name: $name
    }){
      id
      __typename
      name
    }
  }
}
mutation deleteGiftVendor($id: ID!){
  packet{
    deleteGiftVendor(id: $id)
  }
}
query searchGift($cond: String){
  searchGift(cond: $cond){
    elems{
      id
      __typename
      serialNumber
      kind
    }
  }
}
mutation createGift($vendorId: ID!, $serialNumber:String!, $kind: _EN_GiftKind){
  packet{
    createGift(input:{
      vendor: $vendorId
      serialNumber: $serialNumber
      kind: $kind
    }){
      id
      __typename
      serialNumber
      kind
    }
  }
}
mutation deleteGift($id: ID!){
  packet{
    deleteGift(id: $id)
  }
}

Зафиксируем данные запросы в файле src/graphql/requests.graphql.
Нам также понадобится GraphQL-схема API DataSpace, которая доступна в конструкторе визуального редактора.
Справа в закладке SCHEMA выбираем DOWNLOAD - SDL:

Выгруженный файл поместим в корневую папку проекта schema.graphql.

Теперь давайте осуществим генерацию Typescript-конструкций на основе запросов и схемы, зафиксированных ранее.
В файле package.json у нас подключены необходимые JS-библиотеки для этапа разработки и прописана команда генерации в разделе scripts:

...  
  "devDependencies": {
    ...
    "@graphql-codegen/cli": "^1.9.0",
    "@graphql-codegen/typescript": "1.22.3",
    "@graphql-codegen/typescript-operations": "1.18.2",
    "@graphql-codegen/typescript-react-apollo": "2.2.7",
    ...
  },
...
 "scripts": {
    ...
    "codegen": "graphql-codegen --config codegen.yml",
    ...
  },
...

Конфигурируем правила генерации в codegen.yml:

overwrite: true # флаг перезаписи файла генерируемого кода
schema: 'schema.graphql' # файл graphql-схемы
documents: 'src/graphql/**/*.graphql' # маска файлов c graphql-запросами
generates:
  ./src/__generate/graphql-frontend.ts: # результирующий файл генерации
    plugins:
      - typescript # генерация типов
      - typescript-operations # генерация операций
      - typescript-react-apollo # генерация React Apollo компонентов

Все готово для генерации. Запускаем из консоли соответствующую команду npm run codegen:

Генерация прошла успешно. Давайте взглянем на результаты:src/__generate/graphql-frontend.ts.

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

  • aggVersion: версия агрегата, которую можно использовать для формирования транзакции между получением и сохранением данных в БД (оптимистическая блокировка).
  • lastChangeDate: дата/время последнего изменения экземпляра сущности.
  • type: тип сущности (может быть полезен в случае использования наследования в модели данных).
  • aggregateRoot: ID корня агрегата.

Например, тип для сущности Gift:

export type Gift = {
  id: Scalars['ID'];
  aggVersion: Scalars['Long'];
  lastChangeDate?: Maybe<Scalars['_DateTime']>;
  chgCnt?: Maybe<Scalars['Long']>;
  kind?: Maybe<_En_GiftKind>;
  serialNumber: Scalars['String'];
  type: Scalars['String'];
  vendor: GiftVendor;
  aggregateRoot?: Maybe<GiftVendor>;
  ...
};

Также отражено определенное нами ранее перечисление GiftKind:

export enum _En_GiftKind {
  Cap = 'CAP',
  Tshirt = 'TSHIRT',
  Mug = 'MUG'
}

В дальнейшем мы воспользуемся данными перечислением при написании формы создания подарков.

В конце файла видим сгенерированный ряд хуков, соответствующих нашим GraphQL-запросам (src/graphql/requests.graphql).
Например, запросы searchGift, createGift, deleteGift представляют следующие функции:

...
export function useSearchGiftQuery(baseOptions?: Apollo.QueryHookOptions<SearchGiftQuery, SearchGiftQueryVariables>) {
        const options = {...defaultOptions, ...baseOptions}
        return Apollo.useQuery<SearchGiftQuery, SearchGiftQueryVariables>(SearchGiftDocument, options);
      }
...
export function useCreateGiftMutation(baseOptions?: Apollo.MutationHookOptions<CreateGiftMutation, CreateGiftMutationVariables>) {
        const options = {...defaultOptions, ...baseOptions}
        return Apollo.useMutation<CreateGiftMutation, CreateGiftMutationVariables>(CreateGiftDocument, options);
      }
...
export function useDeleteGiftMutation(baseOptions?: Apollo.MutationHookOptions<DeleteGiftMutation, DeleteGiftMutationVariables>) {
        const options = {...defaultOptions, ...baseOptions}
        return Apollo.useMutation<DeleteGiftMutation, DeleteGiftMutationVariables>(DeleteGiftDocument, options);
      }
...

Это функции-обертки над React-хуками Apollo.useQuery и Apollo.useMutation. Данные конструкции призваны типизировать нашу бесшовную интеграцию между серверной и клиентской частью приложения.
Давайте более детально взглянем на useCreateGiftMutation:

  • CreateGiftMutationVariables определяют сигнатуру входящих параметров:
...
export type CreateGiftMutationVariables = Exact<{
  vendorId: Scalars['ID'];
  serialNumber: Scalars['String'];
  kind?: Maybe<_En_GiftKind>;
}>;
...
  • CreateGiftMutation определяет сигнатуру возвращаемого результата:
...
export type CreateGiftMutation = (
  { __typename?: '_Mutation' }
  & { packet?: Maybe<(
    { __typename?: '_Packet' }
    & { createGift?: Maybe<(
      { __typename: '_E_Gift' }
      & Pick<_E_Gift, 'id' | 'serialNumber' | 'kind'>
    )> }
  )> }
);
...

Итак, на данном этапе мы:

  • научились подписывать наши HTTP-запросы к серверной части;
  • определили набор GraphQL-запросов, которые нам понадобятся для работы;
  • осуществили генерацию необходимых Typescript-конструкций, призванных упростить работу при написании кода.

Перейдем непосредственно к прикладным формам.
Форма отображения/добавления/удаления компаний-спонсоров реализована в соответствующем компоненте src/components/GiftVendorList.tsx.
Она отражает список доступных компаний в виде вкладок (Tabs):

Кнопка Add new gift vendor позволяет заводить новые компании-спонсоры:

Кнопка Delete gift vendor удаляет компанию.

На что хочется обратить внимание в коде:

  • Для получения списка компаний-спонсоров используется хук useSearchGiftVendorQuery, который был сгенерирован на основе запроса SearchGiftVendor, зафиксированного в файле src/graphql/requests.graphql.
  • Из результата выполнения хука деструктуризируем параметры data, loading, error. Далее обрабатываем соответствующим образом значения loading, error.
...
  const { data, loading, error } = useSearchGiftVendorQuery()
  const giftVendorList = data?.searchGiftVendor.elems
...
  if (loading) return (<Spin tip="Loading..." />);
  if (error) return <p>`Error! ${error.message}`</p>;

Сам GraphQL-запрос SearchGiftVendor нам вернет JSON-структуру следующего вида:

Так как в дальнейшем для отрисовки вкладок с компаниями нам нужен только сам массив компаний, определим для этого отдельную константу giftVendorList, ссылающуюся на массив elems возвращаемой запросом конструкции.
Преимущество такого подхода заключается в том, что мы работаем с данными через типизированную структуру, которая основывается на модели предметной области приложения, описанной ранее в DataSpace.
Более подробно про работу с мутациями в Apollo можно ознакомиться в статье. Далее при помощи функции getTabs, принимающей на вход параметром список полученных компаний, создаем вкладки:

...
  <Form style={{ margin: "10px" }}>
      <Form.Item>
          <Tabs>
              {getTabs(giftVendorList)}
          </Tabs>
      </Form.Item>
  </Form>
...

Давайте теперь разберемся с добавлением/удалением компаний:

...
  const [createGiftVendorMutation] = useCreateGiftVendorMutation()
  const [deleteGiftVendorMutation] = useDeleteGiftVendorMutation()
...

Вытаскиваем из соответствующих хуков функции — мутации добавления/удаления.
Более подробно про работу с мутациями в Apollo можно почитать в статье.

В модальной форме для кнопки Ок добавляем соответствующий обработчик, где делаем вызов мутации, передавая два параметра:

  • variables— это набор параметров, заполняемых пользователем на форме. В нашем случае имя компании-спонсора.
  • update — параметр позволяет передать функцию, которая в нашем случае обновит кэш Apollo-клиента (store) для запроса SearchGiftVendor, добавив туда результат (result) GraphQL-запроса createGiftVendor.
...
  <Modal visible={showCreateForm}
      onCancel={() => setShowCreateForm(false)}
      onOk={() => {
          setShowCreateForm(false)
          createGiftVendorMutation({
              variables: {
                  name: vendorName!
              },
              update: (store, result) => {
                  store.writeQuery({
                      query: SearchGiftVendorDocument,
                      data: {
                          searchGiftVendor: {
                              elems: [, ...giftVendorList!, result.data?.packet?.createGiftVendor]
                          }
                      }
                  })
              }
          })
      }}
  >
...

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

С удалением ситуация аналогична, только здесь обработчик не добавляет элемент, а фильтрует массив, исключая ранее удаленный элемент:

...
  <Button style={{ margin: "20px" }}
      key={elem.id ?? ""}
      onClick={(e) => {
          deleteGiftVendorMutation({
              variables: {
                  id: elem.id
              },
              update: (store) => {
                  store.writeQuery({
                      query: SearchGiftVendorDocument,
                      data: {
                          searchGiftVendor: {
                              elems: giftVendorList!.filter(x => x.id !== elem.id)
                          }
                      }
                  })
              }
          })
      }}>Delete gift vendor</Button>cond: "it.vendor.$id == '" + vendorId + "'"
...

Функциональность для заведения подарков в рамках конкретной компании-спонсора аналогична заведению самих компаний src/components/GiftList.tsx.
Вместо компонента Tabs используется компонент Table в самом простом его варианте.

Остановим наше внимание на двух моментах:

  • В хук useSearchGiftQuery передается через переменную cond соответствующего GraphQL-запроса условие фильтрации: "it.vendor.$id == '" + vendorId + "'". То есть запрашиваются подарки только конкретной компании-спонсора, на вкладке которой мы сейчас находимся.
    При формировании условий используется нативно понятный язык регулярных выражений, оперирующий структурой модели.
    В то же время доступен гибкий и мощный инструментарий составления разного рода условий выборки данных.
    Например:

    query searchGiftVendor{
    searchGiftVendor(cond: "it.name $like 'Sber%' && it.gifts.$exists"){
      elems{
        name
        lastChangeDate
        gifts(cond:"it.lastChangeDate < root.lastChangeDate.$addDays(1) || it.serialNumber.$substr(1,1) == '1'"){
          elems{
    
            serialNumber
          }
        }
      }
    }
    }

    Запрос всех спонсоров, начинающихся с лексемы 'Sber' и имеющих хотя бы один подарок. У таких компаний нам буду интересны только подарки, которые создавались/менялись в течение суток после создания компании-родителя. Детальное описание всех возможностей данного синтаксиса фильтрации: documentation/expressions.md.

  • Имеются возможности сортировки и постраничной вычитки запросов. Детали в документации: documentation/graphql.md.

Итак, мы написали приложение для фиксации компаний-спонсоров и выпускаемых ими подарков.
Если попробуете аналогичным образом добавить формы для фиксации серий (промоакций) и выдаваемых в рамках акций ваучеров (промокодов), то увидите, что это не займет много времени.
Необходимые сущности в модели и сервис для работы с ними у вас уже имеется.
Нужно лишь зафиксировать новые запросы по аналогии с запросами к спонсорам и их подаркам: src/graphql/requests.graphql.
А также отразить новые формы по аналогии с ранее рассмотренными компонентами: src/components/GiftVendorList.tsx и src/components/GiftList.tsx.

Часть 2

Вторая часть статьи:

  • знакомство с Functions;
  • использование сервисов Platform V при построении логики голосовых помощников.

В первой части мы познакомились с возможностями сервиса Platform V DataSpace: создали хранилище данных и сервис работы с этими данными через GraphQL-запросы, реализовали frontend-приложение для работы в роли администратора системы. Теперь нам необходимо опубликовать данное frontend-приложение, а также разработать и опубликовать ту часть сервиса (backend-приложение), которая будет предоставлять возможность резервировать подарки в роли «Клиента». Для решения этих задач нам понадобится другой сервис — Platform V Functions.

Знакомство с Functions

Платформенный сервис Functions позволяет реализовывать приложения в парадигме serverless, реализуя архитектурный шаблон Function-as-a-Services.

Материалы:

Публикация

Давайте вернемся к нашему ранее разработанному примеру Function 1 Frontend. Воспользуемся Platform V Functions для публикации разработанного frontend-приложения.
Здесь нам поможет файл конфигурации для промышленной сборки webpack.config.js.

Запустим сборку соответствующей командой:

npm run bundle

Результат сборки — директория static в папке fn-admin-frontend. Теперь нам достаточно только запаковать содержимое папки fn-admin-frontend в zip-архив и загрузить в заранее созданную функцию в SmartMarket.

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

После того, как функция загружена, необходимо ее опубликовать нажатием соответствующей копки в правом нижнем углу экрана.

После публикации на вкладке Детали доступен адрес этой функции в поле Синхронный endpoint:

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

Для отключения данной проверки на функции необходимо оставить запрос на снятие проверки ak/sk тут же в чате SmartMarket Studio, указав адрес вашей функции. Пример такого запроса: «Добрый день! Просьба отключить проверки ak/sk для функции:<наименование>».

После снятия ограничения по соответствующему адресу доступно приложение для ведения компаний-спонсоров (GiftVendor) и их подарков(Gift):

Аналогичным образом можно доработать текущий сервис или создать новый для заведения серий ваучеров (VoucherSerie) и самих ваучеров с промокодами (Voucher). Попробуйте сделать это сами.
Пока же можно просто добавить несколько серий и ваучеров непосредственно из «GraphQL конструктора» DataSpace.

Ниже пример создания двух серий и ваучеров в рамках них следующим GraphQL-запросом:

mutation createVoucherSeries{
  p1: packet(idempotencePacketId:"SerieOne"){
    createVoucherSerie(input:{
      code:"SerieOne"
    }){
      id
    }
    
    c001: createVoucher(input:{
      code:"PromoS1G1"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c002: createVoucher(input:{
      code:"PromoS1G2"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c003: createVoucher(input:{
      code:"PromoS1G3"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c004: createVoucher(input:{
      code:"PromoS1G4"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c005: createVoucher(input:{
      code:"PromoS1G5"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c006: createVoucher(input:{
      code:"PromoS1G6"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c007: createVoucher(input:{
      code:"PromoS1G7"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c008: createVoucher(input:{
      code:"PromoS1G8"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
  }
  
  p2: packet(idempotencePacketId:"SerieTwo"){
    createVoucherSerie(input:{
      code:"SerieTwo"
    }){
      id
    }
    
    c001: createVoucher(input:{
      code:"PromoS2G1"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c002: createVoucher(input:{
      code:"PromoS2G2"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c003: createVoucher(input:{
      code:"PromoS2G3"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c004: createVoucher(input:{
      code:"PromoS2G4"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c005: createVoucher(input:{
      code:"PromoS2G5"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c006: createVoucher(input:{
      code:"PromoS2G6"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c007: createVoucher(input:{
      code:"PromoS2G7"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
    
    c008: createVoucher(input:{
      code:"PromoS2G8"
      serie:"ref:createVoucherSerie"
    }){
      id
    }
  }
}

Function 2 Client Frontend/ Function 3 Client Backend

Итак, у нас теперь имеются подарки/ваучеры.
Осталась только часть приложения, которая будет обслуживать «Клиентов».
Реализуем данную функциональность в более традиционном варианте:

  • функция Function 2 Client Frontend будет реализовывать frontend-часть;
  • функция Function 3 Client Backend — backend-часть, работая напрямую с DataSpace как с хранилищем данных.

Начнем с серверной части — функции Function 3 Client Backend. При создании функции в SmartMarket укажем название, выберем язык JavaScript и обязательно выберем наш DataSpace, ранее созданный в этом пространстве.

Здесь представлен сам код функции: promo-action-client-back.

В отличие от первого примера Function 1 Admin Frontend, данная функция написана на чистом JavaScript. В файле promo-action-client-back/main/apolloClient.js также используется Apollo Client: формируются необходимые GraphQL-запросы, подписываемые при помощи ak/sk.
Обратите внимание, что адрес DataSpace + ak/sk мы получаем из соответствующих инфраструктурных переменных: DATASPACE_URL, APP_KEY, APP_SECRET. Система знает эти данные за счет того, что ранее при создании функций мы выбрали необходимый нам DataSpace. Данная функция обрабатывает HTTP-запрос с параметрами code (код ваучера, введенный «Клиентом») и kind (тип подарка, выбранный «Клиентом») promo-action-client-back/main/handler.js.

По аналогии с первой функцией запаковываем содержимое папки promo-action-client-back в архив, импортируем код в функцию и публикуем. После публикации нам доступен адрес функции в поле синхронный endpoint на вкладке Детали.

Так как данная функциональность будет вызываться из frontend-приложения нашими «Клиентами», необходимо снять проверку подписи через ak/sk. Для этого также через чат SmartMarket формируем запрос на снятие проверки.

Серверная часть (backend) «Клиентского» приложения у нас готова. Перейдем к frontend — функция Function 2 Client Frontend. Код функции: promo-action-client-front.

Нам необходимо внести незначительные изменения: в строке 6 файла promo-action-client-front/static/js/main.js указать адрес (синхронный endpoint) backend-функции, созданной нами шагом ранее.
Далее также сохраняем содержимое папки в архив, импортируем, публикуем, снимает проверку ak/sk для соответствующего endpoint.

Микросервис «Промоакция» готов! Теперь вы можете поделиться адресом функции Function 2 Client Frontend со своими «Клиентами». В браузере им будет доступна форма получения подарка:

«Клиент» должен ввести свой промокод, выбрать тип подарка и нажать кнопку Get a gift. При наличии подарка выбранного типа будет получено информационное сообщение с указанием компании-спонсора и серийный номер подарка.

На этом мы закончили разработку микросервиса.

Заметили ошибку?

Выделите текст и нажмите Ctrl + Enter, чтобы сообщить нам о ней