Статьи

Maven, SBT и Modularisation

Позвольте мне вернуться к посту 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 в ближайшие несколько дней.