Привет, Хабр! Продолжаем рассказывать, как быстро и просто создавать микросервисные приложения. В прошлой статье мы написали frontend с помощью Platform V DataSpace. В примере был использован TypeScript, но, как мы и говорили, это необязательное требование. Теперь рассмотрим, как разрабатывать backend-приложения на языке Java с помощью сервиса Platform V Functions и инструмента DataSpace SDK. Platform V Functions — это FaaS-решение, позволяющее загружать исходный код сервиса в виде функции в OpenShift/k8s без создания docker-образов и настройки окружения. Но основное внимание в статье уделим даже не Functions, а DataSpace SDK. Это инструмент для удобного взаимодействия с DataSpace по протоколу JSON-RPC. По ходу статьи мы рассмотрим основные фичи, которые DataSpace SDK предоставляет Java-разработчику. В качестве примера снова возьмём приложение «Промоакция» из предыдущей статьи. Изменим архитектуру нашего приложения, разбив его на микросервисы. Теперь промокоды и подарки будут вестись раздельно разными сервисами, у каждого из которых будет свой DataSpace со своей моделью данных. Архитектура приложения на этот раз будет выглядеть вот так: Function 1 Vouchers — backend-сервис, отвечающий за ведение промокодов. Function 2 Gifts — backend-сервис, отвечающий за ведение подарков. Function 3 Report — backend-сервис, предоставляющий различные аналитические отчёты о подарках. Представим, что разработкой данного приложения занимаются два разработчика: разработчик Vouchers реализует часть приложения, которая связана с управлением промокодами; разработчик Gifts реализует часть приложения, которая связана с управлением подарками. Для начала работы каждому разработчику нужно развернуть сервис DataSpace в своем пространстве в SmartMarket Studio. Подробнее о том, как это сделать, мы рассказывали здесь, в разделе «Как начать работу» в SmartMarket Studio. У каждого DataSpace будет своя модель данных: vouchers_model.xml — модель для DataSpace Vouchers; gifts_model.xml — модель для DataSpace Gifts. Voucher и Gift теперь имеют связь OneToOne. Но тип этой связи «из внешней системы», так как они находятся в разных моделях данных. Итак, сервисы DataSpace развёрнуты. Теперь создадим заготовки для наших сервисов. Разработчик Vouchers создаёт в своём пространстве соответствующую функцию: Разработчик Gifts создаёт в своём пространстве функции Gifts Function, Reports Function: Теперь разработаем «начинки» для функций — это будут хорошо известные всем Spring Boot приложения. Переходим на вкладку «Детали» и скачиваем инструмент DataSpace SDK — он был сгенерирован после развёртывания сервиса DataSpace Vouchers. Создадим проект со стандартной структурой. Для удобства можно взять за основу шаблонный проект в одной из наших функций-заготовок. Для этого в действиях выбираем пункт «Экспортировать»: При этом добавим в src/libs jar, полученный из скачанного ранее архива.  Также нам потребуется java-sdk-core для подписи REST-запросов при помощи ak/sk. Скачиваем его по ссылке, достаём из архива и добавляем в src/libs нашего проекта. В pom.xml проекта необходимо добавить следующие зависимости: Сервис Vouchers будет предоставлять REST API, который принимает на вход промокод и тип подарка. В ответ он отдаёт сообщение с информацией о результате бронирования подарка. Определим API в нашем контроллере: Перейдём к конфигурации. Определим инстансы DataspaceCorePacketClient и DataspaceCoreSearchClient. Нам понадобится адрес сервиса DataSpace и ak/sk для авторизации на API gateway. Все эти значения мы получаем из соответствующих инфраструктурных переменных DATASPACE_URL, APP_KEY, APP_SECRET. Также нам потребуется RestTemplate для осуществления вызовов к сервису Gifts: Также нам понадобятся: адрес проекта; appKey; appSecret. Найти эти значения можно в настройках проекта: В конфигурационном файле config.yaml определим необходимые настройки: Перейдём к реализации VoucherService. Алгоритм заказа подарка по промокоду будет выглядеть так: Запрос клиента поступает с фронта в сервис Vouchers, который выполняет валидацию промокода. Если валидация прошла успешно, сервис Vouchers вызывает сервис Gifts по REST. Сервис Gifts должен найти подходящий подарок и забронировать его либо вернуть ответ, содержащий информацию о том, что доступные подарки отсутствуют. Сервис Vouchers получает идентификатор подарка, привязывает его к промокоду и отправляет ответ с серийным номером подарка и наименованием компании клиенту. Если подарок не был найден, сервис отправляет соответствующий ответ клиенту: В основном методе getGiftsByPromoCode нам необходимо проверить, что переданный промокод валиден. Для этого нужно убедиться, что промокод с таким кодом присутствует в базе данных, и он не был использован ранее. Рассмотрим метод verifyPromoCode: Метод DataspaceCoreSearchClient#getVoucher из состава DataSpace SDK позволяет построить в типизированном формате запрос к сервису DataSpace Vouchers. В лямбда-выражении мы указываем спецификацию с набором полей, которые хотим получить в ответе, а также задаём условие поиска. Get-метод предполагает возникновение ObjectNotFoundException в случае, если по запросу ничего не нашлось. Далее нужно убедиться, что у запрашиваемого промокода нет ссылки на уже полученный подарок, а статус — «ОТКРЫТ». В противном случае отправляем сообщение о том, что данный промокод уже был использован. Мы провели валидацию промокода и получили его идентификатор, теперь нужно забронировать подходящий подарок. В методе getGift вызовем сервис Gifts по REST. При этом подпишем наш запрос при помощи ключей ak/sk для корректной авторизации на ApiGateway: В ответ получаем ошибку, которую пробрасываем на фронт, или атрибуты забронированного подарка. Если мы получили положительный ответ от Gifts, нужно отметить, что обрабатываемый промокод использован и за ним закреплён подарок. Рассмотрим метод updateVoucher: Метод DataspaceCorePacketClient#execute оперирует объектами типа Packet. Packet является реализацией паттерна UnitOfWork. Все команды, содержащиеся в рамках одного Packet, выполняются в одной транзакции на стороне сервиса DataSpace. Создаём объект Packet. При этом задаём параметр idempotencePacketId — таким образом мы наделяем Packet свойством идемпотентности. IdempotencePacketId выступает ключом идемпотентности. Это означает, что на все последующие вызовы Packet c таким же ключом DataSpace вернёт результат, который был получен при первом успешном вызове. При этом сами операции изменения состояния БД выполнены не будут. В качестве ключа идемпотентности используем идентификатор сущности Voucher. Добавляем в Packet команду update сущности Voucher. При этом указываем идентификатор сущности, а также значения полей, которые нужно установить. Вызываем метод DataspaceCorePacketClient#execute, чтобы отправить запрос в DataSpace. В методе getGiftByPromoCode отправляем на фронт сообщение о полученном подарке или ошибку. Скачиваем jar с DataSpace SDK, но на этот раз из сервиса DataSpace Gifts: Создаём проект, подключаем зависимости точно так же, как и в случае с сервисом Vouchers: Сервис Gift будет предоставлять REST API, который принимает на вход идентификатор промокода и тип подарка. В ответ он отдаёт JSON, в котором содержится информация о забронированном подарке или ошибка. Определим API в нашем контроллере: Определим инстансы DataspaceCorePacketClient и DataspaceCoreSearchClient. Получаем необходимые параметры из соответствующих инфраструктурных переменных DATASPACE_URL, APP_KEY, APP_SECRET. Перейдём к реализации GiftsService. Рассмотрим основной метод GiftsService#getGift: Разберём его детально.  В сервисе Gifts помимо самих подарков и компаний ведётся сущность GiftRequestCounter, которая хранит количество поступивших запросов для каждого типа подарка. Предполагается, что она будет использована в аналитических отчётах: В методе updateRequestCount мы отправляем асинхронно запрос на увеличение счётчика GiftRequestCounter в сервис DataSpace Gifts. Создаём Packet с ключом идемпотентности, состоящим из идентификатора промокода и типа подарка, чтобы избежать лишнего накручивания счётчика при ретраях. В Packet добавляем команду UpdateOrCreate. Эта команда позволяет за один вызов проверить наличие сущности в БД и обновить её, а если сущности нет, то создать. Также мы добавляем команду update с установленным параметром на увеличение счётчика. Затем отправляем запрос асинхронно при помощи метода DataspaceCorePacketClient#executeAsync. Далее в основном методе сервиса getGift производим поиск доступного подарка, используя метод DataspaceCoreSearchClient#searchGift: Если доступные подарки не были найдены, формируем ответ с ошибкой: Если подходящие подарки нашлись, нам необходимо забронировать один из них, установив ссылку на соответствующий ваучер. Снова воспользуемся функционалом DataspaceCorePacketClient. Создадим Packet и добавим в него команду на обновление сущности Gift. Обратим внимание, что данный запрос мы выполняем идемпотентно, используя при этом в качестве ключа идентификатор подарка. Данный подход позволяет нам не допустить ситуацию, в которой один и тот же подарок будет забронирован для нескольких разных ваучеров, а также избежать выполнения лишних операций при ретраях: Перейдём к реализации сервиса, который предоставляет API для получения отчётов. Данный сервис будет предоставлять отчёты о подарках, поэтому нам потребуется jar DataSpace SDK из сервиса DataSpace Gifts. Создадим проект, добавим необходимую зависимость: Реализуем API получения следующего отчёта: Компания | тип подарка | кол-во подарков: Рассмотрим реализацию основного метода ReportService#getGiftsReport с применением DataSpace SDK: Конструкция SelectionWith позволяет построить запрос с группировками. Метод $withGroup первым параметром принимает алиас поля, который будет отображён в результирующей выборке. Вторым параметром $withGroup принимает groupSelector, который позволяет указать выражение, на основе которого будут получены данные, будь то значение поля как оно есть или агрегирующая функция. При помощи метода $addGroupBy мы добавляем поля, по которым будет выполнена группировка. После формирования объекта SelectionWith выполняем вызов DataspaceCoreSearchClient#selectionSearch. Формируем JSON-ответ. Метод Selection#$getCalculated позволяет получить данные из объекта Selection, а также привести их к требуемому типу данных. Приложения готовы, теперь необходимо упаковать каждое в zip-архив и загрузить в соответствующую функцию в SmartMarket Studio: vouchers.zip gifts.zip report.zip Затем жмём кнопку «Опубликовать» и ждём, пока функции задеплоятся. После успешного деплоя на вкладке «Тестирование» мы можем проверить работоспособность наших API: С помощью Platform V Functions и DataSpace SDK мы создали и развернули два полноценных микросервиса: Подарки: a) Ведение компаний-спонсоров и их подарков.  b)  Аналитический учёт пользовательских запросов.  ·Промоакции: a)  Ведение промоакций и ваучеров в рамках сервиса.  b) Резервирование подарков в рамках промоакций (интеграция с сервисом «Подарки»). В следующих статьях подробнее раскроем фичи и возможности Platform V Functions и расскажем, как ещё можно сократить время на разработку и реализовать микросервисный подход, используя инструменты Platform V.