msawady’s engineering-note

なにも分からないエンジニアです。

【Scala】Play Framework を使って簡単なWebページを作る

Webページを作ってみる

  • 素数探索、フィボナッチ数列でなんとなく制御構文の要領は掴めてきた
  • オブジェクト、Enum、ファイルI/O…etcを学ぶためにTODOアプリを作ってみる
  • Play Framework をベースにする(業務ではSpring を使っているので、対抗馬?的な意味もこめて)
  • 今回の記事では「サンプルデータをブラウザに表示する」まで

Play Framework 導入

  • 以下のサンプルアプリを git clone github.com

  • IntelliJ の Run/Debug Configurations から tasks に run を追加 f:id:msawady:20170729195216p:plain

で起動できます。素晴らしいスピード感!このサンプルアプリをコピペ参考にして開発を進めます。

TODOアプリ

  • TODOの保存はファイルベースで行う
    • ファイルI/Oの練習も兼ねて
    • 早く画面を出したかったDBの設定でハマるのに怯えた
  • 一旦、項目としては以下の3つ
    • id
    • ステータス(Enum….みたいな感じ)
    • タイトル

ソース

Controller

  • TodoController
package controllers

import javax.inject._

import play.api.mvc._
import services.TodoManager

/**
  * controller class for todo management
  */
@Singleton
class TodoController @Inject()(todoManager: TodoManager, cc: ControllerComponents) extends AbstractController(cc) {

  /**
    * returns view of todo-list
    */
  def list = Action {
    Ok(views.html.todo(todoManager.list))
  }

}

サービスクラスをInjectして返り値をhtmlにセットして返すイメージ。

Domain

  • Todo
package services.domain

/**
  * entity class of todo
  */
class Todo(id: Int, status: TodoStatus, title: String) {

  def getId = id

  def getStatus = status

  def getTitle = title

}

POJO(Javaじゃないけど)はこんな感じで書ける。immutable に保つためにセッターは用意しない。

  • TodoStatus
package services.domain

/**
  * status of todo
  */
sealed trait TodoStatus
case object UNDONE extends TodoStatus
case object DOING extends  TodoStatus
case object DONE extends TodoStatus

Enumsealed trait を用意してそれを継承したクラスを列挙する。traitJava でいう interfaceみたいなイメージ。

Service

  • TodoManager
package services

import com.google.inject.Singleton
import services.domain._

import scala.collection.{immutable, mutable}
import scala.io.Source

/**
  * service class for todo management
  */
@Singleton
class TodoManager {

  private def initializeData(): mutable.Map[Int, Todo] = {
    val map: mutable.Map[Int, Todo] = new mutable.HashMap[Int, Todo]
    val s = Source.fromFile("C:\\data\\TODO.txt")
    val strList: List[String] = try s.getLines.toList finally s.close()
    strList.foreach(str => {
      val data: Array[String] = str split "\t"
      val todo: Todo = new Todo(data(0).toInt, statusMap.get(data(1)).get, data(2))
      map.put(todo.getId, todo)
    })
    return map
  }

  private val statusMap: immutable.Map[String, TodoStatus] = Map("DONE" -> DONE, "DOING" -> DOING, "UNDONE" -> UNDONE)

  private val todoMap: mutable.Map[Int, Todo] = initializeData() // read file at startup

  /**
    * @return todo-list order by id
    */
  def list = todoMap.values.toList.sortBy(t => t.getId)

}

起動時にファイルを読み込んで map にして保持するところまで。 ファイルの読み込み→クローズが サラッと綺麗にできるのが嬉しい。

val s = Source.fromFile("C:\\data\\TODO.txt")
val strList: List[String] = try s.getLines.toList finally s.close()

View

@import services.domain.Todo
@(todo_list: List[Todo])

<head>
    <title>todoList</title>
</head>

<h1>Todo List</h1>
<table>
    @for(todo <- todo_list) {
    <tr>
        <td> @todo.getId</td>
        <td> @todo.getStatus</td>
        <td> @todo.getTitle</td>
    </tr>
    }
<table/>

テンプレートエンジンTwirl が使いやすい。@をつけて Scala を書き始められるのがメチャメチャ楽!
thymeleaf より使いやすい説ある。ちなみに読み方は多分"トゥヮル"って感じだと思ってます。

DI

class Module extends AbstractModule {

  override def configure() = {
    // TodoManager
    bind(classOf[TodoManager]).asEagerSingleton()
  }

}

業務でも使い慣れているgoogleguiceだったので、特に違和感ないです。

動作確認

こんな感じ f:id:msawady:20170730071155p:plain

うん、ださい。とりあえずBootstrapくらいは入れよう。

感想

  • Play frameworkは、総じて素直というか、直感的に開発できるようになってるなぁという印象。
    Spring の黒魔術も嫌いじゃないし、とてもお世話になっているんだけど。
  • Twirlがほんとに使いやすくて感動した。
  • 言うても Java というか、オブジェクト指向なんだなというのも率直な感想。
    関数的に書くのは、当然それなりのメリットは有るんだろうけど、モノ作るならオブジェクト指向が楽な気がする。

今後の課題とか

  • ファイルと言うか、データ管理はrepositoryクラスを切り出しておきたい
  • UIはもうちょい綺麗にする、頑張ってスタイルシートを書く
  • 新規TODO追加、ステータス更新機能追加
  • 検索、フィルタ機能追加(repositoryでやるかクライアントでやるかは検討)
  • DBでのデータ管理

こんな感じで。今週は割りと頑張りました。