Створюємо заглушки сервісів для інтеграційного тестування наApache Camel (з використанням Scala

Часто виникає необхідність емулювати роботу якоїсь частини системи для інтеграційного тестування, зробити заглушку або написати простий компонент інтеграції. Це може бути веб-сервіс, що повертає відповіді, тест, що наповнює базу даних, додаток, який зчитує повідомлення з черги і повертає результат обробки, генератор файлів та інші компоненти.
Для разової перевірки інтеграції ми б використовували простий Java або Scala додаток, сценарій Apache JMeter або SoapUI. Але нам потрібна система, яка постійно працює, відповідає на запити та не вимагає дій з боку тестувальника — запустив та забув. Для вирішення такого завдання ми можемо створити програму, засновану на фреймворку Apache Сamel. Розглянемо 5 прикладів:
- Читання файлів в одному кодуванні, запис в іншому;
- Запит до веб-сервісу з розкладу та збереження повідомлення у сховищі даних;
- Реалізація веб-сервісу, який повертає повідомлення залежно від GET запиту;
- Читання повідомлення з черги та відправлення повідомлення до БД;
- Приклад маршрутизації за вмістом файлу.
Коротко опишемо інструменти, які використовуються для вирішення задачі. Apache camel (http://camel.apache.org/) - Java фреймворк, призначений для реалізації обміну повідомленнями між окремими програмами, підсистемами інформаційної системи. Реалізує підхід до розробки сполучного програмного забезпечення Enterprise Integration Patterns (EIP). Дозволяє працювати з файлами, БД, менеджерами черг, веб-сервісами та іншими компонентами — їх понад 240 видів на сторінці проекту component. Додаток Camelописуються так звані endpoints - кінцеві точки, і правила перетворення та маршрутизації повідомлень між ними.
Компонент Camel реалізує кінцеву точку. Це або виробник повідомлення (Producer) або споживач (Consumer). Деякі компоненти можуть реалізовувати обидва види точок, наприклад, з файлу можна отримати повідомлення та записати. Деякі компоненти реалізують тільки виробника повідомлення, наприклад таймер, або споживача, наприклад, виведення в лог.
У ході роботи програми відбувається маніпулювання тілом повідомлення та його заголовками. Порядок роботи з Camel наступний:
- Описуємо джерело повідомлення (файл, черга, БД, сервіс, таймер тощо);
- Описуємо правила перетворення даних та форматів;
- Описуємо одержувача (одержувачів) повідомлення (файл, черга, БД, сервіс, виведення в консоль тощо) та логіку маршрутизації;
- Запускаємо програму, яка слухає джерело, і при появі повідомлення перетворює його та маршрутизує до одержувачів.
Для опису правил маршрутизації та перетворення повідомлень використовуються різні мови languages. Для себе ми вибрали Scala DSL scala-dsl-eip, тому що ця мова добре підходить для простого та швидкого створення компонентного програмного забезпечення. Для Scala використовуємо систему складання проекту SBT.
Існує чудовий приклад з читанням повідомлення з файлу та надсиланням його http post запитом. Він трохи застарілий, але може бути корисним.
Підготовчі роботи Створимо проект в idea на основі SBT. Приклад створення проекту можна підглянути - Реалізація моніторингу та інтеграційного тестування інформаційної системи за допомогою Scalatest. Частина1 У файлі build.sbt пропишемо налаштування
Додамо файл src/main/resourcesфайл logback.xml, в якому налаштовано рівень логування та формат повідомлення.
Інакше за замовчуванням буде рівень DEBUG – виводиться дуже багато інформації.
Приклад 1 Читання файлів в одному кодуванні, запис в іншому. Це проста програма, яка використовує компонент http://camel.apache.org/file2.html з пакету camel-core. Воно складається з об'єкта, що запускає програму FromFileToFileApp і класу FromFileToFileRoute, де описані маршрути. Клас із маршрутами можна винести в окремий файл.
Вміст файлу src/main/scala/FromFileToFileApp.scala
Приклад 2 Запит до веб-сервісу з розкладу та збереження повідомлення у сховищі даних. У цьому прикладі за таймером збиратимемо дані про курс валют і відправлятимемо в Redis. Для того, щоб випонити дії над повідомленням (записати тіло та заголовки), існує метод "process". Для Redis надсилання значень здійснюється за допомогою пари заголовків "CamelRedis.Key"/"CamelRedis.Value". Нам необхідно витягти тіло повідомлення, яке повертає запит HTTP GET і зробити його заголовком "CamelRedis.Value".
Ключ генеруватимемо унікальний, що підходить для сортування — поточний час у мілісекундах.
Щоб писати Redis з віддаленого хоста, може знадобитися дозвіл. Наприклад, у консолі Redis на хості, де його запущено, виконати команду
Приклад відображення записів Redis представлений на малюнку.

Приклад 3 Реалізація веб-сервісу, який повертає повідомлення залежно від GET запиту. У цьому прикладі за допомогою компонента Jetty реалізуємо простий HTTP сервер, який отримує GET запит з параметром і повертає xml зі значенням параметра або помилкою.
Приклади відповідей сервісу представлені намалюнку

Приклад 4 Читання повідомлення з черги та запис у БД. Робота з чергами та БД була виділена в окремий приклад. Налаштування цих компонентів потребує іншого підходу. Якщо в попередніх прикладах налаштування проводилося за допомогою параметрів у рядку endpoint, то тут потрібно заздалегідь створити об'єкт, зробити на його основі компонент та використовувати далі.
Для БД створюємо екземпляр класу org.apache.commons.dbcp2.BasicDataSource та передаємо йому параметри підключення. Для черги створюємо екземпляр класу javax.jms.ConnectionFactory, у якому зберігаємо параметри підключення. Далі для цих компонентів створюється ім'я кінцевої точки, і використовується в URI. Різниця в тому, що для БД використовується компонент camel-jdbc, а для черг створюється новий компонент на основі camel-jms.
Таблиця, в яку відбувається вставка запису в прикладах, створюється таким запитом:
Наступний код забиратиме повідомлення з черги, виконуватиме в БД запит на додавання унікального ідентифікатора, часу та тіла повідомлення.
Слід пам'ятати про те, що при спробі запису БД повідомлення більше довжини поля (у таблиці, створеної запитом раніше, довжина поля - 65536 символів) - виникне помилка. Її можна вирішити, обрізаючи тіло до потрібного розміру, або додавши errorHandler(deadLetterChannel("file:error")), який надсилатиме повідомлення, що призводять до помилок, до папки "error".
У прикладі розглянуто взаємодію з базою даних H2. Для інших баз даних потрібно додати відповідну бібліотеку в build.sbt, визначити ім'я класу драйвера, URL. Можуть знадобитися інші властивості підключення, наприклад, ім'я користувача та пароль.
Приклад опису реквізитів підключення для роботи з Postgresql:
Додавання бібліотеки доbuild.sbt
Реалізація у класі:
Із чергами дещо складніше. Для деяких з менеджерів черг бібліотеки не відкриті для доступу в репозиторіях. У цьому випадку використовуються файли *.jar, які зберігаються в папці lib проекту.
Для будь-якого менеджера черг необхідно створити відповідний об'єкт типу connection factory. Наприклад, код, що забезпечує взаємодію з IBM Websphere MQ, буде таким:
а endpoint URI буде такого формату: "ora-jms:queue:./TestJMSModule!TestJMSQueue", де ./ позначає поточний сервер, "TestJMSModule" JNDI ім'я модуля "TestJMSQueue" - JNDI ім'я черги
Приклад 5 Маршрутизація вмісту файлу. У цьому прикладі розглянемо маршрутизацію повідомлення в залежності від його вмісту.
Припустимо, що на вході є XML-повідомлення, обробка якого залежить від значення елемента "То".
ActiveMQ - потрібно відправити в чергу, а H2 - обробити якимось чином і відправити до БД, someAdress - обробити ще якимось чином.
У повідомлення буде додано заголовок "Destination" з ім'ям кінцевої точки, до якої потрібно буде надіслати повідомлення.
Якщо виникне помилка при обробці повідомлення або таблиці маршрутизації не буде відповідного значення, то відправляємо повідомлення "direct:trash".
У прикладі використовується конструкція скеля ".", яка дозволяє замінити неіснуючий блок коду для успішної компіляції. Натомість блоку потрібно написати логіку обробки.
Приклади показують, як можна реалізувати невелику програму для наших цілей. Розглянемо додаткові нюанси. що дозволяють зробити розробку та обслуговування програми зручніше.
Для конфігурації програми використовуємо бібліотеку від Typesafe, щоб не зашивати параметри підключення в коді,а зберігати у конфігураційному файлі.
У build.sbt додаємо:
у папці src/main/resources створюємо файл application.conf, в якому прописуємо налаштування та викликаємо їх із коду.
Запуск програми виконується командою sbt run. У деяких випадках це може бути незручно. Можливо створення jar-файлу за допомогою плагіна sbt-assembly https://github.com/sbt/sbt-assembly для запуску командою java-jar camelapp.jar. У .jar-файлі будуть утримуватися всі залежності, тому розмір буде великий, але запуск відбувається відразу, без завантаження компонентів.
Для запуску у фоні зручно використовувати програму nohup.
Створюємо скрипт для запуску в папці, яка входить до змінного середовища $PATH, щоб викликати на ім'я з будь-якої директорії. Наприклад /usr/local/bin/. Скрипт для запуску:
Для зупинки: /usr/local/bin/camelstop
Запуск програми робиться командою camelstart, зупинка - camelstop.
Можемо виділити деякі плюси та мінуси використання Apache Camel. Плюси:
- Швидка реалізація програм;
- Велика кількість готових компонентів;
- Багатопотоковість, паралельна обробка повідомлень із коробки;
- Можливість вибору способу опису у вигляді XML або одного із DSL;
- Кожен компонент має свою логіку роботи, потрібен час на розуміння;
- Існує поріг входу;
Крім того, оскільки Apache Camel працює на JVM, додаткам, створеним на його основі, притаманні плюси та мінуси цієї платформи.
Досвід використання Apache Camel у зв'язці зі ScalaDSL у нашій компанії показав його ефективність для створення заглушок, компонентів інтеграції, а іноді і навантажувальних тестів.