Позвольте мне вернуться к посту Maven в SBT и подробнее остановиться на модульности приложений Akka. Я также выделю дополнение Akka AMQP, которое позволяет вам использовать брокеров AMQP, таких как мой любимый RabbitMQ, в ваших приложениях Akka. Опять же, это длинный пост, так что соберите еду и напитки, чтобы все прошло до конца.
Maven для SBT
Первое, что нужно решить — это модульность исходного кода. Как мы создаем и разворачиваем наше приложение и как наш выбор влияет на подходы к упаковке и сборке. Я начну с типичного Maven strcutrue. Представьте типичное приложение Akka, которое создает важную структуру акторов в core
модуле, работая с экземплярами классов в domain
. Затем мы выставляем функциональность в api
и выставляем API на HTTP в web
модуле. Чтобы поддержать наши усилия по всему коду, у нас может даже быть test
модуль, который помогает нам писать код в тестах.
В типичном Maven зверь, вы бы создать эти модули ( domain
, core
, …) в виде отдельных модулей, с явно определенными зависимостями от других модулей. Причина в том, что это гарантирует, что вы не пропустите уровни; и что вы можете повторно использовать части кода вашего приложения. (Предполагая, что у вас есть какое-то хранилище артефактов для всей компании.)
На первый взгляд, это звучит хорошо. Но мой опыт работы с большим проектом говорит мне, что это почти никогда не работает так, как вы думаете. Четкие зависимости между модулями запутываются; и часто строгая структура «отдельный проект» предотвращает большие рефакторинги. Наконец, вы почти всегда развертываете все приложение , упакованное, скажем, в WAR . Наши причины для использования многомодульных проектов Maven уже не так актуальны; и в большинстве случаев они действительно причиняют нам боль.
Вне с Maven!
Эту линию вы, без сомнения, ждали. С Maven, с SBT; и пока мы на этом, давайте создадим одномодульный проект SBT. Мы создадим небольшое приложение Akka, небольшая часть которого соединяет RabbitMQ для доставки и маршрутизации сообщений. Естественно, мы включим тесты, в том числе тесты, в которых используются компоненты AMQP.
Прежде всего, весь наш исходный код будет находиться в src
каталоге. Поскольку нам также нужны некоторые плагины SBT, мы создадим project
каталог. Мы дополним картинку build.sbt
файлом; вместе с несколькими хорошими домашними файлами. Нам нужно что-то вроде этого:
$ ls -la -rw-r--r-- 1 janmachacek staff 1155 17 Nov 16:42 README.md -rw-r--r-- 1 janmachacek staff 2389 17 Nov 16:42 build.sbt drwxr-xr-x 2 janmachacek staff 102 11 Nov 17:00 project drwxr-xr-x 4 janmachacek staff 136 11 Nov 17:00 src -rw-r--r-- 1 janmachacek staff 31 11 Nov 17:00 version.sbt
Обращаясь к build.sbt
нам, у нас есть зависимости и другие кусочки. Вот оно во всей полноте:
import sbtrelease._ /** Project */ name := "Akka Patterns" version := "1.0" organization := "org.cakesolutions.akkapatterns" scalaVersion := "2.10.0-RC2" /** Shell */ shellPrompt := { state => System.getProperty("user.name") + "> " } shellPrompt in ThisBuild := { state => Project.extract(state).currentRef.project + "> " } /** Dependencies */ resolvers += "spray repo" at "http://repo.spray.io" resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" resolvers += "Sonatype OSS Releases" at "http://oss.sonatype.org/content/repositories/releases/" resolvers += "Sonatype OSS Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/" libraryDependencies <<= scalaVersion { scala_version => val sprayVersion = "1.1-M5" val akkaVersion = "2.1.0-RC2" Seq( "com.typesafe.akka" % "akka-kernel" % akkaVersion cross CrossVersion.full, "io.spray" % "spray-can" % sprayVersion, "io.spray" % "spray-routing" % sprayVersion, "io.spray" % "spray-httpx" % sprayVersion, "io.spray" % "spray-util" % sprayVersion, "io.spray" % "spray-client" % sprayVersion, "io.spray" % "spray-json" % "1.2.2" cross ..., "org.mongodb" % "mongo-java-driver" % "2.9.3", "com.aphelia" %% "amqp-client" % "1.0", "io.spray" % "spray-testkit" % sprayVersion % "test", "com.typesafe.akka" % "akka-testkit" % akkaVersion % "test" cross ..., "org.specs2" % "classycle" % "1.4.1" % "test", "org.specs2" % "specs2" % "1.12.2" % "test" cross ... ) } /** Compilation */ javacOptions ++= Seq("-Xmx1812m", "-Xms512m", "-Xss6m") javaOptions += "-Xmx2G" scalacOptions ++= Seq("-deprecation", "-unchecked") maxErrors := 20 pollInterval := 1000 logBuffered := false cancelable := true testOptions := Seq(Tests.Filter(s => Seq("Spec", "Suite", "Unit", "all").exists(s.endsWith(_)) && !s.endsWith("FeaturesSpec") || s.contains("UserGuide") || s.contains("index") || s.matches("org.specs2.guide.*"))) /** Console */ initialCommands in console := "import org.cakesolutions.akkapatterns._"
Ничего особенного Давайте посмотрим на наш src
каталог, который содержит весь исходный код, разбитый на main
и test
кодовую базу. Мы запускаем main
код на сервере и запускаем его, test
когда хотим проверить, main
правильно ли он работает. Пакеты, которые мы создаем в недрах src
каталога, имитируют наши модули Maven:
$ ls -la total 8 drwxr-xr-x 9 janmachacek staff 340 17 Nov 16:42 . drwxr-xr-x 3 janmachacek staff 102 10 Nov 13:06 .. drwxr-xr-x 2 janmachacek staff 238 17 Nov 16:42 api drwxr-xr-x 4 janmachacek staff 272 17 Nov 16:42 core drwxr-xr-x 2 janmachacek staff 272 17 Nov 16:42 domain drwxr-xr-x 2 janmachacek staff 136 17 Nov 16:42 main drwxr-xr-x 2 janmachacek staff 102 17 Nov 16:42 test drwxr-xr-x 2 janmachacek staff 102 17 Nov 16:42 web
Эта структура требует большей дисциплины ; вы можете запутывать свои посылки. Вместо того чтобы полагаться на жесткую структуру нашей сборки Maven (или многопроектной сборки SBT), мы включим тесты, которые проверяют архитектурную надежность нашей кодовой базы. Мы будем использовать Specs2, поэтому тест, который проверяет нашу архитектуру, не должен удивлять. (Обратите внимание, что вам нужно скомпилировать и запустить код, используя JDK 1.7.)
class ArchitectureSpec extends Specification with Analysis with ClassycleDependencyFinder { "The architecture" should { "Have properly defined layers" in { val ls = layers( "main", "web", "api", "core", "domain" ).withPrefix("org.cakesolutions.akkapatterns"). inTargetDir("target/scala-2.10") ls must beRespected } } }
Таким образом, вместо того, чтобы иметь отдельные модули Maven и некоторые из наших архитектурных ограничений, выраженных в зависимостях модулей, мы перенесли наши архитектурные тесты в наш тестовый код.
Развертывание и запуск модулей
Если наш проект включает в себя только один main
метод, все, что вам нужно сделать, чтобы загрузить свой проект, — это набрать текст, sbt run
и все готово!
Предположим, у вас есть два основных компонента в вашем приложении; и вы соединяете эти два компонента через AMQP. Итак, теперь у нас есть два main
метода. Попытка выполнить sbt run
спросит, что main
вы хотите выполнить? Таким образом, вместо того sbt run
, чтобы сказать нам, нужно sbt run-main org.cakesolutions.akkapatterns.main.Server
загрузить основные субъекты и sbt run-main org.cakesolutions.akkapatterns.Keystore
запустить компонент хранилища ключей.
Несмотря на то, что мы можем контролировать, какой компонент запускать, мы всегда развертываем всю кодовую базу.
Я расскажу вам подробности общения с актерами на основе AMQP в ближайшие несколько дней.