Ребята из Play Play усердно трудятся над новой версией Play 2.0. В Play 2.0 scala играет гораздо более важную роль, и особенно процесс сборки был значительно улучшен. Единственная проблема, с которой я столкнулся в Play 2.0, — это отсутствие хорошей документации. Ребята усердно работают над обновлением вики, но зачастую все еще приходится много проб и ошибок, чтобы получить то, что вы хотите. Обратите внимание, что часто это происходит не только из-за Play, но иногда я все еще борюсь с более экзотическими конструкциями Scala 😉
В этой статье я познакомлю вас с тем, как вы можете выполнять некоторые общие задачи в Play 2.0 с помощью Scala. Более конкретно я покажу вам, как создать приложение, которое:
- использует управление зависимостями на основе sbt для настройки внешних зависимостей
 - редактируется в Eclipse (с плагином Scala-ide) с помощью команды play eclipsify
 - предоставляет Rest API, используя маршруты Play
 - использует Akka 2.0 (предоставляемый платформой Play) для асинхронного вызова базы данных и генерации Json (только потому, что мы можем)
 - конвертировать объекты Scala в Json, используя предоставленную Play функцию Json (на основе jerkson)
 
Я не буду показывать доступ к базе данных с помощью Querulous, если вы хотите узнать больше об этом взгляде на эту статью. Я хотел бы преобразовать код Querulous в Anorm. Но так как мой последний опыт с Anorm был, как бы это выразиться, не убедительно положительным, я оставлю это на потом.
Создание приложения с помощью Play 2.0
Начать работать с Play 2.0 очень легко и хорошо документировано, поэтому я не буду тратить на это слишком много времени. Для полной инструкции смотрите Play 2.0 Wiki . Чтобы начать работу, после того, как вы загрузили и извлекли Play 2.0, сделайте следующее:
Выполните следующую команду из консоли:
| 
 1 
 | 
$play new FirstStepsWithPlay20 | 
Это создаст новый проект и покажет вам что-то вроде следующего:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
 | 
_ __ | | __ _ _  _| || '_ \| |/ _' | || |_||  __/|_|\____|\__ (_)|_|            |__/   play! 2.0-RC2, http://www.playframework.org  The new application will be created in /Users/jos/Dev/play-2.0-RC2/FirstStepsWithPlay20  What is the application name? > FirstStepsWithPlay20  Which template do you want to use for this new application?     1 - Create a simple Scala application  2 - Create a simple Java application  3 - Create an empty project  > 1  OK, application FirstStepsWithPlay20 is created.  Have fun! | 
Теперь у вас есть приложение, которое вы можете запустить. Перейдите в только что созданный каталог и выполните play run.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
 | 
$ play run  [info] Loading project definition from /Users/jos/Dev/play-2.0-RC2/FirstStepsWithPlay2/project[info] Set current project to FirstStepsWithPlay2 (in build file:/Users/jos/Dev/play-2.0-RC2/FirstStepsWithPlay2/)  --- (Running the application from SBT, auto-reloading is enabled) ---  [info] play - Listening for HTTP on port 9000...  (Server started, use Ctrl+D to stop and go back to the console...) | 
Если вы перейдете по адресу http: // localhost: 9000 , вы увидите свое первое приложение Play 2.0. И вы закончили с базовой установкой Play 2.0.
Управление зависимостями
Я упоминал во введении, что я не начинал этот проект с нуля. Я переписал службу отдыха, которую я сделал с помощью Play 1.2.4, Akka 1.x, JAX-RS и Json-Lift, на компоненты, предоставляемые платформой Play 2.0. Так как управление зависимостями изменилось между Play 1.2.4 и Play 2.0, мне нужно было настроить мой новый проект с нужными мне зависимостями. В Play 2.0 вы делаете это в файле с именем build.scala, который вы можете найти в папке проекта в вашем проекте. После добавления зависимостей из моего предыдущего проекта этот файл выглядел так:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
 | 
import sbt._import Keys._import PlayProject._  object ApplicationBuild extends Build {      val appName         = "FirstStepsWithPlay2"    val appVersion      = "1.0-SNAPSHOT"      val appDependencies = Seq(      "com.twitter" % "querulous" % "2.6.5" ,      "net.liftweb" %% "lift-json" % "2.4" ,      "com.sun.jersey" % "jersey-server" % "1.4" ,      "com.sun.jersey" % "jersey-core" % "1.4" ,       "postgresql" % "postgresql" % "9.1-901.jdbc4"    )      val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(      // Add extra resolver for the twitter      )} | 
  Как использовать этот файл довольно просто, как только вы прочитаете документацию sbt (http://code.google.com/p/simple-build-tool/wiki/LibraryManagement ).  По сути, мы определяем библиотеки, которые хотим, используя appDependencies, и определяем некоторые дополнительные репозитории, из которых sbt должен загружать свои зависимости (используя распознаватели).  Приятно отметить, что вы можете указать %% при определении зависимостей.  Это подразумевает, что мы также хотим найти библиотеку, которая соответствует нашей версии scala.  SBT просматривает нашу текущую настроенную версию и добавляет квалификатор для этой версии.  Это гарантирует, что мы получим версию, которая работает для нашей версии Scala. 
  Как я уже упоминал, я хотел заменить большинство внешних библиотек функциональностью Play 2.0.  После удаления материала, который я больше не использовал, этот файл выглядит так: 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
 | 
import sbt._import Keys._import PlayProject._  object ApplicationBuild extends Build {      val appName         = "FirstStepsWithPlay2"    val appVersion      = "1.0-SNAPSHOT"      val appDependencies = Seq(      "com.twitter" % "querulous" % "2.6.5" ,      "postgresql" % "postgresql" % "9.1-901.jdbc4"    )      val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(      // Add extra resolver for the twitter      )} | 
С настроенными зависимостями я могу настроить этот проект для своей IDE. Хотя все мои коллеги являются большими сторонниками IntelliJ, я все еще возвращаюсь к тому, к чему я привык: Eclipse. Итак, давайте посмотрим, что вам нужно сделать, чтобы запустить этот проект в Eclipse.
Работа от Eclipse
В моей версии Eclipse у меня установлен плагин scala , и платформа Play 2.0 прекрасно работает вместе с этим плагином. Чтобы получить ваш проект в eclipse, все, что вам нужно сделать, это запустить следующую команду: play eclipsify
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
jos@Joss-MacBook-Pro.local:~/dev/play-2.0-RC2/FirstStepsWithPlay2$ ../play eclipsify[info] Loading project definition from /Users/jos/Dev/play-2.0-RC2/FirstStepsWithPlay2/project[info] Set current project to FirstStepsWithPlay2 (in build file:/Users/jos/Dev/play-2.0-RC2/FirstStepsWithPlay2/)[info] About to create Eclipse project files for your project(s).[info] Compiling 1 Scala source to /Users/jos/Dev/play-2.0-RC2/FirstStepsWithPlay2/target/scala-2.9.1/classes...[info] Successfully created Eclipse project files for project(s): FirstStepsWithPlay2jos@Joss-MacBook-Pro.local:~/dev/play-2.0-RC2/FirstStepsWithPlay2$  | 
Теперь вы можете использовать «импорт проекта» из Eclipse и редактировать проект Play 2.0 / Scala непосредственно из Eclipse. Можно запустить среду Play непосредственно из Eclipse, но я этим не пользовался. Я просто запускаю проект Play из командной строки, и все изменения, которые я делаю в Eclipse, сразу становятся видимыми. Для тех из вас, кто работал с Play дольше, это, вероятно, уже не так уж и особенное. Лично я все еще поражен производительностью этой среды.
предоставляет Rest API, используя маршруты Play
В моем предыдущем проекте Play я использовал модуль jersey, чтобы иметь возможность использовать аннотации JAX-RS для указания моего Rest API. Поскольку Play 2.0 содержит множество критических изменений API и в значительной степени переписан с нуля, нельзя ожидать, что все старые модули будут работать. Это также относится к модулю Джерси. Я углубился в код этого модуля, чтобы увидеть, были ли изменения тривиальными, но так как я не смог найти никакой документации о том, как создать плагин для Play 2.0, который позволяет вам взаимодействовать с обработкой маршрута, я решил просто переключиться на способ Play 2.0 делает отдых. А с помощью файла «маршрутов» было очень легко соединить (просто) две операции, которые я представил, с простым контроллером:
| 
 1 
2 
3 
4 
5 
6 
 | 
# Routes# This file defines all application routes (Higher priority routes first)# ~~~~  GET     /resources/rest/geo/list    controllers.Application.processGetAllRequestGET     /resources/rest/geo/:id     controllers.Application.processGetSingleRequest(id:String) | 
Соответствующий контроллер выглядит так:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
 | 
package controllers  import akkawebtemplate.GeoJsonServiceimport play.api.mvc.Actionimport play.api.mvc.Controller  object Application extends Controller {    val service = new GeoJsonService()    def processGetSingleRequest(code: String) = Action {    val result = service.processGetSingleRequest(code)    Ok(result).as("application/json")  }    def processGetAllRequest() = Action {    val result = service.processGetAllRequest;    Ok(result).as("application/json");  } } | 
Как видите, я только что создал очень простые, базовые действия. Пока не рассматривали обработку ошибок и исключений, но API Rest, предлагаемый Play, действительно делает ненужным использование дополнительной среды Rest. Это первый из рамок. Следующей частью моего исходного приложения, которую нужно было изменить, был код Akka. Play 2.0 включает в себя последнюю версию библиотеки Akka (2.0-RC1). Так как мой оригинальный код Akka был написан против 1.2.4, было много конфликтов. Обновление исходного кода было не так просто.
Использование Akka 2.0
  Я не буду вдаваться во все проблемы, которые у меня были с Akka 2.0.  Самая большая проблема заключалась в очень дрянной документации на Play Wiki и дрянной документации на веб-сайте Akka, и мои дурацкие умения находить правильную информацию в документации Akka.  Вместе со мной использование Akka в течение трех или четырех месяцев не делает его лучшей комбинацией.  После нескольких часов фрустрации я просто удалил весь существующий код Akka и начал с нуля.  Через 20 минут у меня все заработало, работая с Akka 2 и используя мастер-конфигурацию из Play.  В следующем листинге вы можете увидеть соответствующий код (я намеренно оставил импорт, так как во многих примерах вы можете найти их, они опущены, что облегчает работу, которая намного сложнее) 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
 | 
import akka.actor.actorRef2Scalaimport akka.actor.Actorimport akka.actor.Propsimport akka.dispatch.Awaitimport akka.pattern.askimport akka.util.duration.intToDurationIntimport akka.util.Timeoutimport model.GeoRecordimport play.libs.Akkaimport resources.commands.Commandimport resources.commands.FULLimport resources.commands.SINGLEimport resources.Database  /** * This actor is responsible for returning JSON objects from the database. It uses querulous to  * query the database and parses the result into the GeoRecord class. */class JsonActor extends Actor {    /**   * Based on the type recieved we determine what command to execute, most case classes   * can be executed using the normal two steps. Execute a query, convert result to   * a set of json data and return this result.   */  def receive = {      // when we receive a Command we process it and return the result    case some: Command => {        // execute the query from the FULL command and process the results using the      // processRows function      var records:Seq[GeoRecord] = null;        // if the match parameter is null we do the normal query, if not we pass in a set of varargs      some.parameters match {        case null =>  records = Database.getQueryEvaluator.select(some.query) {some.processRows}        case _ => records = Database.getQueryEvaluator.select(some.query, some.parameters:_*) {some.processRows}      }      // return the result as a json string      sender ! some.toJson(records)    }      case _ => sender ! null  }}  /** * Handle the specified path. This rest service delegates the functionality to a specific actor * and if the result from this actor isn't null return the result */class GeoJsonService {    def processGetSingleRequest(code: String) = {      val command = SINGLE();      command.parameters = List(code);      runCommand(command);  }    /**   * Operation that handles the list REST command. This creates a command   * that forwards to the actor to be executed.   */  def processGetAllRequest:String = {   runCommand(FULL());  }    /**   * Function that runs a command on one of the actors and sets the response   */  private def runCommand(command: Command):String =  {      // get the actor    val actor = Akka.system.actorOf(Props[JsonActor])    implicit val timeout = Timeout(5 seconds)    val result = Await.result(actor ? command, timeout.duration).asInstanceOf[String]    // return result as String    result  }} | 
Много кода, но я хотел показать вам определение актера и как их использовать. Подводя итог, код Akka 2.0, который вам нужно использовать, чтобы выполнить шаблон запроса / ответа с помощью Akka, выглядит так:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
private def runCommand(command: Command):String =  {      // get the actor    val actor = Akka.system.actorOf(Props[JsonActor])    implicit val timeout = Timeout(5 seconds)    val result = Await.result(actor ? command, timeout.duration).asInstanceOf[String]    // return result as String    result  } | 
При этом используется глобальная конфигурация Akka для извлечения актера требуемого типа. Затем мы посылаем команду актеру и возвращаем Future, на котором мы ждем 5 секунд результата, который мы приводим к String. Это будущее ждет нашего актера, чтобы отправить ответ. Это делается в самом актере:
| 
 1 
 | 
sender ! some.toJson(records) | 
С заменой Акки я наконец-то снова получил работающую систему. Просматривая документацию по Play 2.0, я заметил, что они предоставляют собственную библиотеку Json, начиная с 2.0. Поскольку я использовал Json-Lift в предыдущей версии, я подумал, что было бы неплохо перенести этот код в библиотеку Json с именем Jerkson, предоставляемую Play.
Переезд в Джерксон
Переезд в новую библиотеку был довольно легким. И Lift-Json, и Jerkson используют почти одинаковую концепцию построения объектов Json. В старой версии я не использовал автоматическую сортировку (так как я должен был соответствовать формату jsongeo), поэтому в этой версии я также выполнял сортировку вручную. В следующем листинге вы можете увидеть старую версию и новую версию вместе. Как вы можете видеть, концепции, используемые в обоих, в значительной степени одинаковы.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
 | 
#New version using jerkson    val jsonstring = JsObject(      List("type" -> JsString("featureCollection"),        "features" -> JsArray(          records.map(r =>            (JsObject(List(              "type" -> JsString("Feature"),              "gm_naam" -> JsString(r.name),              "geometry" -> Json.parse(r.geojson),              "properties" -> ({                 var toAdd = List[(String, play.api.libs.json.JsValue)]()                r.properties.foreach(entry => (toAdd ::= entry._1 -> JsString(entry._2)))                JsObject(toAdd)              })))))            .toList)))  #Old version using Lift-Json    val json =      ("type" -> "featureCollection") ~        ("features" -> records.map(r =>          (("type" -> "Feature") ~            ("gm_naam" -> r.name) ~            ("geometry" -> parse(r.geojson)) ~            ("properties" -> ({              // create an empty object              var obj = JNothing(0)              // iterate over the properties              r.properties.foreach(entry => (                // add each property to the object, the reason                // we do this is, that else it results in an                 // arraylist, not a list of seperate properties                obj = concat(obj, JField(entry._1, entry._2))))              obj            }))))) | 
И после всего этого у меня точно так же, как у меня уже было. Но теперь с Play 2.0 и без использования каких-либо внешних библиотек (кроме Querulous). Пока что мой опыт с Play 2.0 был очень позитивным. Отсутствие хороших конкретных примеров и документации иногда может раздражать, но это понятно. Они предоставляют несколько обширных примеров в своем распространении, но ничего, что соответствовало моим сценариям использования. Так что снимаю шляпу перед парнями, которые отвечают за Play 2.0. То, что я видел до сих пор, отличную и всеобъемлющую структуру, множество функциональных возможностей и отличную среду для программирования Scala. В ближайшие пару недель я посмотрю, смогу ли я набраться смелости, чтобы начать работать с Anorm, и я посмотрите, что Play может предложить на стороне клиента. До сих пор я смотрел на LESS, который мне действительно нравится, поэтому я надеялся на их шаблонное решение 😉
Ссылка: Play 2.0: Akka, Rest, Json и зависимости от нашего партнера JCG