1. Обзор
AWS Lambda позволяет нам создавать легкие приложения, которые можно легко развертывать и масштабировать. Хотя мы можем использовать фреймворки , такие как функция Spring Cloud, по соображениям производительности мы обычно используем как можно меньше кода фреймворка.
Иногда нам нужно получить доступ к реляционной базе данных из Лямбды. Именно здесь Hibernate и JPA могут быть очень полезны. Но, как мы добавляем гибернацию в нашу Лямбду без весны?
В этом уроке мы рассмотрим проблемы использования любой СУБД в лямбда-системе, а также то, как и когда может быть полезен режим гибернации. В нашем примере будет использоваться модель бессерверного приложения для создания интерфейса REST для наших данных.
Мы рассмотрим, как протестировать все на вашем локальном компьютере с помощью Docker и AWS SAMCLI.
2. Проблемы с использованием СУБД и гибернацией в лямбдах
Лямбда – код должен быть как можно меньше, чтобы ускорить холодный запуск. Кроме того, лямбда-код должен уметь выполнять свою работу за миллисекунды. Однако использование реляционной базы данных может включать в себя много кода фреймворка и может выполняться медленнее.
В облачных приложениях мы стараемся разрабатывать с использованием облачных технологий. Бессерверные базы данных, такие как DynamoDB, могут лучше подходить для Лямбд. Однако потребность в реляционной базе данных может быть обусловлена каким-то другим приоритетом в рамках нашего проекта.
2.1. Использование СУБД Из Лямбда
Лямбды выполняются в течение небольшого промежутка времени, а затем их контейнер приостанавливается. Контейнер может быть повторно использован для будущего вызова, или он может быть удален средой выполнения AWS, если в нем больше нет необходимости. Это означает, что любыми ресурсами, на которые претендует контейнер, необходимо тщательно управлять в течение срока действия одного вызова .
В частности, мы не можем полагаться на обычный пул соединений для нашей базы данных, поскольку любые открытые соединения потенциально могут оставаться открытыми без безопасного удаления. Мы можем использовать пулы соединений во время вызова, но мы должны создавать пул соединений каждый раз . Кроме того, нам нужно отключить все соединения и освободить все ресурсы по завершении нашей функции.
Это означает, что использование лямбда-кода с базой данных может вызвать проблемы с подключением. Внезапное увеличение масштаба нашей Лямбды может потреблять слишком много соединений. Хотя Лямбда-код может сразу же освободить соединения, мы по-прежнему полагаемся на то, что база данных сможет подготовить их к следующему вызову Лямбда-кода. Поэтому часто рекомендуется использовать максимальное ограничение параллелизма для любой лямбды, использующей реляционную базу данных|/.
В некоторых проектах Lambda не является лучшим выбором для подключения к СУБД , а традиционная служба передачи данных Spring с пулом соединений , возможно, работающая в EC2 или ECS, может быть лучшим решением.
2.2. Случай гибернации
Хороший способ определить, нужен ли нам режим гибернации, – это спросить, какой код нам пришлось бы писать без него.
Если не использовать Hibernate, нам придется кодировать сложные соединения или множество стандартных сопоставлений между полями и столбцами, то с точки зрения кодирования Hibernate-хорошее решение. Если наше приложение не испытывает высокой нагрузки или необходимости в низкой задержке, то накладные расходы в режиме гибернации могут не быть проблемой.
2.3. Гибернация-это Тяжеловесная Технология
Однако нам также необходимо учитывать стоимость использования Hibernate в Лямбде.
Размер jar-файла Hibernate составляет 7 МБ. При запуске Hibernate требуется время для проверки аннотаций и создания возможности создания ФОРМЫ. Это чрезвычайно мощно, но для лямбды это может быть излишним. Поскольку лямбды обычно пишутся для выполнения небольших задач, накладные расходы Hibernate могут не оправдать преимуществ.
Возможно, будет проще использовать JDBC напрямую. В качестве альтернативы, легкая платформа, подобная ORM, такая как JDBI, может обеспечить хорошую абстракцию над запросами без слишком больших накладных расходов.
3. Пример Приложения
В этом уроке мы создадим приложение для отслеживания для малообъемной транспортной компании. Давайте представим, что они собирают крупные товары у клиентов, чтобы создать Консигнацию . Затем, куда бы ни направлялся этот груз, он регистрируется с отметкой времени, чтобы клиент мог следить за ним. Каждая партия груза имеет источник и пункт назначения , для которого мы будем использовать what3words.com как наш сервис геолокации.
Давайте также представим, что они используют мобильные устройства с плохими соединениями и повторными попытками. Таким образом, после создания отправления остальная информация о нем может поступить в любом заказе. Эта сложность, наряду с необходимостью двух списков для каждой партии – товаров и регистрации-является веской причиной для использования Hibernate.
3.1. Дизайн API
Мы создадим REST API со следующими методами:
- ОТПРАВИТЬ/отправку – создайте новую отправку, верните идентификатор и укажите источник и пункт назначения ; это должно быть сделано до любых других операций
- ОТПРАВКА/отправление/{идентификатор}/товар – добавление товара в отправку; всегда добавляется в конец списка
- ОТПРАВКА/отправление/{идентификатор}/регистрация – регистрация отправления в любом месте по пути, указав местоположение и отметку времени; всегда будет сохраняться в базе данных в порядке отметки времени
- ПОЛУЧИТЬ/груз/{идентификатор} – получить полную историю отправления, включая информацию о том, достигло ли оно пункта назначения
3.2. Лямбда-дизайн
Мы будем использовать одну лямбда-функцию, чтобы предоставить этому REST API модель бессерверного приложения для ее определения. Это означает, что наша единственная функция лямбда-обработчика должна быть в состоянии удовлетворить все вышеперечисленные запросы.
Чтобы сделать тестирование быстрым и простым, без накладных расходов на развертывание в AWS, мы протестируем все на наших машинах разработки.
4. Создание Лямбды
Давайте создадим новую Лямбду, чтобы удовлетворить наш API, но пока не реализуя его уровень доступа к данным.
4.1. Предварительные условия
Во-первых, нам нужно установить Docker , если у нас его еще нет. Он понадобится нам для размещения нашей тестовой базы данных, и он используется AWS SAMCLI для имитации лямбда-среды выполнения.
Мы можем проверить, есть ли у нас Докер:
$ docker --version Docker version 19.03.12, build 48a66213fe
Далее нам нужно установить AWS SAMCLI и затем протестировать его:
$ sam --version SAM CLI, version 1.1.0
Теперь мы готовы создать нашу Лямбду.
4.2. Создание ОДНОГО и ТОГО ЖЕ шаблона
Тот же интерфейс командной строки предоставляет нам способ создания новой лямбда-функции:
$ sam init
Это подскажет нам настройки нового проекта. Давайте выберем следующие варианты:
1 - AWS Quick Start Templates 13 - Java 8 1 - maven Project name - shipping-tracker 1 - Hello World Example: Maven
Мы должны отметить, что эти номера опций могут отличаться в более поздних версиях ОДНОГО и того же инструмента.
Теперь должен появиться новый каталог с именем shipping-tracker , в котором есть приложение-заглушка. Если мы посмотрим на содержимое его шаблона .yaml файл, мы найдем функцию под названием Функция Hello World с простым REST API:
Events: HelloWorld: Type: Api Properties: Path: /hello Method: get
По умолчанию это удовлетворяет базовому запросу GET на /привет . Мы должны быстро проверить, что все работает, используя sam для его сборки и тестирования:
$ sam build ... lots of maven output $ sam start-api
Затем мы можем протестировать hello world API с помощью curl :
$ curl localhost:3000/hello { "message": "hello world", "location": "192.168.1.1" }
После этого давайте остановим sam запуск своего прослушивателя API, используя CTRL+C для прерывания программы.
Теперь, когда у нас есть пустая лямбда Java 8, нам нужно настроить ее так, чтобы она стала нашим API.
4.3. Создание Нашего API
Чтобы создать наш API, нам нужно добавить наши собственные пути в раздел События шаблона .yaml файл:
CreateConsignment: Type: Api Properties: Path: /consignment Method: post AddItem: Type: Api Properties: Path: /consignment/{id}/item Method: post CheckIn: Type: Api Properties: Path: /consignment/{id}/checkin Method: post ViewConsignment: Type: Api Properties: Path: /consignment/{id} Method: get
Давайте также переименуем функцию, которую мы вызываем, из Функции Hello World в Функцию доставки :
Resources: ShippingFunction: Type: AWS::Serverless::Function
Затем мы переименуем каталог в Функция доставки и изменим пакет Java с hello world на com.baeldung.лямбда.доставка . Это означает, что нам нужно будет обновить Url кода и Обработчик свойств в шаблоне.yaml чтобы указать на новое местоположение:
Properties: CodeUri: ShippingFunction Handler: com.baeldung.lambda.shipping.App::handleRequest
Наконец, чтобы освободить место для вашей собственной реализации, давайте заменим тело обработчика:
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { Mapheaders = new HashMap<>(); headers.put("Content-Type", "application/json"); headers.put("X-Custom-Header", "application/json"); return new APIGatewayProxyResponseEvent() .withHeaders(headers) .withStatusCode(200) .withBody(input.getResource()); }
Хотя модульные тесты-хорошая идея, в этом примере мы также удалим предоставленные модульные тесты, удалив каталог src/test .
4.4. Тестирование пустого API
Теперь, когда мы все изменили и создали наш API и базовый обработчик, давайте еще раз проверим, все ли еще работает:
$ sam build ... maven output $ sam start-api
Давайте используем curl для проверки запроса HTTP GET:
$ curl localhost:3000/consignment/123 /consignment/{id}
Мы также можем использовать curl-d для ПУБЛИКАЦИИ:
$ curl -d '{"source":"data.orange.brings", "destination":"heave.wipes.clay"}' \ -H 'Content-Type: application/json' \ http://localhost:3000/consignment/ /consignment
Как мы видим, оба запроса успешно отправлены. Наш код-заглушка выводит ресурс – путь запроса, который мы можем использовать при настройке маршрутизации к различным методам обслуживания.
4.5. Создание конечных точек В Лямбда –
Мы используем одну лямбда-функцию для обработки наших четырех конечных точек. Мы могли бы создать другой класс обработчика для каждой конечной точки в одной и той же кодовой базе или написать отдельное приложение для каждой конечной точки, но объединение связанных API позволяет единому парку лямбда-выражений обслуживать их общим кодом, что может быть лучшим использованием ресурсов.
Однако нам нужно создать эквивалент контроллера REST, чтобы отправлять каждый запрос подходящей функции Java. Итак, мы создадим заглушку Служба доставки класс и направим к нему маршрут из обработчика:
public class ShippingService { public String createConsignment(Consignment consignment) { return UUID.randomUUID().toString(); } public void addItem(String consignmentId, Item item) { } public void checkIn(String consignmentId, Checkin checkin) { } public Consignment view(String consignmentId) { return new Consignment(); } }
Мы также создадим пустые классы для Отправления , Товара, и Проверки . Они скоро станут нашей моделью.
Теперь, когда у нас есть сервис, давайте использовать ресурс для маршрутизации к соответствующим методам обслуживания. Мы добавим оператор switch в наш обработчик для маршрутизации запросов в службу:
Object result = "OK"; ShippingService service = new ShippingService(); switch (input.getResource()) { case "/consignment": result = service.createConsignment( fromJson(input.getBody(), Consignment.class)); break; case "/consignment/{id}": result = service.view(input.getPathParameters().get("id")); break; case "/consignment/{id}/item": service.addItem(input.getPathParameters().get("id"), fromJson(input.getBody(), Item.class)); break; case "/consignment/{id}/checkin": service.checkIn(input.getPathParameters().get("id"), fromJson(input.getBody(), Checkin.class)); break; } return new APIGatewayProxyResponseEvent() .withHeaders(headers) .withStatusCode(200) .withBody(toJson(result));
Мы можем использовать Jackson для реализации наших функций FromJSON и toJSON .
4.6. Заглушенная Реализация
До сих пор мы научились создавать AWS Lambda для поддержки API, тестировать его с помощью sam и curl и создавать базовые функции маршрутизации в нашем обработчике. Мы могли бы добавить дополнительную обработку ошибок при неправильных вводах.
Следует отметить, что сопоставления в шаблоне .yaml уже ожидает, что шлюз API AWS будет фильтровать запросы, которые не соответствуют правильным путям в нашем API. Таким образом, нам нужно меньше обработки ошибок для плохих путей.
Теперь пришло время внедрить наш сервис с его базой данных, моделью сущностей и гибернацией.
5. Настройка базы данных
В этом примере мы будем использовать PostgreSQL в качестве СУБД. Любая реляционная база данных может работать.
5.1. Запуск PostgreSQL в Docker
Во-первых, мы вытащим изображение докера PostgreSQL:
$ docker pull postgres:latest ... docker output Status: Downloaded newer image for postgres:latest docker.io/library/postgres:latest
Давайте теперь создадим сеть докеров для запуска этой базы данных. Эта сеть позволит нашей Лямбде взаимодействовать с контейнером базы данных:
$ docker network create shipping
Затем нам нужно запустить контейнер базы данных в этой сети:
docker run --name postgres \ --network shipping \ -e POSTGRES_PASSWORD=password \ -d postgres:latest
С помощью –name, мы дали контейнеру имя postgres . С помощью –сети, мы добавили его в нашу сеть доставки докеров. Чтобы установить пароль для сервера, мы использовали переменную среды POSTGRES_PASSWORD , установленную с помощью переключателя -e|/.
Мы также использовали -d для запуска контейнера в фоновом режиме, а не для привязки нашей оболочки. PostgreSQL запустится через несколько секунд.
5.2. Добавление схемы
Нам понадобится новая схема для наших таблиц, поэтому давайте используем psql клиент внутри нашего контейнера PostgreSQL, чтобы добавить схему доставки :
$ docker exec -it postgres psql -U postgres psql (12.4 (Debian 12.4-1.pgdg100+1)) Type "help" for help. postgres=#
В этой оболочке мы создаем схему:
postgres=# create schema shipping; CREATE SCHEMA
Затем мы используем CTRL+D для выхода из оболочки.
Теперь у нас работает PostgreSQL, готовый к использованию нашей Лямбды.
6. Добавление нашей модели сущности и DAO
Теперь у нас есть база данных, давайте создадим нашу модель сущности и DAO. Хотя мы используем только одно соединение, давайте воспользуемся пулом соединений Hikari, чтобы посмотреть, как его можно настроить для лямбд, которым, возможно, потребуется запустить несколько подключений к базе данных за один вызов.
6.1. Добавление режима гибернации в проект
Мы добавим зависимости в ваш pom.xml как для гибернации , так и для Пул соединений Хикари . Мы также добавим драйвер PostgreSQL JDBC :
org.hibernate hibernate-core 5.4.21.Final org.hibernate hibernate-hikaricp 5.4.21.Final org.postgresql postgresql 42.2.16
6.2. Модель сущности
Давайте конкретизируем объекты сущностей. У Отправления есть список товаров и регистрации, а также его источник , пункт назначения и было ли оно еще доставлено (то есть, зарегистрировалось ли оно в конечном пункте назначения):
@Entity(name = "consignment") @Table(name = "consignment") public class Consignment { private String id; private String source; private String destination; private boolean isDelivered; private List items = new ArrayList<>(); private List checkins = new ArrayList<>(); // getters and setters }
Мы аннотировали класс как сущность и с именем таблицы. Мы также предоставим добытчиков и сеттеров. Давайте пометим геттеры именами столбцов:
@Id @Column(name = "consignment_id") public String getId() { return id; } @Column(name = "source") public String getSource() { return source; } @Column(name = "destination") public String getDestination() { return destination; } @Column(name = "delivered", columnDefinition = "boolean") public boolean isDelivered() { return isDelivered; }
Для наших списков мы будем использовать аннотацию @ElementCollection , чтобы сделать их упорядоченными списками в отдельных таблицах с отношением внешнего ключа к таблице консигнация :
@ElementCollection(fetch = EAGER) @CollectionTable(name = "consignment_item", joinColumns = @JoinColumn(name = "consignment_id")) @OrderColumn(name = "item_index") public List getItems() { return items; } @ElementCollection(fetch = EAGER) @CollectionTable(name = "consignment_checkin", joinColumns = @JoinColumn(name = "consignment_id")) @OrderColumn(name = "checkin_index") public List getCheckins() { return checkins; }
Вот где Hibernate начинает окупать себя, довольно легко выполняя работу по управлению коллекциями.
Элемент сущность более проста:
@Embeddable public class Item { private String location; private String description; private String timeStamp; @Column(name = "location") public String getLocation() { return location; } @Column(name = "description") public String getDescription() { return description; } @Column(name = "timestamp") public String getTimeStamp() { return timeStamp; } // ... setters omitted }
Он помечен как @Встраиваемый , чтобы он мог быть частью определения списка в родительском объекте.
Аналогично, мы определим Проверку :
@Embeddable public class Checkin { private String timeStamp; private String location; @Column(name = "timestamp") public String getTimeStamp() { return timeStamp; } @Column(name = "location") public String getLocation() { return location; } // ... setters omitted }
6.3. Создание DAO доставки
Наш класс Shipping Dao будет зависеть от передачи открытого сеанса гибернации /. Для этого потребуется Служба доставки для управления сеансом:
public void save(Session session, Consignment consignment) { Transaction transaction = session.beginTransaction(); session.save(consignment); transaction.commit(); } public Optionalfind(Session session, String id) { return Optional.ofNullable(session.get(Consignment.class, id)); }
Мы свяжемся с этим в нашей Службе доставки позже.
7. Жизненный Цикл Гибернации
До сих пор наша модель сущности и DAO сопоставимы с реализациями, отличными от Лямбда. Следующая задача-создать спящий режим SessionFactory в рамках жизненного цикла Лямбды.
7.1. Где находится База Данных?
Если мы собираемся получить доступ к базе данных из нашей Лямбды, то ее необходимо настроить. Давайте поместим URL-адрес JDBC и учетные данные базы данных в переменные среды в нашем шаблоне .yaml :
Environment: Variables: DB_URL: jdbc:postgresql://postgres/postgres DB_USER: postgres DB_PASSWORD: password
Эти переменные среды будут введены в среду выполнения Java. Пользователь postgres является пользователем по умолчанию для нашего контейнера Docker PostgreSQL. Мы назначили пароль как пароль , когда запускали контейнер ранее.
В DB_URL у нас есть имя сервера – /postgres/| – это имя, которое мы дали нашему контейнеру, а имя базы данных postgres является базой данных по умолчанию.
Стоит отметить, что, хотя в этом примере мы жестко кодируем эти значения, шаблоны SAM позволяют нам объявлять входные данные и переопределения параметров. Следовательно, позже их можно будет сделать параметризуемыми.
7.2. Создание фабрики сеансов
У нас есть как спящий режим, так и Хикари пул подключений для настройки. Чтобы предоставить настройки для перехода в спящий режим, мы добавляем их в Карту :
Mapsettings = new HashMap<>(); settings.put(URL, System.getenv("DB_URL")); settings.put(DIALECT, "org.hibernate.dialect.PostgreSQLDialect"); settings.put(DEFAULT_SCHEMA, "shipping"); settings.put(DRIVER, "org.postgresql.Driver"); settings.put(USER, System.getenv("DB_USER")); settings.put(PASS, System.getenv("DB_PASSWORD")); settings.put("hibernate.hikari.connectionTimeout", "20000"); settings.put("hibernate.hikari.minimumIdle", "1"); settings.put("hibernate.hikari.maximumPoolSize", "2"); settings.put("hibernate.hikari.idleTimeout", "30000"); settings.put(HBM2DDL_AUTO, "create-only"); settings.put(HBM2DDL_DATABASE_ACTION, "create");
Здесь мы используем System.getenv для извлечения параметров среды выполнения из среды. Мы добавили настройки HBM2DDL_ , чтобы наше приложение создавало таблицы базы данных . Однако мы должны закомментировать или удалить эти строки после создания схемы базы данных и не должны позволять нашей Лямбде делать это в рабочей среде. Однако сейчас это полезно для нашего тестирования.
Как мы видим, многие настройки имеют константы, уже определенные в классе Доступные настройки в режиме гибернации, в отличие от специфичных для Хикари.
Теперь, когда у нас есть настройки, нам нужно создать SessionFactory . Мы индивидуально добавим в него наши классы сущностей:
StandardServiceRegistry registry = new StandardServiceRegistryBuilder() .applySettings(settings) .build(); return new MetadataSources(registry) .addAnnotatedClass(Consignment.class) .addAnnotatedClass(Item.class) .addAnnotatedClass(Checkin.class) .buildMetadata() .buildSessionFactory();
7.3. Управление Ресурсами
При запуске Hibernate выполняет генерацию кода вокруг объектов сущности. Приложение не предназначено для выполнения этого действия более одного раза, и для этого требуется время и память. Итак, мы хотим сделать это один раз при холодном запуске нашей Лямбды.
Поэтому мы должны создать SessionFactory , поскольку наш объект обработчика создается лямбда-фреймворком. Мы можем сделать это в списке инициализаторов класса обработчика:
private SessionFactory sessionFactory = createSessionFactory();
Однако, поскольку наш SessionFactory имеет пул соединений, существует риск того, что он будет удерживать соединения открытыми между вызовами, связывая ресурсы базы данных.
Хуже того, нет события жизненного цикла, которое позволяло бы Лямбде закрывать ресурсы, если они удаляются во время выполнения AWS . Таким образом, есть вероятность, что соединение, удерживаемое таким образом, никогда не будет должным образом освобождено.
Мы можем решить эту проблему, покопавшись в SessionFactory для нашего пула соединений и явно закрыв все соединения:
private void flushConnectionPool() { ConnectionProvider connectionProvider = sessionFactory.getSessionFactoryOptions() .getServiceRegistry() .getService(ConnectionProvider.class); HikariDataSource hikariDataSource = connectionProvider.unwrap(HikariDataSource.class); hikariDataSource.getHikariPoolMXBean().softEvictConnections(); }
Это работает в данном случае, потому что мы указали пул соединений Hikari, который предоставляет softEvictConnections , чтобы позволить нам освободить его соединения.
Мы должны отметить, что метод SessionFactory ‘s close также закроет соединения, но также сделает SessionFactory непригодным для использования.
7.4. Добавить в обработчик
Теперь нам нужно убедиться, что обработчик использует фабрику сеансов и освобождает свои соединения . Имея это в виду, давайте извлекем большую часть функций контроллера в метод, называемый запрос маршрута , и изменим ваш обработчик, чтобы освободить ресурсы в , наконец, блоке:
try { ShippingService service = new ShippingService(sessionFactory, new ShippingDao()); return routeRequest(input, service); } finally { flushConnectionPool(); }
Мы также изменили наш Доставка Сервис , чтобы иметь SessionFactory и Данные о доставке в качестве свойств, введенных через конструктор, но он их еще не использует.
7.5. Тестирование режима гибернации
На данный момент, хотя служба Доставки ничего не делает, вызов Лямбда-кода должен привести к запуску Hibernate и генерации DDL.
Давайте дважды проверим DDL, который он генерирует, прежде чем мы прокомментируем настройки для этого:
$ sam build $ sam local start-api --docker-network shipping
Мы создаем приложение, как и раньше, но теперь мы добавляем параметр –docker-network в sam local . Это запускает тестовую Лямбду в той же сети, что и наша база данных , чтобы Лямбда могла связаться с контейнером базы данных, используя его имя контейнера.
Когда мы впервые попадем в конечную точку с помощью curl , наши таблицы должны быть созданы:
$ curl localhost:3000/consignment/123 {"id":null,"source":null,"destination":null,"items":[],"checkins":[],"delivered":false}
Код заглушки по-прежнему возвращал пустой Груз . Но давайте теперь проверим базу данных, чтобы узнать, были ли созданы таблицы:
$ docker exec -it postgres pg_dump -s -U postgres ... DDL output CREATE TABLE shipping.consignment_item ( consignment_id character varying(255) NOT NULL, ...
Как только мы будем довольны тем, что наша настройка гибернации работает, мы сможем прокомментировать настройки HBM2DDL_ .
8. Заполните бизнес-логику
Все, что остается, – это сделать Службу доставки использовать Доставку Айдахо для реализации бизнес-логики. Каждый метод создаст sessionfactory в блоке try-with-resources , чтобы убедиться, что он будет закрыт.
8.1. Создайте Консигнацию
Новая партия не была доставлена и должна получить новое удостоверение личности. Тогда мы должны сохранить его в базе данных:
public String createConsignment(Consignment consignment) { try (Session session = sessionFactory.openSession()) { consignment.setDelivered(false); consignment.setId(UUID.randomUUID().toString()); shippingDao.save(session, consignment); return consignment.getId(); } }
8.2. Просмотр Отправления
Чтобы получить груз, нам нужно прочитать его из базы данных по идентификатору. Хотя API REST должен возвращать Не найден по неизвестному запросу, в этом примере мы просто вернем пустой груз, если он не найден:
public Consignment view(String consignmentId) { try (Session session = sessionFactory.openSession()) { return shippingDao.find(session, consignmentId) .orElseGet(Consignment::new); } }
8.3. Добавить Элемент
Товары войдут в наш список товаров в полученном заказе:
public void addItem(String consignmentId, Item item) { try (Session session = sessionFactory.openSession()) { shippingDao.find(session, consignmentId) .ifPresent(consignment -> addItem(session, consignment, item)); } } private void addItem(Session session, Consignment consignment, Item item) { consignment.getItems() .add(item); shippingDao.save(session, consignment); }
В идеале у нас была бы лучшая обработка ошибок, если бы груз не существовал, но в данном примере несуществующие грузы будут игнорироваться.
8.4. Регистрация заезда
Проверки должны быть отсортированы в порядке их возникновения, а не при получении запроса. Кроме того, когда товар достигает конечного пункта назначения, он должен быть помечен как доставленный:
public void checkIn(String consignmentId, Checkin checkin) { try (Session session = sessionFactory.openSession()) { shippingDao.find(session, consignmentId) .ifPresent(consignment -> checkIn(session, consignment, checkin)); } } private void checkIn(Session session, Consignment consignment, Checkin checkin) { consignment.getCheckins().add(checkin); consignment.getCheckins().sort(Comparator.comparing(Checkin::getTimeStamp)); if (checkin.getLocation().equals(consignment.getDestination())) { consignment.setDelivered(true); } shippingDao.save(session, consignment); }
9. Тестирование приложения
Давайте смоделируем посылку, направляющуюся из Белого дома в Эмпайр-Стейт-билдинг.
Агент создает путешествие:
$ curl -d '{"source":"data.orange.brings", "destination":"heave.wipes.clay"}' \ -H 'Content-Type: application/json' \ http://localhost:3000/consignment/ "3dd0f0e4-fc4a-46b4-8dae-a57d47df5207"
Теперь у нас есть идентификатор 3dd0f0e4-fc4a-46b4-8dae-a57d47df5207 для отправления. Затем кто – то собирает два предмета для отправки- картину и пианино:
$ curl -d '{"location":"data.orange.brings", "timeStamp":"20200101T120000", "description":"picture"}' \ -H 'Content-Type: application/json' \ http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207/item "OK" $ curl -d '{"location":"data.orange.brings", "timeStamp":"20200101T120001", "description":"piano"}' \ -H 'Content-Type: application/json' \ http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207/item "OK"
Через некоторое время состоится регистрация заезда:
$ curl -d '{"location":"united.alarm.raves", "timeStamp":"20200101T173301"}' \ -H 'Content-Type: application/json' \ http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207/checkin "OK"
И еще раз позже:
$ curl -d '{"location":"wink.sour.chasing", "timeStamp":"20200101T191202"}' \ -H 'Content-Type: application/json' \ http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207/checkin "OK"
На этом этапе клиент запрашивает статус отправления:
$ curl http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207 { "id":"3dd0f0e4-fc4a-46b4-8dae-a57d47df5207", "source":"data.orange.brings", "destination":"heave.wipes.clay", "items":[ {"location":"data.orange.brings","description":"picture","timeStamp":"20200101T120000"}, {"location":"data.orange.brings","description":"piano","timeStamp":"20200101T120001"} ], "checkins":[ {"timeStamp":"20200101T173301","location":"united.alarm.raves"}, {"timeStamp":"20200101T191202","location":"wink.sour.chasing"} ], "delivered":false }%
Они видят прогресс, но он еще не достигнут.
Сообщение должно было быть отправлено в 20:12, чтобы сообщить, что оно достигло отклонения.famed.apple , но он задерживается, и сообщение от 21:46 в пункте назначения приходит первым:
$ curl -d '{"location":"heave.wipes.clay", "timeStamp":"20200101T214622"}' \ -H 'Content-Type: application/json' \ http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207/checkin "OK"
На этом этапе клиент запрашивает статус отправления:
$ curl http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207 { "id":"3dd0f0e4-fc4a-46b4-8dae-a57d47df5207", ... {"timeStamp":"20200101T191202","location":"wink.sour.chasing"}, {"timeStamp":"20200101T214622","location":"heave.wipes.clay"} ], "delivered":true }
Теперь это доставлено. Итак, когда сообщение с задержкой пройдет:
$ curl -d '{"location":"deflection.famed.apple", "timeStamp":"20200101T201254"}' \ -H 'Content-Type: application/json' \ http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207/checkin "OK" $ curl http://localhost:3000/consignment/3dd0f0e4-fc4a-46b4-8dae-a57d47df5207 { "id":"3dd0f0e4-fc4a-46b4-8dae-a57d47df5207", ... {"timeStamp":"20200101T191202","location":"wink.sour.chasing"}, {"timeStamp":"20200101T201254","location":"deflection.famed.apple"}, {"timeStamp":"20200101T214622","location":"heave.wipes.clay"} ], "delivered":true }
Регистрация производится в нужном месте на временной шкале.
10. Заключение
В этой статье мы обсудили проблемы использования тяжелой платформы, такой как Hibernate, в легком контейнере, таком как AWS Lambda.
Мы создали API Lambda и REST и узнали, как протестировать его на вашем локальном компьютере с помощью Docker и AWS SAMCLI. Затем мы построили модель сущностей для Hibernate для использования с нашей базой данных. Мы также использовали Hibernate для инициализации наших таблиц.
Наконец, мы интегрировали Hibernate SessionFactory в наше приложение, обеспечив его закрытие до выхода Лямбды.
Как обычно, пример кода для этой статьи можно найти на GitHub .