Розробка микросервисов з використанням Scala, Spray, MongoDB, Docker і Ansible

Мета даної статті — показати можливий підхід для побудови микросервисов з використанням Scala, RESTful JSON, Spray і Akka. В якості бази даних ми будемо використовувати MongoDB. У результаті нашої роботи ми упакуємо наш проект Docker-контейнер, а Vagrant і Ansible дозволить нам керувати конфігурацією програми.

В цій статті ви не знайдете подробиць про мову Scala і інших технологіях, які будуть використовуватися в проекті. У ній ви не знайдете керівництва, яке відповість на всі ваші запитання. Мета статті — показати техніку, яку можна використовувати при розробці микросервисов. Насправді, більша частина цієї статті не зав'язана на конкретній технології. Docker має більш широку сферу використання, а не тільки микросервисы. Ansible дозволяють швидко розгорнути будь-яку необхідну оточення, а Vagrant — відмінний інструмент для створення віртуальних машин.

Отже, приступимо до створення «Книжкового сервісу» з наступними методами:

  • Отримати всі книги
  • Отримати інформацію про книги
  • Оновити існуючу книгу
  • Видалити існуючу книгу

Оточення

Ми будемо використовувати Ubuntu в якості сервера. Найбільш простий шлях його створити — скористатися Vagrant. Якщо у вас ще не встановлено, будь ласка, скачайте і встановіть. Вам так само потрібно Git для клонування репозиторію з вихідним кодом. Інша частина статті не зажадає від вас ручної установки додаткових пакетів.

Приступимо до клонування репозиторію

git clone https://github.com/vfarcic/books-service.git
cd books-service

Далі необхідно створити Ubuntu-сервер використовуючи Vagrant з наступними налаштуваннями:

# -*- mode ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.synced_folder ".", "/vagrant"
config.vm.provision "shell", path: "bootstrap.sh"
config.vm.provider "virtualbox" do |v|
v.memory = 2048
end
config.vm.define :dev do |dev|
dev.vm.provision :shell, inline: 'ansible-playbook /vagrant/ansible/dev.yml-c local'
end
config.vm.define :prod do |prod|
prod.vm.provision :shell, inline: 'ansible-playbook /vagrant/ansible/prod.yml-c local'
end
end


Ми визначили box (ОС) як Ubuntu, synced_folder означає, що все, що знаходиться усередині каталогу ./vagrant на хост-машині буде доступний усередині віртуальної машини. Іншу роботу, по установці програм і підготовки оточення ми покладемо на Ansible, який буде встановлений з допомогою bootstrap.sh. У Vagrantfile знаходиться дві віртуальні машини: dev prod. Кожна з них буде використовувати Ansible, тому переконайтеся, що він встановлений коректно.

Класичний шлях роботи з Ansible — розділити конфігурацію на ролі. У нашому випадку ми маємо 4 ролі, що знаходяться в ansible/roles. Перша роль включає в себе установку Scala і SBT. Ще одна встановлює Docker. Третя встановлює MongoDB-контейнер. Остання роль (books) буде використовуватися для розгортання додатків на бойову віртуальну машину.
В якості прикладу, оголосимо mongodb роль наступним чином:

- name: Directory is present
file:
path=/data/db
state=directory
tags: [mongodb]

- name: Container is running
docker:
name=mongodb
image=dockerfile/mongodb
ports=27017:27017
volumes=/data/db:/data/db
tags: [mongodb]

Ця роль гарантує, що папка існує і контейнер mongodb працює. Плейбук ansible/dev.yml пов'язує ці ролі разом:

- hosts: localhost
remote_user: vagrant
sudo: yes
roles:
- scala
- docker
- mongodb

Кожен раз коли ми запускаємо плейбук, виконуються всі завдання з ролей scala, docker і mongodb.

Одна з принад Ansible в тому, що він виконує завдання лише тоді, коли це потрібно. Якщо ви запустіть його вдруге, він перевірить, що все на місці і нічого не зробить. Однак, якщо ви видалите папку /data/db, Ansible помітить пропажу і створить її знову.

Час запускати віртуальну машину! Перший запуск, буде трохи довгим, так як Vagrant необхідно завантажити дистрибутив Ubuntu, встановити пакети і завантажити Docker-образ MongoDB. Наступні запуски будуть відбувається помітно швидше.

vagrant up dev
vagrant ssh dev
ll /vagrant


Команда vagrant up створює нову віртуальну машину або запускає одну з існуючих. З vagrant ssh ми заходимо на нещодавно створену машину. І, нарешті ll /vagrant показує список файлів та директорій як доказ того, що наші локальні файли доступні всередині віртуальної машини.
Це все. Наш сервер розробки з Scala, SBT і MongoDB готовий до роботи. Приступимо до розробки нашого сервісу.

«Книжковий сервіс»

Мені подобається Scala, це дуже потужний мову, а akka мій улюблений фреймворк для побудови message-driven JVM-додатків. Незважаючи на те, що Akka з'явилася з Scala, ніщо не заважає використовувати її в Java.
Spray — це простий, але потужний інструмент для побудови REST/HTTP сервісів. Він асинхронний, завдяки використанню Akka-акторів і має чудовий DSL для опису HTTP маршрутів.
У моду використання TDD ми пишемо тести перед реалізацією. Ось приклад тестів, який перевіряє маршрут, яким віддається список книг.
"GET /api/v1/books" should {
"return OK" in {
Get("/api/v1/books") ~> route ~> check {
response.status must equalTo(OK)
}
}

"return all books" in {
val expected = insertBooks(3).map { book =>
BookReduced(book._id, book.title, book.author)
}

Get("/api/v1/books") ~> route ~> check {
response.entity must not equalTo None
val books = responseAs[List[BookReduced]]
books must haveSize(expected.size)
books must equalTo(expected)
}
}
}

Це простий тест, який, сподіваюся, покаже напрямок, у якому потрібно рухатися для розробки тестів на API побудовані на Spray. Перше, що ми перевіряємо — сервер, за даним запитом повинен повертати код 200 (OK). Друге, на що ми звертаємо увагу, що після додавання книги в базу даних, вона коректно повертається. Повний вихідний код з тестами ви можете подивитися в ServiceSpec.scala

Як реалізовані ці перевірки? Код, який це дозволить, представлений нижче:

val route = pathPrefix("api" / "v1" / "books") {
get {
complete(
collection.find().toList.map(grater[BookReduced].asObject(_))
)
}
}


Ми визначили маршрут /api/v1/books, GET метод і відповідь всередині виразу complete(). В нашому випадку ми отримали список всіх книг з БД і перетворили його до кейс-класу BookReduced. Весь вихідний код, який включає всі методи (GET, PUT, DELETE) ви можете знайти в ServiceActor.scala
Обидва тесту і реалізація наведено в якості прикладу, на практиці вони, як правило, складніше. Але Spray з цим справляється по-справжньому здорово.

Під час розробки ви можете запустити тести в швидкому режимі.

#[Усередині віртуальної машини]
cd /vagrant
sbt ~test-quick

Кожен раз, при зміні вихідного коду, всі порушені тести будуть запущені знову. Зазвичай у мене відкрито вікно терміналу з результатами тестів, це дає мені можливість отримувати миттєву зворотний зв'язок про якість коду, над яким я працюю.

Тестування, складання і розгортання

Наш додаток, як і будь-яке інше, потребує тестуванні, складанні та розгортанні.
Давайте створимо Docker-контейнер з сервісом. Необхідні налаштування ви можете вказати в Dockerfile.

#[Усередині віртуальної машини]
cd /vagrant
sbt assembly
sudo docker build-t vfarcic/books-service .
sudo docker push vfarcic/books-service

Ми скомпілювали JAR (проходження тестів — це частина етапу зборки), зібрали Docker-контейнер і відправили це в Docker Hub. Якщо ви плануєте відтворити ці кроки знову, будь ласка, створіть акаунт у hub.docker.com і змініть «vfarcic» на ваш логін.
Контейнер, який ми створили містить все, що потрібно для запуску нашого сервісу. Він заснований на Ubuntu, містить JDK7 з MongoDB і зібраний JAR-файл. Цей контейнер може бути запущений на будь-якій машині, з встановленим Docker. Він не вимагає установки додаткових залежностей на сервер, контейнер самодостатня і може бути запущений де завгодно.
Давайте розгорнемо (запустимо) контейнер який ми створили на іншій віртуальній машині. Це дуже схоже на розгортання додаток в production-середовищі.
Для створення production-віртуальної машини, з нашим сервісом необхідно виконати наступні команди:

#[з директорії з вихідним кодом]
vagrant halt dev
vagrant up prod

Перша команда зупиняє dev-віртуальну машину. Кожна машина вимагає 2ГБ ОЗУ, і якщо ви маєте достатньо вільної ПАМ'ЯТІ, то можете пропустити цей крок. Друга команда запускає production-машину, з розгорнутою сервісом.
Через деякий час очікування, віртуальна машина створиться, Ansible встановиться і запустить playbook prod.yml. Він встановить Docker і запустить vfarcic/books-service, зібраний на попередньому кроці і відправлений у Docker Hub. Під час роботи буде використовувати порт 8080 і мати загальну, приймаючої системою, папку /data/db.
Давайте спробуємо, що у нас вийшло. Для початку спробуємо відправити PUT-запит, щоб додати тестові дані:

curl-H 'Content-Type: application/json' -X PUT-d '{"_id": 1, "title": "My First Book", "author": "John Doe", "description": "Not a very good book"}' http://localhost:8080/api/v1/books
curl-H 'Content-Type: application/json' -X PUT-d '{"_id": 2, "title": "My Second Book", "author": "John Doe", "description": "Not a bad as the first book"}' http://localhost:8080/api/v1/books
curl-H 'Content-Type: application/json' -X PUT-d '{"_id": 3, "title": "My Third Book", "author": "John Doe", "description": "Failed writers club"}' http://localhost:8080/api/v1/books

Давайте перевіримо, що сервіс повернув нам коректні дані:
curl-H 'Content-Type: application/json' http://localhost:8080/api/v1/books

Ми можемо видалити книгу:
curl-H 'Content-Type: application/json' -X DELETE http://localhost:8080/api/v1/books/_id/3

Перевіримо, що віддалена книга більше не існує:
curl-H 'Content-Type: application/json' http://localhost:8080/api/v1/books

На закінчення спробуємо витягти конкретний екземпляр книги:
curl-H 'Content-Type: application/json' http://localhost:8080/api/v1/books/_id/1

Ми спробували швидкий спосіб розробки, складання та розгортання микросервиса. Docker спрощує розгортання і не вимагає додаткових залежностей. Кожен сервіс, що вимагає JDK та MongoDB не вимагає встановлених додатків на кінцевій машині. Це все є частиною контейнера, який запущений як Docker-процес.

Підсумки

Ідея микросервисов існувала тривалий час, і до недавнього часу не отримувала належної уваги, з-за проблем сумісності програм, необхідних для одночасної роботи сотень і тисяч різних примірників сервісів. Переваги, які виникали завдяки використанню микросервисов (поділ, зменшення терміну розробки, масштабованість і т. д) були не настільки значущими порівняно з проблемами, які вони несли при забезпеченні потрібного оточення і розгортанні. Docker і такі інструменти як Ansible допомагають значно скоротити зусилля. З відходом від цієї проблеми, самі різнорідні микросервисы стають в моді з переваг, які вони несуть.

Spray — це відмінний вибір для микросервиса. Docker-контейнери містять все, що потрібно для роботи програми, і нічого зайвого. Використання великих веб-серверів, таких як JBoss і WebSphere може бути невиправдано для невеликого сервісу. Навіть у таких невеликих серверах як Tomcat зазвичай немає необхідності. Play! — це чудовий фреймворк для побудови RESTFul API, однак він містить багато речей, які ми не використовуємо. Spray, з іншого боку робить лише одну річ — надає асинхронну маршрутизацію для RESTFul API, і робить це чудово.

Ми можемо продовжити розширювати функціонал сервісу. Наприклад, ми можемо додати модуль реєстрації та аутентифікації.
Тим не менш, це наблизить нас на один крок до монолітного додатком. У світі микросервисов нові сервіси повинні бути новими додатками, а у випадку з Docker — окремими контейнерами, кожен з яких слухає свій порт і відповідає на адресовані йому запити.

При побудові микросервисов, потрібно намагатися створити їх таким чином, щоб вони робили тільки одну або невелику кількість роботи. Складність вирішується шляхом об'єднання їх разом, а не побудовою великого монолітного програми.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.