Вебчик и Scala (via Play! Framework)
Введение
“Не консолью единой живы люди” - подумал я и решил, что надо попробовать наваять простое веб-приложение на Play! Framework. Play! Framework - это MVC фреймворк для создания веб-приложений на Java/Scala. На первый (да и на второй, если честно) взгляд кажется довольно простым и ничем особо не отличающимся от других подобных фреймворков. Однако, есть мнение, что для версии 2.x надо брать на вооружение Scala, а для Java лучше подойдет более взрослая версия 1.x.
Задача
Свой небольшой эксперимент я решил провести со Scala и Play! версии 2.1, который идет в комплекте с IDE от JetBrains IntelliJ IDEA. Удобным является то, что запустить приложение можно прямо в IDEA. Если вы еще не установили плагин для Scala - самое время это сделать. Теперь можно создать приложение из шаблона Play 2.x.
Play! Framework
При создании проекта в IDE будет создана базовая структура проекта с необходимым минимумом для приложения: по одному контроллеру и представлению. Модели в шаблон приложения не завезли. Так же для создания скелета приложения можно исспользовать activator. Что радует - активатор содержит в себе полноценную IDE выполнив команду activator ui. На самом деле, для простого приложения этого более чем достаточно. А делать ничего сильно серьезного и не планировали: просто перенесем предыдущее приложение в веб. Вкратце: приложение получает на вход поисковый запрос к сайту stackoverflow.com и возвращает список вопросов подходящих этому запросу.
Представление
Представления в Play имеют расширение *.scala.html и, по сути, являются такими же функциями и могут вызываться одно из другого:
<footer>
   Powered by Play Framework
</footer>
@(title: String)(content: => Html)(implicit flash: Flash)
<!DOCTYPE html>
<html>
    <head>
        <title>@title</title>
    </head>
    <body>
        <section class="content">@content</section>
        @footer()
    </body>
</html>
Есть возможность создать “базовое” представление (по аналогии с Master Page в
ASP.NET WebForms или Shared Layout в ASP.NET MVC), которое будет расширяться
конкретными представлениями. Например, у есть представление main.scala.html:
@(title: String)(content: Html)
<html>
   <head>
      <title>@title</title>
   </head>
   <body>
      @content
   </body>
</html>
В представлении index.scala.html мы можем написать следующий код:
@main("Result page") {
  <h1>Hi there!</h1>
}
На деле всё это соберется во что-то похожее на следующий кусок:
<html>
   <head>
      <title>Result page</title>
   </head>
   <body>
      <h1>Hi there!</h1>
   </body>
</html>
Контроллеры
Контроллеры располагаются в модуле controllers. Каждый контроллер должен
наследоваться от класса play.api.mvc.Controller. Конфигурирование путей
(routs) необходимо производить в файле /conf/routes. Например, строка
GET     /Search                     controllers.Application.search(query: String)
Значит, что при обращении к адресу http://host:port/Search?query=scala будет
выполняться GET запрос к action-у search контроллера Application с
параметром query равным scala. Подробнее можно почитать
здесь.
Модели
Модель - обычный класс, который, по своей сути, являются классом, описывающими бизнес-объекты области.
Приложение
Так как мы делаем веб-приложение, то возможностей у нас больше: будет
отображаться не просто список заголовков вопросов. Заголовки будут являться
ссылками на сами вопросы на StackOverflow. Так же добавим в список вопросов
автора вопроса со ссылкой на его профиль. Приложение будет включать три
представления: index.scala.html (“главная” страница нашего сайта),
search.scala.html (страница с поиском и результатами поиска) и мастер-
представление main.scala.html. В приложении будет так же один контроллер
Application.scala, который будет содержать всю логику и модель
QuestionModel.scala, содержащая описание вопроса. Контроллер содержит два
action-а: index и search (соответсвенно используют index.scala.html и
search.scala.html). Представление и action index будет отображаться,
если к приложению обращаются по адресу http://host:port/. Если обращаются по
адресу http://host:port/search?query=scala, то будет вызываться action
search с параметром из адресной строки q. В приложении используются
библиотеки json4s для работы с JSON и
scalaj-http для выполнеия запросов к
API StackOverflow. Логика получения ответов от API StackOverflow ничем не
отличается от предыдущего
приложения:
   val url = "https://api.stackexchange.com/2.2/search?order=desc&sort=activity&site=stackoverflow&intitle=" +
      java.net.URLEncoder.encode(query, "UTF-8")
   // response будет содержать ответ от API StackOverflow
   val response: HttpResponse[String] = Http(url).asString
Для того, чтобы ответ корректно десериализовался необходимо создать несклько
вспомогательных case-классов: Questions, представляющий собой список всех
вопросов; Question - один вопрос; User - автор вопроса:
case class Questions(items: List[Question])
case class Question(answer_count: Int,
                    creation_date: String,
                    is_answered: Boolean,
                    last_activity_date: Long,
                    link: String,
                    owner: User,
                    question_id: Long,
                    score: Integer,
                    tags: List[String],
                    title: String,
                    view_count: Integer)
case class User (display_name: String,
                 link: String,
                 profile_image: String,
                 reputation: Integer,
                 user_id: Long,
                 user_type: String)
Данные классы написаны на основе ответа от API StackOverflow:
{"items":[{"tags":["java","scala","maven","apache-spark"],"owner":{"reputation":1019,"user_id":317027,"user_type":"registered","accept_rate":69,"profile_image":"https://www.gravatar.com/avatar/de811098d6fa6c11aa0be27262cd1796?s=128&d=identicon&r=PG","display_name":"hba","link":"http://stackoverflow.com/users/317027/hba"},"is_answered":false,"view_count":16,"answer_count":0,"score":-1,"last_activity_date":1449080010,"creation_date":1449080010,"question_id":34050028,"link":"http://stackoverflow.com/questions/34050028/scala-test-maven-plugin-multiple-spark-context-exception","title":"Scala Test Maven Plugin - Multiple Spark Context Exception"}]}
json4s позволяет просто распарсить ответ в виде JSON-строки к case-классу:
   // Требуется для корректной сериализации JSON.
   implicit val formats = Serialization.formats(NoTypeHints)
   // Десериализация ответа StackOverflow к объекту Questions
   val output = org.json4s.jackson.Serialization.read[Questions](response.body)
Представление search.scala.html ожидает на вход объект класса
scala.collection.immutable.List[models.QuestionModel] (об этом чуть позже).
Для этого преобразуем наш объект класса Questions к
scala.collection.immutable.List[models.QuestionModel] и вернем его в
представление search.scala.html:
   Ok(views.html.search(output.items.map(q =>
      new models.QuestionModel(q.title, q.link, q.is_answered, q.owner.display_name, q.owner.link))))
Класс QuestionModel не содержит никакой логики:
package models
class QuestionModel(t: String, l: String, a: Boolean, on: String, ol: String) {
  var title: String = t
  var link: String = l
  var is_answered: Boolean = a
  var owner_name: String = on
  var owner_link: String = ol
}
Приложеие содержит три представления: - main.scala.html - “базовое”
представление - index.scala.html - стартовая страница приложения -
search.scala.html - страница отображения результатов поиска На каждой
странице приложения должна быть возможность поиска вопросов. Таким образом,
форму поиска вопросов можно вынести в представление main.scala.html. Так же
каждая страница должна иметь стандартную html разметку (теги <html>,
<head> и т.п.). Следовательно, их тоже можно вынести в это представление:
@(title: String)(content: Html)
<!DOCTYPE html>
<html>
   <head>
      <title>@title</title>
      <link rel="stylesheet" media="screen" href="assets/stylesheets/main.css">
      <link rel="shortcut icon" type="image/png" href="assets/images/favicon.png">
   </head>
   <body>
      <div class="center">
         <form action="Search" method="GET">
            <input id="query" name="query" type="text" />
            <input id="search" type="submit" value="search"/>
         </form>
      </div>
      @content
   </body>
</html>
В представлении index.scala.html означим заголовок страницы и значение тега
title:
@(message: String)
<h1>@message</h1>
@main("Welcome to SO searcher") { }
Строка @(message: String) значит, что в это представление можно передать
строку, которой потом можно будет воспользоваться в представлении. Что и
делаем в строке <h1>@message</h1>. Строка @main("Welcome to SO searcher") { } вызывает представление main.scala.html с первым параметром Welcome to SO searcher и пустым вторым параметром. Т.е. в итоге эти два представления
сольются в одну страницу. Представление search.scala.html имеет чуть более
сложную структуру. В этом представлении требуется отображать резульататы
поискового запроса. К счастью, как говорилось выше, представления в Play!
являются Scala-функциями и позволяются использовать Scala-код (правда с
некоторыми ограничиениями). Нам достаточно базовой функциональсти
предоставляемой шаблонизатором Play!: проход по циклу и формирование
разметки маркированного списка с примененем соответствующего класса, если
вопрос содержит ответ:
<ul>
   @for(item <- result) {
      <li>
         <a @if(item.is_answered) { class='answered' } href="@item.link">@item.title</a> from <a href="@item.owner_link">@item.owner_name</a>
      </li>
   }
</ul>
Заключение
В целом впечатление Scala как о языке для веб-разработки осталось двоякое: с
одной стороны, довольно приятный синтаксис Scala, но, с другой стороны,
стандартный шаблонизатор Play! иной раз выводил из себе непонятными
сообщениями об ошибках. Возможно, всё это можно решить. Но пока, пожалуй, не
буду советовать бросать всё и начинать разрабатывать на связке Play! Framework и Scala. Итоговый проект можно найти на
github По мотивам: -
[Scala - Working with REST service calls and handling
JSON](http://krishnabhargav.github.io/scala,/how/to/2014/06/15/Scala-Json-
REST-Example.html) - [Working With JSON in Scala USsing The JSON4S Library
(Part Two)](https://nosqlnocry.wordpress.com/2015/06/23/working-with-json-in-
scala-using-the-json4s-library-part-two/) - Scala
Templats
