Create a gist now

Instantly share code, notes, and snippets.

@monzou /slide.md Secret
Last active Oct 1, 2016

What would you like to do?

最近の Java Web 開発

@monzou

自己紹介

  • トレーディングシステムとか作ってます
  • Web アプリ開発は今回が初めて

自分が話せそうな Java ネタ

  • JVM を利用した分散計算(GridGain, Hazelcast etc ...)
  • 最近作ってる Web アプリ

今回は後者の紹介にします。

お品書き

  • PJ 概要
  • 主な使用技術
  • サーバーサイド
  • クライアントサイド
  • まとめ

PJ 概要

  • 金融機関向け Web アプリケーション
  • クライアントは IE9 ~
  • そこそこ複雑な業務ロジック
  • 他システムとの SOA 連携も想定
  • 顧客都合でサーバは WebLogic / Java 指定

アプリ概要

  • Single Page Application
  • 今後よりリッチなアプリを作る予定なので試してみた
  • 社内利用のみを想定した業務アプリなので色々割り切れた

主な使用技術(サーバー)

  • JAX-RS (Jersey)
  • Jackson
  • Hibernate
  • guice
  • guava
  • Gradle

主な使用技術(クライアント)

  • HTML 5
  • SCSS
  • CoffeeScript
  • Handlebars
  • Backbone.js
  • Require JS
  • bower
  • grunt

最近の Java WAF を調べてみた

Java WAF について詳しくなかったので調べてみた。

速攻で採用を見送ったもの

  • Struts:無いわー
  • SAStruts:S2 は最近更新頻度が落ちてるので
  • Juzu:枯れてない……
  • Ninja:枯れてない……

Struts 系は無視。Juzu, Ninja は次世代感あるが……。

採用を見送ったもの

  • Play Framework:プロダクションで使うの怖い
  • Dropwizard:非常に良い感じだが……

いずれも J2EE サーバで動かすことは意図してない。

採用候補となったもの

  • Spring MVC:現時点で Java WAF としては最有力?
  • JAX-RS:シンプルかつ拡張性が高くて魅力的

いずれも REST API を実装するのに向いている。

  • Spring MVC は Spring 周辺プロダクトとの組み合わせが可能
  • JAX-RS はシンプルなので自分で好きなように組み立てられる

JAX-RS を採用

  • Spring が好きではない(Guice 派)
  • Spring MVC は REST サポートが後付け
  • JAX-RS の方がシンプルかつ美しい
  • 自分で使いやすいコンポーネントを組み合わせて構築したい

部分的には Dropwizard の実装も参考にしました。

JAX-RS を採用

  • Web アプリなので入出力が明確
  • JAX-RS の拡張性は高い
  • フレームワークレイヤを作りこめばそこそこ綺麗に書ける

例えば

  • セッション管理(JAX-RS のフィルタを使って実装)
  • 権限チェック(JAX-RS のフィルタを使って実装)
  • トランザクション管理(Guice の interceptor と JTA を実装)
  • DTO - JSON 変換(Dropwizard を参考に Jackson で実装)
  • Validation(↑と同じ箇所で実装)

というわけで主に使用しているライブラリを紹介します。

Jackson

  • JSON・YAML 変換可能なライブラリ
  • 使いやすいし速度も十分

用途

Hibernate

数年前に作った Hibernate のラッパーをライブラリ化して使用。

特徴

このへん に纏めました。

  • ランタイムバインドを排し, 完全にタイプセーフ化(Query DSL より型安全)
  • エンティティ・メタクラス・DDL 等の自動生成
  • タイプセーフ DAO および継続クエリの実現(今回は後者は使いませんが)

更新処理

エンティティ・メタクラス等は自動生成。

ProjectMeta meta = ProjectMeta.getInstance();
Dao<Project> dao = daoFactory.createDao(meta);

dao.save(project);
dao.update(project);
dao.delete(project);

エンティティをクエリ

EntityQuery<Project> query = dao.createQuery();
List<Project> projects = query
  .orderBy(meta.id, Order.ASC);
  .list(meta.createCriteria()
    .add(meta.sequenceNo.lt(5))
    .add(meta.status.eq(ProjectStatus.ACTIVE)));

射影

ProjectionQuery<Project> query = dao.createProjectionQuery();
List<Projection<Project>> values = query.listValues(
  meta.createCriteria()
    .add(meta.users.admin.isTrue())
    .add(meta.users.status.eq(UserStatus.ACTIVE)),
  meta.id,
  meta.name
);

// projection.get(meta.name) //=> String

集約

AggregationQuery<Project> query = dao.createAggregationQuery();
AggregationFunctionProperty<Project, Long> maxUserIdProperty = Aggregations.max(meta.users.id);
Long maxUserId = query.uniqueValue(
  meta.createCriteria()
    .add(meta.id.eq(1L))
    .add(meta.status.eq(UserStatus.ACTIVE)),
  maxUserIdProperty
);

guice

軽量・高速・型安全な DI コンテナ。

  • リファクタリングが容易
  • モジュール化しやすい

jersey-servlet 使えば JAX-RS 統合も簡単。

JerseyServletModule

public class WebAppModule extends JerseyServletModule {
  @Override
  protected void configureServlets() {
    installModules();
    Map<String, String> properties = // ...
    serve(config.endpoint()).with(GuiceContainer.class, properties);
  }
  private void installModules() {
    install(new SessionModule());
    install(new PersistenceModule());
    install(new TransactionModule());
    install(new ServiceModule());
    // ...

Gradle

柔軟なビルドツール。Gradle 使わなくて良いのは小学生まで。

  • Maven より学習が容易で, Maven より柔軟
  • スクリプトの使用・動的なタスク生成等が可能
  • ビルド定義が DRY に
  • 複雑なプロジェクト構成の管理が容易
  • 頻繁にプロジェクト構成が変わる場合も対応が楽
  • Gradle Wrapper で CI サーバとの連携が簡単
  • ドキュメントが豊富
  • Eclipse Plugin も十分に使えるレベル
  • JS のビルドとの統合も容易

guava

昨今の Java 開発での必須ライブラリ。

  • 取り敢えず base, collect だけは押さえておくべき
  • Function(s), Predicate(s), Supplier(s), Optional
  • Joiner, Splitter, Objects, Strings, CaseFormat
  • ImmutableCollection, Collections2, FluentIterable, Ordering

guava あるある

Function, Predicate, Supplier を使って操作するようになる。

FluentIterable.from(users)
    .filter(User.ADMIN) // Predicate
    .transform(User.ID) // Function
    .toList();

可読性と効率のために関数を定数化することが多くなるので

  • Eclipse 等の IDE で Code Template 化する
  • guavapt みたいな APT を使う

等の対応が効果的。

guava あるある

段々 guava の機能だけでは足りなくなってくる。

// Cache 化する Function を作ったり
Function<Integer, String> fn = Functions2.concurrentCacheOf(f, true);

// RichFunction 作ったり
RichFunction<Integer, RichFunction<Double, Double>> curried = PlusFunction.INSTANCE.curried();
assertThat(curried.apply(1).apply(-0.5d), is(0.5d));
assertThat(curried.apply(1).andThen(TripleFunction.INSTANCE).apply(2d), is(9d));

他言語の経験があると楽しい。

プロジェクト構成

プロジェクト構成

  • 厳密に依存関係を整理する
  • 業務レイヤが Web を意識することが無いようにする
  • 将来的には application-rich-client とか増やしたい

(正直各クライアントが REST API 叩いても良いかなと思い始めてますが……)

開発環境

Jenkins + Gradle で継続デプロイも捗る。

関心事を分離する

public interface Session
  extends TypeSafeProperties<Session>, Serializable {
  Serializable getId();
  // ...
  void invalidate();
}
public interface SessionManager extends SessionProvider {
  Option<Session> newSession(Principal principal);
  Option<Session> removeSession();
}
  • 業務レイヤと Web レイヤの切り離し
  • HttpBoundSessionManager(webapp)
  • ThreadBoundSessionManager(batch, rich-client)

REST API 定義

@Path("/branches")
@AuthenticationRequired // Session の有無
@AuthorityRequired(Authorities.BRANCH_MANAGEMENT) // 権限の有無
public interface BranchResource {

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  PageableCollectionDto<BranchDto> get(
    @ResourceParam BranchSearchConditionDto condition);

  @GET
  @Path("{id}")
  @Produces(MediaType.APPLICATION_JSON)
  BranchDto get(@PathParam("id") Long id);

REST API 定義

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  BranchDto create(@Valid BranchDto branch);

  // ...

REST API 定義

public class BranchDto
  implements Serializable, Function<Branch, Branch> {

  static final Function<Branch, BranchDto> FROM_ENTITY
  static final Function<BranchDto, Branch> TO_ENTITY

  private Long id;
  @NotBlank
  @Pattern(regexp = "B\\d{3}")
  private String code;
  @NotBlank
  @Size(min = 1, max = 20)
  private String name;

REST API 定義

public class BranchSearchConditionDto
extends PaginationConditionDto implements Serializable {

  @QueryParam("status")
  private Status status;
  @QueryParam("includeDeleted")
  private Boolean includeDeleted;

  public Criteria<Branch> createCriteria() {
    // Meta クラスに Criteria を定義することも可能
    BranchMeta meta = BranchMeta.getInstance();
    Criteria<Branch> criteria = meta.createCriteria();
    // ...
    return criteria;
  }
  // ...

API Implementation

単純なマスタなら DTO を介さずに Entity をそのまま JSON 変換しても良いですが。

@Transactional
public class BranchResourceImpl
  implements BranchResource {

  private static final Configuration CONFIGURATION =
  // ... load branches-resource.yml

  @Inject
  private EntityQueryService queryService;
  @Inject
  private BranchService branchService;

  // ...

API Implementation

@Override
public PageableCollectionDto<BranchDto> get(BranchSearchConditionDto condition) {
  // executeQuery(criteria, paginationCondition)
  return queryService
    .executeQuery(condition.createCriteria(), condition)
    .transform(BranchDto.FROM_ENTITY);
}
@Override
public BranchDto get(Long id) {
  return BranchDto.FROM_ENTITY.apply(find(id));
}

API Implementation

@Override
public BranchDto create(BranchDto branch) {
  try {
    return BranchDto.FROM_ENTITY.apply(
        branchService.register(
          BranchDto.TO_ENTITY.apply(branch)));
  } catch (DataUpdateException e) {
    // 業務レイヤのチェック例外 -> Web レイヤのレスポンス
    throw DataUpdateExceptionMapper.map(e);
  }
}

// ...

課題

Bean Validation (JSR-303) が使いづらい

  • 条件の組み合わせが難しい

REST API のデザインが難しい

  • 業務アプリにありがちな複雑な更新処理
  • 例えば約定の一括承認

一括更新

PUT /branches
{ branches: [ { id: 1, status: "AUTHORIZED "}, ... ] }

単純な一括更新でペイロードを最小化したければこんな感じ?

PUT /branches?id[]=1&id[]=2&id[]=3
{ status: "AUTHORIZED" }
  • ステータスを直接更新する場合はきっとこんな感じ
  • このへん参照

一括更新

  • 業務アプリでありがちな複雑な更新処理の場合は?
  • 単なるステータスの更新には留まらない
  • 編集前履歴の保存
  • ドメインロジックの実行
  • 他の様々なプロパティの更新

一括更新

API 内部で Service にディスパッチするための情報を渡す?

PUT /branches?id[]=1&id[]=2&id[]=3
{ __op__: "Authorize" }

一括更新

操作毎にトランザクションリソースをつくる?

PUT /branches/authorize?id[]=1&id[]=2&id[]=3

または

PUT /branches/authorize
{ ids: [ 1, 2, 3 ] }

クライアントサイド

はじめての JS 開発……

方針

  • 余りに複雑な技術は採用しない
  • コードに規約を持ち込みたい
  • アプリケーションのモジュール化
  • 多言語対応

Coffee Script

  • 素の JS は地獄に見えたので Alt JS, Alt CSS を採用
  • Haxe は既存コンポーネントとの組み合わせが難しそう
  • TS はまだ枯れてないので CS を採用

感想

  • CS めっちゃ書きやすいワイワイ
  • でも型は欲しいです

Backbone.js

取り敢えず JS MVC FW を調べてみた。

  • pros: 軽量かつ柔軟
  • pros: 既存ライブラリとの組み合わせが容易
  • cons: 素のままでは機能が不足

ひとこと

  • 特に学習の必要が無いので導入の敷居は非常に低い
  • インスタンスのライフサイクル管理等を作りこむ必要あり
  • イベントバスも自前で実装する必要あり

Angular JS

  • pros: リッチなデータバインディング
  • pros: CRUD 主体の業務アプリには向いている
  • cons: 独特の概念があるため学習コストがやや高そう

ひとこと

  • 最近もっとも人気がある(ように思える)
  • チームを育てられるのであれば採用したい

Ember.js

  • pros: 著名な開発者
  • pros: リッチなデータバインディング
  • pros: イケてる(ように見える)デザイン
  • cons: デバッグ地獄
  • cons: API 変更多すぎ
  • cons: Ember Data ...

ひとこと

  • 僕の期待を裏切ったな……!

Backbone.js を採用

  • JS 経験が殆ど無いので勉強も兼ねてプレーンなものを使いたかった
  • 素の JS に近いので最悪どうにか出来そう
  • そのままでは機能不足だったので自前で FW を実装

特に以下の辺りは作り込んだけど……

  • View のライフサイクル管理
  • メッセージング周り

Backbone.Marionette

  • 薄さを残したかったので FW は採用しなかったが……
  • 結局自前で FW を作ったら Marionette みたいなものになった
  • なので最近 Marionette ベースに書き直してます
  • Marionette は比較的ベストプラクティスが共有されてる

ひとこと

  • 車輪の再発明は勉強になる
  • 車輪の再発明を使うのは避けましょう(保守的な意味で)

Backbone.Marionette

  • module, reqres 等が使いやすい
  • Router を自作して reqres で逆引き可能にすると捗る
  • サブリソースを表現可能にすると更に捗る
  • 全体的に API を Promise 化しておくと捗る

所謂 linkTo 等が自動的に出来上がって幸せ。

  • execute : App.execute "resource:method"
  • navigate : App.execute "navigate:resource:method"
  • route : App.request "route:resource:method"

Backbone.Marionette

Show = Application.module "Branches.Show"
Show.Controller =
  show: (id) ->
    $.when(Application.request "session:checkAuthorities", "BRANCH_MANAGEMENT")
      .then ->
        Application.request "branch", id
      .done (branch) ->
        view = new Show.View.FormView model: branch
        Application.mainRegion.show view
        Application.execute "set:layout", "application"

Backbone.js 感想

良かったところ

  • Backbone は薄い(1500 行程度)のでハマらない
  • 既存ライブラリとの組み合わせも非常に容易

悪かったところ

  • 特にないが, 今なら Angular JS を採用するかも

bower

  • JS ライブラリ依存関係管理ツール
  • JS 版 Bundler みたいなもの
  • 無いと困るレベル

ひとこと

  • 特にないです

grunt

node.js ベースのビルドツール。

用途

  • CoffeeScript / SCSS / Handlebars の監視・コンパイル
  • JS / CSS / images 等の concat / minify, html の変換など
  • development / production 等の環境情報の埋め込み
  • livereload

などなど。

Yeoman

grunt + bower を利用したフロントエンド開発環境構築ツール。

  • ベストプラクティス的な構成を自動生成してくれる
  • これを元に改造するのがオススメ

その他に使ったライブラリ達

  • modernizr
  • lodash
  • backbone-forms
  • backgrid
  • moment
  • perfect-scrollbar
  • i18next

などなど……。

まとめ

  • JAX-RS + JS MVC は普通に開発出来る
  • Gradle 最高

ありがとうございました。

Owner

monzou commented Oct 26, 2013

思いがけず大量のブックマークありがとうございます。こちら に多少の追記・それからこちら に変換したプレゼンテーションがあります。プレゼンの都合上コード例は殆ど出していませんが、興味のある方おられましたら是非お声がけ下さい。一緒にお話しましょう!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment