Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

マイクロフレームワークEnkan(とKotowari)ではじめるREPL駆動開発

2,183 views

Published on

JJUG CCC 2016 Spring CD-7 のセッションスライドです。

Published in: Technology
  • Be the first to comment

マイクロフレームワークEnkan(とKotowari)ではじめるREPL駆動開発

  1. 1. マイクロフレームワーク Enkan(とKotowari)ではじめる REPL駆動開発 kawasima JJUG CCC 2016 Sping CD-7
  2. 2. Enkan http://enkan.github.io/
  3. 3. Background
  4. 4. A new server-side framework Why now? The twelve factor app Microservices 主流のJavaフレームワークで大丈夫か? Focus on mean time to recovery
  5. 5. Requirements Java8 以上 Maven3以上
  6. 6. Enkan's backbone Connect javascript Ring clojure duct clojure liberator clojure Rack ruby Component clojure Enkan Kotowari Middleware / Component Web application framework based on Enkan (Routing, HTML Template, ...)
  7. 7. Trade-off Implicit utility 暗黙的な便利機能 High level dependency injection Easy configuration by many annotations Understability 理解のしやすさ Explicit declaration
  8. 8. Concepts
  9. 9. Key Concepts Explicitness 明示的であること Ease of deveopment 開発がしやすいこと Ease of Operation 保守運用が簡単であること
  10. 10. Explicitness Middleware pattern No configuration files Anti-blackbox Avoid annotation hell
  11. 11. Middleware Applicationの共通なコードはすべてMiddlewareに集約されている Filter? Interceptor? Containerの機能? など迷うことが無くなる app.use(new DefaultCharsetMiddleware()); app.use(new MetricsMiddleware<>()); app.use(NONE, new ServiceUnavailableMiddleware<>(     new ResourceEndpoint("/public/html/503.html"))); app.use(envIn("development"), new StacktraceMiddleware());
  12. 12. Middleware Params NestedParams MultipartParams Session Conversation Flash Normalization Authenticate ContentNegotiation SerDes Routing ValidateForm ControllerInvoker Trace その他、多くのMiddlewareがあります http://enkan.github.io/reference/middlewares.html
  13. 13. Predicate ここが他のミドルウェアフレームワークと違う 以下のPredicateが標準で利用可能 None 全てのリクエストに適用しない Any 全てのリクエストに適用する Path パス、methodのマッチしたものだけ Authenticated 認証されたリクエストのみ Permission 権限を持っているリクエストのみ Env 指定した環境のみ 適用するミドルウェアの条件をカスタマイズできる
  14. 14. Predicateの合成 Predicateはjava.utilfunction.Predicateの実装なので、 negate(), and(), or()で合成が可能。 app.use(and(path("^/guestbook/"), authenticated().negate()), (Endpoint<HttpRequest, HttpResponse>) req -> HttpResponseUtils.redirect("/guestbook/login?url=" + req.getUri(), HttpResponseUtils.RedirectStatusCode.TEMPORARY_REDIRECT)); ↓ /guestbook 配下のURIで、認証されていないリクエスト (をログインページにリダイレクトする)
  15. 15. No configuration files & Avoid annotaion hell ???「評判の悪かったXMLは、Annotationに形を変 え生き残っていると言えよう」 Java8 Syntaxを活かせば、DSL的に書けるようになる
  16. 16. Routes routes = Routes.define(r -> { r.get("/").to(HomeController.class, "index"); r.get("/login").to(LoginController.class, "index"); r.post("/login").to(LoginController.class, "login"); r.scope("/admin", admin -> { admin.resource(UserController.class); }); }).compile(); 例えばルーティングの定義 割と可読性高いのでは!?
  17. 17. EnkanとKotowariで使うAnnotation @Inject コントローラのフィールドにComponentをインジェクションする ためのマーカー @Named 同型のコンポーネントを区別する場合にのみ使用 @Transactional コントローラメソッドをトランザクション境界にする
  18. 18. Anti Blackbox Component, Middlewareの初期化は すべて明示的にnewする 黒魔術を使わない バイトコードを触る Proxy
  19. 19. Default or Using BeanBuilder builder(new UndertowComponent()) .set(UndertowComponent::setPort, 77777) .build(); 例えばポートをデフォルトから変更する場合 build() コール時に、BeanValidationされるので、 MisconfigurationExceptionが発生する デフォルトで十分動作するように設定されていて、 変更する場合もBeanBuilderを使っていれば 設定ミスにすぐ気づくことができる http://qiita.com/kawasima/items/0bee34fea7749aa2e776
  20. 20. Ease of development Hot reloading Easy to realize misconfiguration Trace the request / response
  21. 21. Hot Reloading  Dispose ClassLoader Seasar2型のHot deployと同じ方式だが、毎リクエスト破棄するのでは なく、REPLから/resetを実行したタイミングで再ロードされる。 /autoresetするとクラスの変更を監視し、自動でResetしてくれる  将来的には、JVM containerを使うことで、より高速かつ 安全にリロード可能になる (詳しくはJDT2016でお話します)
  22. 22. Exception MisconfigurationException 設定のミスと思われるものは問題と対策をもった専用の Exceptionを出力する。
  23. 23. Trace the request/response どのミドルウェアをとおり、 どこでどれだけ時間がかかっているのかを 1リクエスト毎に可視化して表示できる
  24. 24. Ease of Operation Start application quickly Metrics REPL
  25. 25. Start application quickly Enkan application starts 1~3 sec Causes starting application is slow Scanning classes Loading and parsing many configurations
  26. 26. Metrics Dropwizard metrics アクティブなリクエスト数 エラーになったリクエスト数 1リクエストあたりの性能 スループット View in the REPL
  27. 27. REPL Connect a remote Enkan application Start, stop, restart a server Show the middlewares Change the predicate of a middleware Show the routes Show the metrics Restart components automatically Compile sources
  28. 28. Why not JShell 現段階では以下の問題があり未対応 動いているプロセスにアタッチできない コマンドを追加できない 将来的にはJShell REPL対応する予定です。 (多分 Java 10)
  29. 29. Show the routes ルーティングの定義を一覧化します enkan> /routes app GET / {controller=class hoge.controller.IndexController, action=index} GET /product/ {controller=class hoge.controller.ProductController, action=index} GET /product/:id {controller=class hoge.controller.ProductController, action=show} GET /product/new {controller=class hoge.controller.ProductController, action=newForm} POST /product/ {controller=class hoge.controller.ProductController, action=create} GET /product/:id/edit {controller=class hoge.controller.ProductController, action=edit} PUT /product/:id {controller=class hoge.controller.ProductController, action=update} DELETE /product/:id {controller=class hoge.controller.ProductController, action=delete}
  30. 30. Show the metrics enkan> /metrics -- Active Requests ---------------------------------- count = 16 -- Errors ------------------------------------ count = 0 mean rate = 0.00 events/s 1-minute rate = 0.00 events/s 5-minute rate = 0.00 events/s 15-minute rate = 0.00 events/s -- Request Timer ----------------------------- count = 23815 mean rate = 176.33 calls/sec 1-minute rate = 176.33 calls/sec 5-minute rate = 73.44 calls/sec 15-minute rate = 24.78 calls/sec min = 0.00 sec max = 0.29 sec mean = 0.01 sec stddev = 0.02 sec median = 0.00 sec 75% <= 0.00 sec 95% <= 0.04 sec 98% <= 0.07 sec 99% <= 0.09 sec 99.9% <= 0.29 sec
  31. 31. Show the middlewares enkan> /middleware app list ANY defaultCharset (enkan.middleware.DefaultCharsetMiddleware@64596fc4) NONE serviceUnavailable (enkan.middleware.ServiceUnavailableMiddleware@252e9fc0) env = {development} stacktrace (enkan.middleware.devel.StacktraceMiddleware@20ef35b2) env = {development} traceWeb (enkan.middleware.devel.TraceWebMiddleware@1b7068c2) ANY trace (enkan.middleware.TraceMiddleware@34f80327) ANY contentType (enkan.middleware.ContentTypeMiddleware@65f95ae7) env = {development} httpStatusCat (enkan.middleware.devel.HttpStatusCatMiddleware@3ce2b14f) ANY params (enkan.middleware.ParamsMiddleware@244cf869) ANY multipartParams (enkan.middleware.MultipartParamsMiddleware@50d936fc) ANY methodOverride (enkan.middleware.MethodOverrideMiddleware@7f586062) ANY normalization (enkan.middleware.NormalizationMiddleware@5619ce0f) ANY nestedParams (enkan.middleware.NestedParamsMiddleware@1555fb15) ANY cookies (enkan.middleware.CookiesMiddleware@c225195) ANY session (enkan.middleware.SessionMiddleware@30c56b03)
  32. 32. Change the predicate of a middleware enkan> /middleware app list ANY defaultCharset (enkan.middleware.DefaultCharsetMiddleware@ NONE serviceUnavailable (enkan.middleware.ServiceUnavailableMi ANY stacktrace (enkan.middleware.StacktraceMiddleware@545872dd enkan> /middleware app predicate serviceUnavailable ANY enkan> /middleware app list ANY defaultCharset (enkan.middleware.DefaultCharsetMiddleware@ ANY serviceUnavailable (enkan.middleware.ServiceUnavailableMid ANY stacktrace (enkan.middleware.StacktraceMiddleware@545872dd 現在はすべてのリクエストについて非適用 すべてのリクエストについて有効にする
  33. 33. Core parts
  34. 34. Architecture
  35. 35. Dependency policy 依存ライブラリは極力少なく enkan-core slf4j-api javaee-api enkan-webenkan-system cache-apijline msgpack
  36. 36. Component Singleton scope Field injection Start/stop lifecycle Componentとはアプリケーションの状態を管理するもの Componentとはその状態を安全に開始、終了できるもの
  37. 37. Available components Undertow / Jetty Web server HikariCP DataSource Doma2 O/R Mapper Flyway Database migration Freemarker / Thymeleaf HTML Templating Jackson Bean converter Metrics Metrics
  38. 38. Enkan System Minimal DI container EnkanSystem.of( "doma", new DomaProvider(), "jackson", new JacksonBeansConverter(), "flyway", new FlywayMigration(), "template", new FreemarkerTemplateEngine(), "metrics", new MetricsComponent(), "datasource", new HikariCPComponent( OptionMap.of("uri", "jdbc:h2:mem:test")), "app", new ApplicationComponent( "kotowari.example.MyApplicationFactory"), "http", builder(new UndertowComponent()) .set(UndertowComponent::setPort, Env.getInt("PORT", 3000)) .build() ).relationships( component("http").using("app"), component("app").using("datasource", "template", "doma", "jackson", "metrics"), component("doma").using("datasource", "flyway"), component("flyway").using("datasource") );
  39. 39. What's injected to Controller Field injection Component Parameter injection Parameters (Query string & Post body) Session Flash User principal Conversation / Conversation state Request body object
  40. 40. Controller code public class CustomerController { @Inject private TemplateEngine templateEngine; @Inject private DomaProvider daoProvider; @Inject private BeansConverter beans; public HttpResponse index() { CustomerDao customerDao = daoProvider.getDao(CustomerDao.class); List<Customer> customers = customerDao.selectAll(); return templateEngine.render("customer/list", "customers", customers); } public List<Customer> list() { CustomerDao customerDao = daoProvider.getDao(CustomerDao.class);
  41. 41. Session Selectable session store In-memory JCache Separate between the request session and response session リクエストセッションとレスポンスセッションが分離しているのが HttpSessionとの大きな違い
  42. 42. Sessionを使ったコード public HttpResponse login(Parameters params, Conversation conversation) { if (!conversation.isTransient()) conversation.end(); CustomerDao dao = daoProvider.getDao(CustomerDao.class); String email = params.get("email"); Customer customer = dao.loginByPassword(email, params.get("password" if (customer == null) { return templateEngine.render("guestbook/login"); } else { Session session = new Session(); session.put("principal", new LoginPrincipal(email)); return builder(redirect(GuestbookController.class, "list", SEE_OTHER)) .set(HttpResponse::setSession, session) .build(); } }
  43. 43. Conversation 入力-確認-完了のような遷移で、Conversation IDを 発行し、それに状態をもつことができる Servlet APIのHttpSessionのように、複数タブの操 作で状態が混ざりあうことがない 二重サブミット対策にセッションを使う必要がない
  44. 44. Routing Rails-like scopeによるグルーピングやresourceによるRESTfulな ルーティングの定義が可能 Routes routes = Routes.define(r -> { r.get("/").to(HomeController.class, "index"); r.scope("/admin", admin -> { admin.resource(UserController.class); }); }).compile();
  45. 45. Generate routes Routes routes = Routes.define(r -> { r.get("/a/b/").to(TestController.class, "index"); r.get("/a/b/:id").to(TestController.class, "show"); }).compile(); // Generates "/a/b/" routes.generate(OptionMap.of("controller", TestController.class, "action", "index")); // Generates "/a/b/1" routes.generate(OptionMap.of("controller", TestController.class, "action", "show", "id", 1));
  46. 46. Content Negotiation RESTful APIをEasyに作る JAX-RSのBodyWriter, BodyReaderがそのまま利用できる。 public List<Customer> list() { CustomerDao customerDao = daoProvider                 .getDao(CustomerDao.class); return customerDao.selectAll(); }
  47. 47. Getting Started http://enkan.github.io/getting-started.html
  48. 48. Maven archetype 最小限のアプリケーションを出力します デフォルトコンポーネントを組み込んだEnkanSystem REPLサーバを起動するMain % mvn archetype:generate -DarchetypeGroupId=net.unit8.enkan -DarchetypeArtifactId=kotowari-archetype -DarchetypeVersion=0.1.0-beta2
  49. 49. Scaffolding テーブル(Flyway migration)の生成 CRUDアプリケーションの生成 enkan> /generate table PRODUCT (id identity primary key, name varchar(255)) enkan> /generate crud PRODUCT
  50. 50. Amagicman Yeoman的なものをJVMで。 Yoeman含むよくあるScaffoldingツールとは違い、 テンプレートはピュアJavaファイル つまり、Amagicmanのタスクは、テンプレートを いじること https://github.com/kawasima/amagicman
  51. 51. Conclusion
  52. 52. Javaでも結構REPL-Drivenできそう 仕事でJavaで書かなきゃいけないClojure好きな人は 是非使ってみてください Java EE、Spring派の方も、ポートフォリオとして Enkanを加えていただけるとウレシイです
  53. 53. さぁ、みなさんも、 EnkanとKotowariに 導かれるのです… 円 環 理

×