2016年9月27日火曜日

【JavaでPlayFramwork2.4.6】routesファイル

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

ただ今、もうすぐ始まるプロジェクトの実装で使用するPlayFrameworkについて事前勉強を進めています。


今回はURLを制御するroutesファイルについてご紹介します。

伝説のXML地獄


JavaWebシステム業界では長年の間、Struts1が支配的な地位を占めてきました。
そこで常に大問題を起こしてきたのが「struts-config.xml」「web.xml」という巨大XMLです。

コイツラはWebシステムが受け付けるURLを制御したり、そのリクエストを行った際に取得するパラメータを格納するFormや、遷移先のjspなど、WebシステムのWeb部分を管轄する設定ファイル達です。

欠点は、もの凄く煩雑なんですよね!!

基本的にココで躓くのは新人レベルの人なのですが、XMLなんでちょっと壊れただけでも動かなくなりますし、設定が間違っていても動かないし、ログを追うのも難しいし、と不慣れな若手プログラマーの未来の芽を摘むのに多大な役割を果してきました。

更にORMappingにSpringとHibernateを導入すれば完璧。

今やJavaWebシステム業界の金字塔とも言える究極のXML地獄が完成します。

これね、慣れれば分かるんですよ。でも不慣れだと全然分からない。
だから自分が数年の月日を経て手慣れてきても、その頃には会社には後輩が入ってきているわけで、「何か動かないんですけど~」と単体環境で起動も出来ないような始末になっていることは日常茶飯事。

あのXML地獄は最悪だとみんな身に染みて分かっているんですね。
そうして出てきた概念が、あの有名なRubyOnRailsのキャッチフレーズ、

「設定より規約」

です。

一言で言いますと、「ファイル名、クラス名を合わせておけばXMLは省略出来るよ」というものです。

RubyOnRailsの命脈を引き継ぐPlayFramworkもこの思想がありまして、膨大な設定ファイルを多数記述しなくても、ある程度の命名規約に則してソースを作っておくだけでとりあえず動いてくれる。

もう「設定より規約」はRubyOnRailsがどうこうと言うより、Webシステム業界の基本概念と言って良いくらいになってきましたね。

今回はPlayFramworkでURL制御をする為のやり方を検証してみたいと思います。

コントローラ


まず最初にリクエストを叩いた直後に呼び出されるクラス「コントローラ」を作成します。

public class LoginController extends Controller {
 /**
  * @return Result
  */
 public Result index() {

  Form userForm = Form.form(LoginForm.class);

  return ok(views.html.layout.login.render(userForm));
 }

}


命名規約と言っておいてなんですが、実際には「Controllerクラスを継承する」という点だけ守っておけばOKです。

しかし、ここで特徴が一つ。
コントローラを格納するcontrollersパッケージはルート直下に置くのがオススメです。


普通はJavaのパッケージって言うと組織を表現する長~いフルパスを指定するものじゃないですか?
弊社ジェニシスで言えば「jp.co.net.genesis.controllers」というパッケージにするのがJava業界のお約束ですが、PlayFrameworkはいきなり「controllers」だけにするのが普通です。

理由は後述します。

routesファイル


クラスを作ったところで、次にURLの定義に移ります。

上記「LoginController」のメソッド「index」とURLリクエストを結びつけるには、confフォルダ配下のroutesファイルに以下のように記述すればOKです。


GET     /                           controllers.LoginController.index()
GET     /login/                           controllers.LoginController.index()


見た目そのままかと。簡単ですね。

RubyOnRailsと違い「LoginというURLを打ち込めば自動的にLoginクラス」という挙動はありません。
「設定より規約」の思想を受け継ぎつつも、この辺りは「規約より設定の方が便利」という崩しが入っているんですね。

ほら、上記の例だと「URLに何も入力しなかったら自動的にログインを表示」という挙動も簡単に表現できます。
このくらいが自動化するのに丁度良い塩梅だと考えたのでしょう。

また、従来のStruts1のAction方式が「1URL1アクションクラス」だったのに対し、PlayFrameworkのコントローラはメソッド分けにも対応しています。
なのでクラス数が膨大に増えていってしまう問題が解消されている一方、URL設計をちゃんとやらないと1クラスにメソッドが大量終結してしまう欠点があります。
この辺りは設計者の腕の見せ所ですね。

しかし、ここで注意事項。

  • controllers.LoginController.index()

「controllers」という文字がありますね。そう、このroutesファイルの記述はフルパスなんですよ。
これが上記でルート直下をオススメした理由です。

もし「jp.co.net.genesis.controllers」と書いてしまうと、記述がこうなります。

  • jp.co.net.genesis.controllers.LoginController.index()

長いんですよ。
もちろん1システムにURL数は数十~数百まで沢山ありますから、もし会社名まで含めたパッケージにしてしまうと、routesファイルは以下のようになってしまいます。


  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()
  • jp.co.net.genesis.controllers.xxx.xxxx()

これは冗長でしょう!!

気にしなければ良いと言えばそれだけなのですが、PlayFrameworkは軽快にアプリケーションを作っていくという思想がありますから、「フルパスがどれだけ長かろうがコピペするだけなんだから作業効率は変わらん」ということは分かりつつも、
出来るだけ記述する文字数を減らすのがPlayFramework。

伝統的なJavaエンジニアにはちょっと気持ち悪いですが、ここはルートフォルダ直下にcontrollersパッケージを置くのがPlayFrameworkの標準かな、という所感です。

終わりに


これでURL制御は終わりです。
PlayFrameworkの簡単さがよく分かる一例だったと思います。

引き続きPlayFrameworkの検証を進めます。

2016年9月20日火曜日

【JavaでPlayFramwork2.4.6】ログ出力とフィルター

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

業務多忙により更新が停滞していましたが、再びPlayFrameworkの連載を復活します。

ログ出力

システムを構築する時に最初にどこから作っていくか、というところは個人の趣味があるところかと思いますが、私の場合はまずログ出力から固めていきます。
今回はログ出力についてご紹介します。

出力本体

PlayFrameworkのログ出力と銘打ってこんな記事を書いていますが、PlayFrameworkはJavaを内包していますから、早い話が単なるLog4jなんですよね。


PlayFrameworkの中には最初からLog4jが入っていますので、素直にコレを使えば良いかと。
log4jの標準に従ってlogback.xmlを設置すればその通りに動いてくれるようになります。

この辺りはPlayFrameworkの話ではなくlog4jそのままの話ですので、この記事でわざわざ記述するほどの話ではないかと。

今回はここから一歩踏み込みまして「リクエストの開始と終了のログ」を出力する方法に行ってみたいと思います。

ロギングフィルター


純粋なJavaのフレームワークであれば、リクエストの開始と終了は「doGet」とか「doPost」のメソッドの最初と終了にログを出力するようAbstractの親クラスを作るだけの話ですが、PlayFrameworkはそういう風に出来ていませんのでちょっとしたコツが必要になります。

そのテクニックを「フィルター」と言います。


フィルターとは、前回でご紹介したGrobal.javaに適応するものでして、
全てのリクエストの開始時に呼び出される共通処理を提供する機能です。

使い方はGrobal.javaのメソッド「filters()」にフィルタークラスをセットするだけでOKです。

 /**
  * Get the filters that should be used to handle each request.
  */
 @Override
 public  Class[] filters() {

  Class[] clazz = new Class[1];
  clazz[0] = RequestLogFilter.class;

  return clazz;
 }

これで全てのリクエストの開始時に「RequestLogFilter」が呼び出されますので、そこにログ出力の処理を書けば良いわけですね。
ただし、Filterはscalaで書かなければなりません。

PlayFrameworkでJavaを選択したプロジェクトであってもFilterクラスはscalaのみなのでご注意を。

しかしですので、Filterというのは開始時に呼び出されるものであって、終了時には呼び出されません。
よって「リクエストの終了ログはどうやって出すの?」という問題が発生します。

これにつきましてもPlayFrameworkとscalaならではの機能で実現してくれます。

traitとインターセプター


以下が私が使用しているログフィルターです。

class RequestLogFilter extends EssentialFilter {

  def apply(nextFilter: EssentialAction) = new EssentialAction {

    def apply(requestHeader: RequestHeader) = {

      def isTarget = RequestLogFilterUtils.isLonningTarget(requestHeader.uri);

      val startTime = System.currentTimeMillis

      if (isTarget) {
        Logger.info(RequestLogFilterUtils.getStartLog(requestHeader.method, requestHeader.uri))
      }

      nextFilter(requestHeader).map { result =>

        val requestTime = System.currentTimeMillis - startTime

        if (isTarget) {
          Logger.info(RequestLogFilterUtils.getEndLog(requestHeader.method, requestHeader.uri, result.header.status, requestTime))
        }

        result.withHeaders("Request-Time" -> requestTime.toString)

      }

    }

  }

}


RequestLogFilterUtilsというのは、私はscalaが不見識ですので文字列加工機能部分だけJavaに切り出しているだけなので気にする必要はありません。
今回のポイントはこちら。

nextFilter(requestHeader).map { result =>


nextFilterです。

resultという単語が出ていることから察せられるとおり、この中身がリクエストの終了時に呼び出されることになります。

?????

Javaエンジニアには意味不明な機構ですね。

これはscalaが搭載している「trait」という機能でして、Javaで言うところのインターフェースに近い性質を持つものです。
ただし、インターフェースほどカッチリとはしておらず、より柔軟性を持たせたものである模様。

ともかく、流れたとしては、

  1. リクエストが来る。
  2. リクエストをフィルターでインターセプト(傍受)する。
  3. リクエストにフィルターで定義した機能を挟み込む。

こういうちょっとメタっぽい形でリクエストにフィルター機能を持たせます。

そして開始ログを提供するメイン部分は普通にそのまま実行。
終了ログ部分は「trait」を使って定義しておいて、リクエスト終了時に後から呼び出す(chainするように)ようにフィルターの親クラスである「EssentialFilter」が作られている、とこういう構図なようです。

むむ。
これはPlayFrameworkの中でもかなりscala寄りの部分なようですね。
本格的に理解を深めるにはscalaの勉強を深めなければならなさそうですが、この連載は「JavaでPlayFramwork」なので掘り下げはこの辺り留めたいと思います。

終わりに


引き続きPlayFrameworkの勉強を進めます。