経緯
上司兼メンターから、
との助言をいただきました。なぜそう言われたか、については端折ります。そこで、Scala製オープンソースで有名なものと言えばPlay framework
だろうと思い至り、読み始めました。仕事でも使っていますし、ちょうどよいです。
Play frameworkとは
バージョンは2.8
が最新ですが、日本語ではないので2.4
を貼っています。
一言で言うと、MVCをベースにしたWebフレームワークです。他の言語だと
- C#: ASP.NET MVC
- Ruby: Ruby on Rails
- Python: Django, Flask
などがあります。MVCパターンについての説明は世の中にあふれていますので、この記事に辿り着くような方であれば自力で調べられると思います。(丸投げ)
Play frameworkの特徴
Play framework自体は、JavaとScalaで書かれています。そして、利用する際もJava及びScalaのどちらからでも利用できます。
Javaでの利用
Scalaでの利用
違いはそれほどありませんが、Scalaでは言語機能のおかげでより楽に使えるようになっているという印象を受けます。
利用する場合、言語によって名前空間参照先が異なります。
Java:play.mvc
など
Scala:play.api.mvc
など
これ以上の詳細は、この記事の主題から外れますので、気になる方は各自ドキュメントを読むなどお願いします。
どこを読むのか
まずは、それほど難しくない、かつ、私がそれなりに使う部分にしました。結果、play.api.mvc.Call
になりました。
Callとは
Call
は、リバースルーティングする際に取得できるオブジェクトの型です。たいていのWebフレームワークでは自身のURLとControllerの対応関係を定義します。Play frameworkの場合、routes
というファイルに
GET / homeController.home() GET /shop shopController.list()
のような形で定義します。
このとき、例えば/shop
へのリンクを他のページに埋め込む場合、
<a href="/shop">shop</a>
という形で文字列埋め込みするのはあまり得策とは言えません。これをやってしまうと、他ページリンクする際にURL構成を知らなければなりませんし、万が一サイトのURL構成を変更した場合に、変更箇所の特定が困難になります。
Play frameworkでは、一度コンパイルするとroutes定義からルーティング定義のオブジェクトを生成してくれます。その定義オブジェクトの個々のURLを指すのが、Call
オブジェクトです。Twirlで使用する場合は
<a href="@routes.shopController.list()">shop</a>
のような形になります。これで、/shop
が/shop-list
に変わったような場合であっても使用箇所には影響が出ません。
基本的にはフレームワークが自動生成するものであり、Javaから利用する場合はコンストラクタすらありません。自動生成されたCallにfragmentを追加した後のCallを生成する、といった程度です。
Callの定義
Call自体は、二か所に定義があります。
play.mvc.Call
play.api.mvc.Call
違いですが、先述の通り、前者がJava用、後者がScala用です。
Java定義
playframework/Call.java at master · playframework/playframework · GitHub
下記は、コメントを除いた部分です。
public abstract class Call { public abstract String url(); public abstract String method(); public abstract String fragment(); // 以下、公開メソッド }
Scala定義
playframework/Call.scala at master · playframework/playframework · GitHub
case class Call(method: String, url: String, fragment: String = null) extends play.mvc.Call { // 省略 }
Javaでabstract定義されていたフィールドをcase classの引数で受け取るようになっています。
言えること
上記の通り、Javaで定義しているのは抽象部分、Scalaで定義しているのは具象部分となっています。そして、Scala部分については、よりScalaで使いやすいよう、定義が増えている個所があります。
例として、absoluteURL
というメソッドは、Javaだと
public String absoluteURL(Http.Request request) public String absoluteURL(Http.Request request, boolean secure) public String absoluteURL(boolean secure, String host)
の3つのオーバーロードがあります。Scalaではこれらに加えて
def absoluteURL()(implicit request: RequestHeader): String def absoluteURL(secure: Boolean)(implicit request: RequestHeader): String
という定義が追加されています。implicit parameter
が定義されていることにより、
// どこかで implicit val request = ... みたいな定義があること前提 val url = call.absoluteURL
という感じで、request
変数を意識せずに使えます。よりScalaらしく扱えます。便利。
気になった部分
Java定義が抽象、Scala定義が具象なのはわかりました。しかし、Javaのほうで気になった定義がありまして。
public Call unique() { return new play.api.mvc.Call(method(), this.uniquify(this.url()), fragment()); }
のように、Scala(play.api.mvc.Call
で定義しているコンストラクタを見に行っています。これっていいんだっけ…?という疑問が。
似た定義は他の場所にもあります。例えば
play.mvc.Filter
public abstract class Filter extends EssentialFilter { public play.api.mvc.Filter asScala() { return new play.api.mvc.Filter() { // 以下略
のような感じです。これはそういうものなのだろうと考えることにします。(おい)
Scalaが具象と考えれば特に変でもないですし、私ごときが良い方法を思いつくはずもないです。今後さらに読み進めるときに何か気づくかもしれません。Scalaで実装が提供されることが前提なら、全く問題ない方法です。
気になった部分その2
JavaのabsoluteURL
に
public String absoluteURL(boolean secure, String host) { return "http" + (secure ? "s" : "") + "://" + host + this.url() + this.appendFragment(); }
というメソッドがあり、Scalaにも
def absoluteURL(secure: Boolean)(implicit request: RequestHeader): String = "http" + (if (secure) "s" else "") + "://" + request.host + this.url + this.appendFragment
というメソッドがあります。内容が実質一緒です。DRY原則に反している感じがします。というわけで、ここは修正の上issueに上げてみようと思っています。ScalaのCallにはテストも無いため、そちらもセットで実装中です。
おわりに
Callは便利ですが、大したクラスではないです。定義も簡単です。でも、こういうところから読み進め、徐々に範囲を広げることも重要だと思うので、このペースで続けます。
今は、routes
からどうやってリバースルーティング用のソースを生成しているのか調べようと思っている最中です。
Play frameworkを読むに際し気になっているのは、JavaとScalaでいろいろ分かれている部分をどういう指針で分けて、どうやって実装しているのかです。そこに注目して読んでいきます。
次週予定
下記のどれかです。
- Django3.0が出ているので内容見てみる
- 『Design it!』感想
- Play frameworkさらに読んでみた
こうご期待(?)