LoginSignup
0
0

More than 5 years have passed since last update.

ClojureでDropwizardを使う

Last updated at Posted at 2015-01-09

ClojureでDropwizardを使う

Motivation

  • Clojureでも始めたい
  • それなら使い慣れたDropwizardでも使ってAPIでも作ってみよう

結論

  • io.dropwizard.Applicationを継承する部分はJavaで書く
  • それ以外はClojureで書いても大丈夫

構成

leiningenを使っています。
JavaとClojureのソースを分けるため、src下でさらにディレクトリを区切っています。

  ./
    resources/
      test.yml
    src/
      clojure/
        test2/
          Test2Configuration.clj
          Test2Resource.clj
      java/
        test2/
          Test2Service.java
    project.clj

Test2Service.javaの中でClojureで作成したモジュールを参照しているため、先にClojureのモジュールをコンパイルするようにしています。

project.clj
(defproject test "0.1.0-SNAPSHOT"
  :description "Dropwizard Test Project"
  :url "http://example.com/FIXME"
  :license {:name "MIT License"
            :url "http://opensource.org/licenses/MIT"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [io.dropwizard/dropwizard-core "0.7.1"]]
  :source-paths ["src/clojure"]
  :java-source-paths ["src/java"]
  :prep-tasks [["compile" "test2.Test2Configuration"]
               ["compile" "test2.Test2Resource"]
               "javac"]
  :main test2.Test2Service)

普通のJavaBeanっぽくアクセスできるようにしています。(Test2Configurationに値が設定されるのは起動時のみです)

Test2Configuration.clj
(ns test2.Test2Configuration
  (:import [com.fasterxml.jackson.annotation JsonProperty])
  (:gen-class :name test2.Test2Configuration
              :extends io.dropwizard.Configuration
              :init init
              :state template
              :methods [[getTemplate [] String]
                        [^{JsonProperty {}} setTemplate [String] void]]))

(defn -init [& args]
  [nil (ref "")])

(defn -getTemplate [this]
  @(.template this))

(defn -setTemplate [this template]
  (dosync
    (ref-set (.template this) template)))

アクセス部分です。gen-class部分にアノテーションが集中します。

Test2Resource.clj
(ns test2.Test2Resource
  (:gen-class :name ^{javax.ws.rs.Path "/test"
                      javax.ws.rs.Produces ["text/plain"] #_javax.ws.rs.core.MediaType/TEXT_PLAIN}
               test2.Test2Resource
              :init init
              :constructors {[test2.Test2Configuration] []}
              :state template
              :methods [[^{javax.ws.rs.GET {}} hello [] String]]))

(defn -init [template]
  [nil template])

(defn -hello [this]
  (.getTemplate (.template this)))

Javaのソースです。

TestService.java
package test2;

import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

public class Test2Service extends Application<Test2Configuration> {

    public static void main(String...args) throws Exception {
        new Test2Service().run(args);
    }

    @Override
    public void run(Test2Configuration configuration, Environment environment) throws Exception {
        System.out.println("run");

        environment.jersey().setUrlPattern("/api/*");

        environment.jersey().register(new Test2Resource(configuration));
    }

    @Override
    public void initialize(Bootstrap<Test2Configuration> bootstrap) {
        System.out.println("initialize");
    }
}

プロパティファイルです。

test.yml
template: hello world!

実行

lein run server resource\test.yml

とかやればサーバが動きます。
http://localhost:8080/api/test にアクセスするとtest.ymlで定義した文字列が表示されます。

なぜJavaを使ったか

Test2Service.javaのClojure版がTestService.cljです。

TestService.cjj
(ns test.TestService
  (:import [io.dropwizard.setup Bootstrap]
           [io.dropwizard.setup Environment])
  (:gen-class :extends ^{:parameters [test.TestConfiguration]} io.dropwizard.Application
              :init init
              :constructors {[] []}
              :main true))

(defn -init [& args]
  [[] []])

(defn -main [& args]
  (.run (test.TestService.) (into-array args)))

(defn -run [this configuration environment]
  (println "run")
  (.setUrlPattern (.jersey environment) "/api/*")
  (.register (.jersey environment) (test.TestResource. configuration)))

これを実行すると起動直後にjava.lang.IllegalStateExceptionで異常終了します。

StackTrace
Exception in thread "main" java.lang.IllegalStateException: Cannot figure out type parameterization for test.TestService, ...
        at clojure.lang.Compiler.load(Compiler.java:7142)
        ...(省略)...
        at clojure.main.main(main.java:37)
Caused by: java.lang.IllegalStateException: Cannot figure out type parameterization for test.TestService
        at io.dropwizard.util.Generics.getTypeParameter(Generics.java:62)
        at io.dropwizard.Application.getConfigurationClass(Application.java:30)
        at io.dropwizard.cli.ServerCommand.<init>(ServerCommand.java:25)
        at io.dropwizard.Application.run(Application.java:68)
        at test.TestService$_main.doInvoke(TestService.clj:13)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at clojure.lang.Var.invoke(Var.java:383)
        at user$eval5.invoke(form-init1306515106957402759.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:6703)
        at clojure.lang.Compiler.eval(Compiler.java:6693)
        at clojure.lang.Compiler.load(Compiler.java:7130)
        ... 11 more

Javaで作ったクラスとClojureで作ったクラスをjavapで逆アセンブルして比較してみます。

TestServiceをjavap
public class test.TestService extends io.dropwizard.Application {
  public static {};
  public test.TestService();
  public java.lang.Object clone();
  public int hashCode();
  public java.lang.String toString();
  public boolean equals(java.lang.Object);
  public void initialize(io.dropwizard.setup.Bootstrap);
  public java.lang.String getName();
  public void run(io.dropwizard.Configuration, io.dropwizard.setup.Environment);
  public static void main(java.lang.String[]);
}
Test2Serviceをjavap
public class test2.Test2Service extends io.dropwizard.Application<test2.Test2Configuration> {
  public test2.Test2Service();
  public static void main(java.lang.String...) throws java.lang.Exception;
  public void run(test2.Test2Configuration, io.dropwizard.setup.Environment) throws java.lang.Exception;
  public void initialize(io.dropwizard.setup.Bootstrap<test2.Test2Configuration>);
  public void run(io.dropwizard.Configuration, io.dropwizard.setup.Environment) throws java.lang.Exception;
}

比較するとio.dropwizard.Applicationio.dropwizard.Appilcation<test2.Test2Configuration>が違います。
もとのio.dropwizard.Application<T>で型パラメータを受け取るように設計されていて、その型パラメータをあちこち持って回るつくりになっています。

最終的にはここで例外を吐いています。
https://github.com/dropwizard/dropwizard/blob/master/dropwizard-util/src/main/java/io/dropwizard/util/Generics.java#L35

Clojureでコンパイルした方は型パラメータの情報が消えてしまっているので、io.dropwizard.util.Generics.getTypeParameterで実行時にエラーとなっています。

対策

先にも書いたのですが、io.dropwizard.Applicationを継承する部分だけJavaで書いて、残りはClojureで書きます。
JavaからClojureを使うので、先にClojureのソースをコンパイルするようにしています。

Javaで使っていたからDropwizardを使ってみましたが、Liberatorを使うのが良いのかもしれません。

参考

extend/implement parameterized types (generics)
http://dev.clojure.org/jira/browse/CLJ-970

Is it possible to extend a parameterized type using gen-class?
https://groups.google.com/forum/#!topic/clojure/Xv1pKATfP0c

Liberator
http://clojure-liberator.github.io/liberator/

heion (RESTful Clojure with Dropwizard)
https://github.com/jblomo/heion
今のバージョンだとこの方法で動かない・・・・

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0