Play1 の後継? Ninja Web Framework #ninja_ja
Ninja web framework というのが、少し試したところ、なかなか好印象だったのでご紹介します。Play1 に強く影響されているフレームワークなのですが、このまま育っていけば Play1 の後継候補(or 超える)に十分なれると思いました。
http://www.ninjaframework.org/
Ninja はフルスタックな Web フレームワークである点が売りですが、何でもかんでも自作はせず、土台は Servlet を使い DI は Guice、バリデーションは JSR 303、テンプレートエンジンは FreeMarker を使っいて Play とは思想の違いを感じさせます。
DB アクセスは今のところは標準でこれというのは特にないようですが、何でも好きなものを使って対応できそうですし、私はそれでよいと思います。
開発が始まったのは去年でまだまだ若いフレームワークですが、それなりに使えそうな感じであることを考えると、開発陣(数名)に勢いがありますね。9 ヶ月前の commit 履歴を見ると、現在では Play2 の重要人物であり、あの #scalaconfjp では伝説のライブコーディングを披露した James Roper さんも開発に関わっていたようです(おそらく Typesafe に join する前?)。この点も Play をウォッチしている方からすれば好材料ですね。
デモ
まずは git clone してこのデモを動かしてみてください。1.3-SNAPSHOT を 1.2 に置換すれば mvn jetty:run ですぐに動かせます。
https://github.com/reyez/ninja/tree/develop/ninja-core-demo
試してみた
とはいえ、どれくらい手軽に動かせるものかなということで http://localhost:8080/ で Hello World! を表示するまでにどれだけの準備が必要か試してみました。
結論としてはこれだけで非常に手軽でした。
├── pom.xml ├── src │ └── main │ ├── java │ │ ├── conf │ │ │ ├── Routes.java │ │ │ ├── application.conf │ │ │ └── messages.properties │ │ ├── controllers │ │ │ └── ApplicationController.java │ │ └── views │ │ └── ApplicationController │ │ └── index.ftl.html │ └── webapp │ └── WEB-INF │ └── web.xml └── target
なお ソースコード以外を src/main/java の下に置くことに抵抗がある方は src/main/resourcesの下に置けばよいと思います。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>ninja-demo</artifactId> <packaging>war</packaging> <name>ninja web framework demo</name> <parent> <groupId>org.ninjaframework</groupId> <artifactId>ninja</artifactId> <version>1.2</version> </parent> <build> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.22</version> <configuration> <contextPath>/</contextPath> <stopKey>stop</stopKey> <stopPort>8889</stopPort> <scanIntervalSeconds>1</scanIntervalSeconds> <reload>automatic</reload> <scanTargets> <scanTarget>target/classes</scanTarget> </scanTargets> </configuration> </plugin> </plugins> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*</include> </includes> </resource> </resources> </build> <dependencies> <dependency> <groupId>org.ninjaframework</groupId> <artifactId>ninja-core</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.ninjaframework</groupId> <artifactId>ninja-core-test</artifactId> <version>1.2</version> <scope>test</scope> </dependency> </dependencies> </project>
src/main/java/conf/Routes.java
ルーティング設定ですね。Play でいう conf/routes の役割です。デモにはありますが、Path パラメータなど一通りの機能は問題なくあります。
package conf; import ninja.*; import ninja.application.ApplicationRoutes; import controllers.*; public class Routes implements ApplicationRoutes { @Override public void init(Router router) { router.GET().route("/").with(ApplicationController.class, "index"); router.GET().route("/assets/.*").with(AssetsController.class, "serve"); } }
src/main/java/conf/application.conf
このファイルがないと起動時に「このファイルがないよ!」とエラーになります。
application.name=ninja demo application application.cookie.prefix=ninjaapp application.languages=en application.secret=xxxyyyzzzdummy application.session.expire_time_in_seconds=3600 application.session.send_only_if_changed=true application.session.transferred_over_https_only=false
src/main/java/conf/messages.properties
エラーメッセージなどを国際化対応で表示するために messages.ja.properties のように言語毎に配置します。これでもデフォルトのファイルが存在しない場合は「ファイルがないよ!」とエラーになります。
src/main/java/controllers/ApplicationController.java
いよいよ実装です。先ほどの Routes.java で指定した controller を配置します。Play のように static メソッドではないですが Singleton なので指向性は同じですね。ここでは Map を渡していますが Java Bean を渡すのももちろん OK です。
package controllers; import ninja.*; import java.util.*; import com.google.inject.Singleton; @Singleton public class ApplicationController { public Result index(Context context) { Map<String, Object> m = new HashMap<String, Object>(); m.put("message", "Hello, Ninja!!!"); return Results.html().render(m); } }
src/main/java/views/ApplicationController/index.ftl.html
先ほどの Map から message というキーの値を表示します。
${message}
src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>ninja</display-name> <listener> <listener-class>ninja.servlet.NinjaServletListener</listener-class> </listener> <filter> <filter-name>guiceFilter</filter-name> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class> </filter> <filter-mapping> <filter-name>guiceFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
起動
mvn jetty:run で起動します。こんな感じの出力です。モロに Play ですね。
[INFO] Starting jetty 6.1.22 ... 2013-04-06 22:22:20.548:INFO::jetty-6.1.22 2013-04-06 22:22:20.695:INFO::No Transaction manager found - if your webapp requires one, please configure one. 22:22:21.841 [main] INFO ninja.utils.SwissKnife - Could not load file conf/messages.en.properties (not a bad thing necessarily, but I am returing null) 22:22:21.843 [main] INFO ninja.i18n.MessagesImpl - Did not find conf/messages.en.properties but it was specified in application.conf. Using default language instead. _______ .___ _______ ____. _____ \ \ | |\ \ | | / _ \ / | \| |/ | \ | |/ /_\ \ / | \ / | \/\__| / | \ \____|__ /___\____|__ /\________\____|__ / web\/framework \/ \/ 22:22:21.939 [main] INFO ninja.lifecycle.LifecycleServiceImpl - Starting Ninja application... 22:22:21.943 [main] INFO ninja.lifecycle.LifecycleServiceImpl - Ninja application started in 3ms 2013-04-06 22:22:22.012:INFO::Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server [INFO] Starting scanner at interval of 1 seconds.
Ninja だけに?
ということで期待を込めて紹介しました。日本の Java コミュニティ発で Ninja が盛り上がれば楽しくなりそうです。