本文试图提供一种构建微服务的可能方法。 我们将使用Scala作为编程语言。 API将是Spray和Akka提供的RESTful JSON。 MongoDB将用作数据库。 完成所有操作后,我们会将其打包到Docker容器中。 Vagrant with Ansible将照顾我们的环境和配置管理需求。
我们将提供图书服务。 它应该能够执行以下操作:
- 列出所有书籍
- 检索与一本书有关的所有信息
- 更新现有书籍
- 删除现有书籍
本文不会尝试教有关Scala,Spray,Akka,MongoDB,Docker,Vagrant,Ansible, TDD等的所有知识。没有哪一篇文章可以做到这一点。 目的是显示开发服务时可能使用的流程和设置。 实际上,本文的大部分内容与其他类型的开发同样相关。 Docker的使用范围比微服务要广泛得多,Ansible和CM通常可用于任何类型的配置,而Vagrant对于快速创建虚拟机非常有用。
环境
我们将使用Ubuntu作为开发服务器。 设置服务器最简单的方法是使用Vagrant 。 如果尚未安装,请下载并安装。 您还需要Git用源代码克隆存储库。 本文的其余部分将不需要任何其他手动安装。
让我们从克隆仓库开始。
git clone https://github.com/vfarcic/books-service.git
cd books-service
接下来,我们将使用Vagrant创建一个Ubuntu服务器。 定义如下:
# -*- 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
我们将盒子(OS)定义为Ubuntu。 Sync文件夹为/ vagrant,这意味着主机上当前目录内的所有内容都将作为VM中的/ vagrant目录提供。 我们将需要使用Ansible安装其余的东西,因此我们将通过bootstrap.sh脚本为其配置VM。 最后,此Vagrantfile定义了两个VM: dev和prod 。 他们每个人都将运行Ansible,以确保所有内容均已正确安装。
使用Ansible的首选方法是将配置分为多个角色。 在我们的案例中, ansible / roles目录中有四个角色。 一个将确保已安装Scala和SBT,另一个将确保Docker已启动并正在运行,另一个将确保运行MongoDB容器。 最后一个角色(书籍)将在以后用于将我们正在构建的服务部署到生产VM。
例如,下面是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]
对于那些曾经使用Docker的人来说,这应该是不言自明的。 该角色可确保该目录存在并且mongodb容器正在运行。 我们将Playbook ansible / dev.yml捆绑在一起。
- hosts: localhost
remote_user: vagrant
sudo: yes
roles:
- scala
- docker
- mongodb
作为前面的示例,该示例也应该是不言自明的。 每次运行此剧本时,都会执行角色scala,docker和mongodb中的所有任务。
一般来说,Ansible和Configuration Management的好处是它们不会盲目运行脚本,而是仅在需要时才执行。 如果您第二次运行配置,Ansible将检测到一切正常,并且不执行任何操作。 另一方面,例如,如果您删除目录/ data / db ,则Ansible将检测到该目录不存在并重新创建。
让我们启动开发虚拟机 ! 第一次可能要花一些时间,因为Vagrant需要下载整个Ubuntu发行版,安装少量软件包并下载MongoDB的Docker映像。 每次下一次运行都会更快。
vagrant up dev
vagrant ssh dev
ll /vagrant
流浪汉创建一个新的VM或使现有的VM栩栩如生。 使用vagrant ssh,我们可以输入新创建的框。 最后, ll / vagrant列出该目录中的所有文件,以证明我们的所有本地文件在VM中都可用。
而已。 我们已经准备好使用Scala,SBT和MongoDB容器的开发环境。 现在是时候发展我们的图书服务了。
图书服务
我爱Scala和Akka 。 Scala是一种非常强大的语言,而Akka是我最喜欢的用于构建消息驱动的JVM应用程序的框架。 虽然它是从Scala诞生的,但是Akka也可以与Java一起使用。
Spray是用于构建基于REST / HTTP的应用程序的简单但功能强大的工具包。 它是异步的,使用Akka actor,并且具有用于定义HTTP路由的出色的DSL(起初很奇怪)。
以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所应遵循的方向。 首先,我们要确保路线返回代码200(确定)。 在向数据库插入少量示例书之后,第二个规范验证了是否正确检索了它们。 包含所有测试的完整源代码可以在ServiceSpec.scala中找到。
我们将如何实施这些测试? 这是基于上述测试提供实现的代码。
val route = pathPrefix("api" / "v1" / "books") {
get {
complete(
collection.find().toList.map(grater[BookReduced].asObject(_))
)
}
}
那很简单。 我们在complete语句中定义路由/ api / v1 / books , GET方法和响应。 在这种情况下,我们将从数据库中检索所有书籍,并将它们转换为BookReduced案例类。 可以在ServiceActor.scala中找到具有所有方法(GET,PUT,DELETE)的完整源代码。
此处介绍的测试和实现均已简化,在现实世界中,还有更多工作要做。 实际上,复杂的路线和场景是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容器并将其推送到Hub。 如果你打算重现这些步骤,请创建帐户hub.docker.com和变化vfarcic到您的用户名。
我们构建的容器包含运行此服务所需的一切。 它基于Ubuntu,具有JDK7,包含MongoDB的实例,并具有我们组装的JAR。 从现在开始,该容器可以在安装了Docker的任何机器上运行。 无需在服务器上安装JDK,MongoDB或任何其他依赖项。 容器是自给自足的,可以在任何地方运行。
让我们部署(运行)我们刚刚在其他VM中创建的容器。 这样,我们将模拟部署到生产中。
要创建带有已部署的books服务的生产VM,请运行以下命令。
[从源目录]
vagrant halt dev
vagrant up prod
第一条命令停止开发VM。 每个需要2GB。 如果您有足够的RAM,则可能不需要停止它并可以跳过此命令。 第二个使用已部署的books服务来启动生产VM。
等待一会后,将创建新的VM,安装Ansible并运行playbook prod.yml 。 它安装Docker并运行以前构建并推送到Docker Hub的vfarcic / books-service 。 在运行时,它将公开端口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进程运行的容器的一部分。
摘要
微服务已经存在了很长时间,但是直到最近,由于尝试提供能够运行数百甚至数千个微服务的环境时出现的问题,它们并未引起足够的重视。 通过微服务获得的收益(分离,更快的开发,可伸缩性等)不如通过部署部署和配置需要付出更多努力而产生的问题那么大。 像Ansible这样的Docker和CM工具可以减少这种工作量几乎可以忽略不计。 随着部署和供应问题的解决,微服务因其提供的好处而逐渐流行起来。 与整体应用程序相比,开发,构建和部署时间更快。
喷雾是微服务的很好选择。 当Docker容器包含应用程序所需的所有内容时,它们会发光。 对于单个(小型)服务而言,使用大型Web服务器(如JBoss和WebSphere)将是过大的选择。 甚至不需要占地面积较小的Web服务器(例如Tomcat)。 玩! 非常适合构建RESTful API。 但是,它仍然包含许多我们不需要的东西。 另一方面,喷雾只会做一件事情,并且做得很好。 它为RESTful API提供了异步路由功能。
我们可以继续向该服务添加更多功能。 例如,我们可以添加注册和身份验证模块。 但是,这将使我们更接近单片应用程序。 在微服务世界中,新服务将是新应用程序,在Docker新容器的情况下,每个新容器都侦听不同的端口并愉快地响应我们的HTTP请求。
构建微服务时,请尝试以一种或几种方式创建微服务。 通过将它们组合在一起来解决复杂性,而不是构建一个大型的整体应用程序。