Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modern Java Web / SPA Development

Modern Java Web / SPA Development

JJUG CCC 2014 Spring の発表資料です。

monzou

May 18, 2014
Tweet

More Decks by monzou

Other Decks in Programming

Transcript

  1. JAVA
    MODERN
    WEB DEVELOPMENT
    JJUG CCC 2014 SPRING
    Simplex Inc, Takuro Monji

    View Slide

  2. JAVA
    MODERN
    SPA DEVELOPMENT
    JJUG CCC 2014 SPRING
    Simplex Inc, Takuro Monji

    View Slide

  3. @monzou
    Takuro Monji 文字 拓郎
    Simplex Inc.
    Financial Application Developer

    View Slide

  4. 6 MONTHS AGO …
    https://gist.github.com/monzou/e355a2c425cdad109537

    View Slide

  5. 700+ BOOKMARKS
    6 MONTHS AGO …
    https://gist.github.com/monzou/e355a2c425cdad109537

    View Slide

  6. 700+ BOOKMARKS
    60+ STARS
    6 MONTHS AGO …
    https://gist.github.com/monzou/e355a2c425cdad109537

    View Slide

  7. 700+ BOOKMARKS
    60+ STARS
    40+ STOCKS
    6 MONTHS AGO …
    https://gist.github.com/monzou/e355a2c425cdad109537

    View Slide

  8. WHY ?

    View Slide

  9. モダンな Java の 開発事情
    Java + SPA の 開発事例
    プロジェクトの 全体感
    公開情報 の 不足

    View Slide

  10. 実例を知りたい

    View Slide

  11. 目次
    プロジェクトの概要
    アーキテクチャ説明
    アーキテクチャ概要
    サーバーサイド詳細
    クライアントサイド詳細
    まとめ

    View Slide

  12. プロジェクトの概要
    アーキテクチャ
    アーキテクチャ
    サーバーサイド
    クライアントサイド
    まとめ

    View Slide

  13. 金融機関向け
    Web アプリケーション
    プロジェクト条件
    !
    ・海外拠点向け (英語)
    ・そこそこ複雑な業務ロジック
    インフラ条件
    ・Java 7
    ・WebLogic 12c
    ・Oracle 11g
    ・IE 9

    View Slide

  14. ウェブの案件は滅多に無い
    開発チームも経験値低め
    業務に関しては多少ナレッジあり
    はじめて の ウェブ案件

    View Slide

  15. 既存 Web システム の 問題点
    “構造”の欠落
    !
    ・Ajax 化に伴うフラグメント化
    ・業務レイヤの独立性が担保出来ていない
    クライアントの進歩についていけない
    !
    ・クライアントとサーバーの密結合
    ・独立して開発することが困難
    ・将来的なネイティブクライアント対応が …

    View Slide

  16. SPA (Single Page Application)
    未来のために
    !
    ・B2C の Web サービスはリッチ化が進む一方……
    ・金融業界でもリッチなクライアントは増加傾向
    ・今後のためにナレッジを蓄積したい
    クライアントとサーバーを分離
    !
    ・ステートフルな JavaScript Client + REST API
    ・ネイティブクライアントに近い

    View Slide

  17. 不安もあった……
    ノウハウの不足
    !
    ・Servlet ? Struts ? war ? WebLogic ?
    ・JavaScript?jQuery ?

    View Slide

  18. 不安もあった……
    対策
    2 週間ほど技術調査
    !
    ・jQuery の使い方を調べたり …
    ・Backbone.js のコードを読んでみたり …
    ・JS MVC FW を作ってみたり …
    ・WAF を作ってみたり …
    ノウハウの不足
    !
    ・Servlet ? Struts ? war ? WebLogic ?
    ・JavaScript?jQuery ?

    View Slide

  19. 不安もあった……
    対策
    2 週間ほど技術調査
    !
    ・jQuery の使い方を調べたり …
    ・Backbone.js のコードを読んでみたり …
    ・JS MVC FW を作ってみたり …
    ・WAF を作ってみたり …
    ノウハウの不足
    !
    ・Servlet ? Struts ? war ? WebLogic ?
    ・JavaScript?jQuery ?
    問題なさそう!
    … だけど他メンバーは大丈夫かな?

    View Slide

  20. プロジェクトの
    アーキテクチャ説明
    アーキテクチャ概要
    サーバーサイド
    クライアントサイド
    まとめ

    View Slide

  21. View Model
    Backbone.js
    Presentation
    Layer
    Resource
    JAX-RS
    Web
    Layer
    HTTP
    REST
    JSON
    Service Model
    Business
    Layer
    Infrastructure
    Database
    Persistence
    Layer
    JDBC
    IE 9 WebLogic 12c Oracle 11g

    View Slide

  22. View Model
    Backbone.js
    Presentation
    Layer
    Resource
    JAX-RS
    Web
    Layer
    HTTP
    REST
    JSON
    Service Model
    Business
    Layer
    Infrastructure
    Database
    Persistence
    Layer
    JDBC
    IE 9 WebLogic 12c Oracle 11g
    Web から切り離し
    独立性を保つ

    View Slide

  23. View Model
    Backbone.js
    Presentation
    Layer
    Resource
    JAX-RS
    Web
    Layer
    HTTP
    REST
    JSON
    Service Model
    Business
    Layer
    Infrastructure
    Database
    Persistence
    Layer
    JDBC
    IE 9 WebLogic 12c Oracle 11g
    Web から切り離し
    独立性を保つ
    API の
    提供のみ

    View Slide

  24. application-base
    application-common
    application-web
    application-batch
    web
    REST
    service
    service-implementation REST-implementation
    persistence
    domain-model domain-model-meta
    静的ファイル群
    HTML, JS, CSS …
    コンパイル時に
    実装に依存しないように
    モジュールを分割

    View Slide

  25. ポイント
    “ふつう” のレイヤリング
    !
    ・業務レイヤが Web の都合を意識しない
    ・レイヤ構造を遵守する
    ・依存関係の破綻はコンパイル時に検知する

    View Slide

  26. ポイント
    “ふつう” のレイヤリング
    !
    ・業務レイヤが Web の都合を意識しない
    ・レイヤ構造を遵守する
    ・依存関係の破綻はコンパイル時に検知する
    クライアントや API は容易に変化するが
    ドメインは簡単には変化しない

    View Slide

  27. プロジェクトの
    アーキテクチャ説明
    アーキテクチャ
    サーバーサイド詳細
    クライアントサイド
    まとめ

    View Slide

  28. 主な使用技術
    ・Gradle - Build Runner
    ・Guice - DI Container
    ・Jersey - WAF (JAX-RS)
    ・Hibernate - ORM
    ・Guava - Utilities
    ・Jackson - JSON Converter
    その他, 開発時には Jetty の埋め込みサーバーを利用して
    スタンドアローンの Java アプリ化しています
    http://qiita.com/monzou/items/eb5f25ae9b9925d3c63e

    View Slide

  29. Gradle
    知識ゼロでも使える敷居の低さ
    !
    ・Maven と違って何も知らなくても使える
    ・自動的に良く出来たビルド環境が手に入る
    全体は Gradle によるマルチプロジェクト構成
    拡張性の高さ
    !
    ・Groovy によるスクリプティング
    ・様々な自動化タスクを組み込んだり …
    ・動的に環境設定を読み込んでビルドタスクを作成したり …

    View Slide

  30. Guava
    モダン Java 開発の必須ライブラリ
    !
    ・アプリケーションのあらゆるところで多用
    ・関数型プログラミングの導入にも
    頻繁に登場するクラス群
    !
    ・Function, Supplier, Predicate, Optional …
    ・ImmutableCollection, Iterables, Ordering …
    ・Cache, Joiner, Splitter …

    View Slide

  31. Function を多用するので, 自動生成すると良い
    @Grind
    public class Trade implements Serializable {
    private final ID id;
    private String tradeNo;
    private long version;
    private boolean deleted;
    public Trade(ID id) {
    this.id = id;
    }
    public String getTradeNo() {
    return tradeNo;
    }
    public void setTradeNo(String tradeNo) {
    this.tradeNo = tradeNo;
    }

    }
    import static trade.TradeMeta.*;
    !
    List trades = dao.findAll();
    ImmutableSet activeTradeNos = FluentIterable.from(trades)
    .filter(Predicates.not(Predicates2.fromFunction(deleted))).transform(tradeNo).toSet();
    今回は Grinder という
    自作の APT を使っています
    https://github.com/monzou/grinder

    View Slide

  32. application-base
    application-common
    application-web
    application-batch
    web
    REST
    service
    service-implementation REST-implementation
    persistence
    domain-model domain-model-meta
    DOMAIN MODEL

    View Slide

  33. DDD ? DCI ?

    View Slide

  34. 技術的な方法論以前に……
    ・業務に興味を持つ
    ・As-Is だけでなく To-Be を考える
    ・進化を止めない
    その上で原理主義的にならず柔軟に考えれば良いのでは?

    View Slide

  35. 技術的な方法論以前に……
    ・業務に興味を持つ
    ・As-Is だけでなく To-Be を考える
    ・進化を止めない
    その上で原理主義的にならず柔軟に考えれば良いのでは?
    ライブラリを利用してアプリを作るだけなら誰でも出来る
    ビジネス的な問題を解決するための「システム」をデザインする
    DDD 云々以前にドメインに踏み込んでいく姿勢が必要

    View Slide

  36. 興味大事
    ・顧客の業務に興味を持つ
    ・競合製品を知る
    ・要件定義や基本設計から参加する

    View Slide

  37. 考える
    ・エンジニアが最も価値を出すべきところ
    ・ビジネス上の課題をエンジニア的に解釈する
    ・「何故その業務があるのか?」を考える
    ・「業務で達成したいことは何か?」を考える

    View Slide

  38. 考える
    ・エンジニアが最も価値を出すべきところ
    ・ビジネス上の課題をエンジニア的に解釈する
    ・「何故その業務があるのか?」を考える
    ・「業務で達成したいことは何か?」を考える
    顧客に言われるままにシステムをデザインせず, 批判的に考える

    View Slide

  39. 進化を止めない
    ・ドメインへの理解は日々変化する
    ・突然あたらしい概念を見出すこともある
    ・完全な正解は無い

    View Slide

  40. 進化を止めない
    ・ドメインへの理解は日々変化する
    ・突然あたらしい概念を見出すこともある
    ・完全な正解は無い
    基本設計時から大きく変化することもある
    変化を恐れずより良いものを追求する
    アプリケーションへのフィードバックを繰り返す

    View Slide

  41. 実装方針
    ・出来るだけドメインモデルに寄せる
    ・トランザクションスクリプトを避ける
    ・抽象化可能なドメイン知識は抽象化する
    多少の妥協を許容して, バランスを取りつつ素直に実装する

    View Slide

  42. 実装方針
    ・出来るだけドメインモデルに寄せる
    ・トランザクションスクリプトを避ける
    ・抽象化可能なドメイン知識は抽象化する
    多少の妥協を許容して, バランスを取りつつ素直に実装する
    具体的には …

    View Slide

  43. 実装方針
    ・出来るだけドメインモデルに寄せる
    ・トランザクションスクリプトを避ける
    ・抽象化可能なドメイン知識は抽象化する
    多少の妥協を許容して, バランスを取りつつ素直に実装する
    → 副作用の無い業務的な操作はドメインモデルに実装
    具体的には …

    View Slide

  44. 実装方針
    ・出来るだけドメインモデルに寄せる
    ・トランザクションスクリプトを避ける
    ・抽象化可能なドメイン知識は抽象化する
    多少の妥協を許容して, バランスを取りつつ素直に実装する
    → 副作用の無い業務的な操作はドメインモデルに実装
    → 副作用を伴う一連のトランザクションはサービスに実装
    具体的には …

    View Slide

  45. 副作用の無い業務ロジック
    public interface HolidayChecker {
    boolean isHoliday(LocalDate date, BusinessCenter ... businessCenters);
    }
    !
    public class BusinessDateCalculator {
    !
    private final HolidayChecker holidayChecker;
    private final BusinessDayConvention convention;
    private final BusinessCenter[] businessCenters;
    !
    public BusinessDateCalculator(HolidayChecker holidayChecker, BusinessDayConvention convention,
    BusinessCenter ... businessCenters) {
    this.holidayChecker = holidayChecker;
    this.convention = convention;
    this.businessCenters = businessCenters;
    }
    !
    public LocalDate calculateNextBusinessDate(LocalDate baseDate) {
    ...
    }
    !
    }
    例えば業務日付計算などの計算処理はドメインに寄せやすい

    View Slide

  46. ポイント
    「カレンダー情報」のような依存は切り離す
    → HolidayChecker
    「日付計算方式」のような情報は設定ファイルに切り出す
    → BusinessDayConvention
    切り離した依存コンポーネントはアプリケーション側で実装する

    View Slide

  47. ポイント
    「カレンダー情報」のような依存は切り離す
    → HolidayChecker
    「日付計算方式」のような情報は設定ファイルに切り出す
    → BusinessDayConvention
    切り離した依存コンポーネントはアプリケーション側で実装する
    ・依存を切り離すことで lazy な評価を可能にする
    ・依存の実装についての詳細を隠蔽する
     (実装がキャッシュしてもしなくても良い)

    View Slide

  48. ポイント
    「カレンダー情報」のような依存は切り離す
    → HolidayChecker
    「日付計算方式」のような情報は設定ファイルに切り出す
    → BusinessDayConvention
    切り離した依存コンポーネントはアプリケーション側で実装する
    ・依存を切り離すことで lazy な評価を可能にする
    ・依存の実装についての詳細を隠蔽する
     (実装がキャッシュしてもしなくても良い)
    依存を Push するのではなく
    Pull するように設計するのが大事

    View Slide

  49. 副作用を伴う業務ロジック
    ex) 約定の状態を遷移させて監査ログに記録 → 通知する
    ex) 約定の感応度を分散計算 → 永続化する

    View Slide

  50. 副作用を伴う業務ロジック
    ex) 約定の状態を遷移させて監査ログに記録 → 通知する
    ex) 約定の感応度を分散計算 → 永続化する
    多少の妥協は許容する
    これらの一連のトランザクションは
    サービス層で実装した方が楽だし自然
    実装コストや実行パフォーマンスを考えると,
    ドメインモデルに寄せすぎるのはツラいときも多い

    View Slide

  51. 業務知識を正確に反映する
    例えば「通貨」や「ステータス」は単なるコード値ではない
    業務的にはかなり重要な知識を含んでいる
    public final class Currency implements Serializable, Comparable {
    !
    public static final Currency BASIS_CURRENCY;
    public static final Currency DOMESTIC_CURRENCY;
    public static final Currency HEAD_OFFICE_CURRENCY;
    private static final Map VALUES;
    static {
    // load from currency.yml
    }
    !
    public int getOrdinal() {}
    public String getDisplayName() {}
    public RoundingMode getDefaultRoundingMode() {}
    public int getDefaultSignificantScale() {}
    ...
    !
    protected Currency readResolve() {}
    !
    }

    View Slide

  52. application-base
    application-common
    application-web
    application-batch
    web
    REST
    service
    service-implementation REST-implementation
    persistence
    domain-model domain-model-meta
    PERSISTENCE

    View Slide

  53. ORM の選択
    Hibernate
    !
    ・生 SQL / JPQL は避けたい
    ・ドメインが複雑なので ORM は必須
    ・もともと Hibernate を利用していたので実績がある
    ・今のところ Hibernate (JPA) 以外の選択肢が無い
    http://tech.usopla.com/blog/2012/12/26/honeyant/
    Type-safe extension
    !
    ・自作のライブラリで型安全性と生産性を向上
    ・Eclipse プラグインでメタクラスなどを自動生成

    View Slide

  54. エンティティメタクラス
    RepositoryMeta meta = RepositoryMeta.getInstance();
    Criteria criteria = meta.createCriteria()
    .add(meta.deleted.isFalse())
    .add(meta.issues.issueDate.gt(LocalDate.today())
    .add(meta.issues.status.eq(Status.OPEN));
    Hibernate に依存しないタイプセーフな
    クライテリアを生成 (QueryDSL より型安全)
    クライテリアの利用用途
    !
    ・タイプセーフ DAO (Hibernate Criteria に変換)
    ・継続クエリ (OQL に変換)

    View Slide

  55. タイプセーフ DAO
    Dao dao = daoFactory.createDao(meta);
    !
    // Find by PK
    Repository repository = dao.find(id);
    !
    // Update, Save, Delete etc …
    Repository updated = dao.update(repository);
    !
    // Entity Query (エンティティ単位)
    List repositories = dao.createQuery().orderBy(meta.id, Order.ASC).list(criteria);
    !
    // Projection Query (射影)
    List> projections =
    dao.createProjectionQuery().listProjections(criteria, meta.id, meta.issues.subject);
    !
    // Aggregation Query (集約)
    LocalDate maxIssueDate =
    dao.createAggregationQuery().uniqueValue(criteria, Aggregations.max(meta.issues.issueDate));
    Hibernate を利用した型安全な DAO
    DAO レイヤは作らず, 必要に応じてサービスレイヤで作成

    View Slide

  56. 継続クエリ
    CriteriaFilter filter = CriteriaFilterFactory.create(criteria);
    boolean match = filter.apply(repository);
    ネイティブクライアントで利用
    !
    Presentation
    Model
    Binding
    !
    Cache
    Criteria Consumer
    Criteria Filter MQ
    Entity
    Update
    XA
    View
    RMI
    DAO
    フィルタを登録して
    イベントを選択
    同じ Criteria から
    フィルタを作成出来る
    Subscribe

    View Slide

  57. 改善したいところ
    ・Eclipse プラグインは重いので APT ベースにしたい
    ・いい加減 JPA から脱却したいが選択肢が無い……

    View Slide

  58. 改善したいところ
    ・Eclipse プラグインは重いので APT ベースにしたい
    ・いい加減 JPA から脱却したいが選択肢が無い……
    ちなみに
    !
    普段は WebLogic の TransactionManager で
    分散トランザクションしようとしたら結構ハマりました……

    View Slide

  59. application-base
    application-common
    application-web
    application-batch
    web
    REST
    service
    service-implementation REST-implementation
    persistence
    domain-model domain-model-meta
    SERVICE

    View Slide

  60. 実装方針
    ・業務的な要求に応じてつくる
    ・”ふつう” のサービスレイヤ
    ・Web のことを一切意識しないのが重要
    他のコンポーネントを利用しつつ副作用のある
    トランザクションを実装するだけなので, 比較的薄くなる
    ・ドメインの各コンポーネントに委譲して計算
    ・DAO を使って永続化
    ・必要に応じて他サービスと連携

    View Slide

  61. @Transactional
    public class TradeServiceImpl implements TradeService {
    !
    @Inject
    public TradeServiceImpl(sessionProvider, businessContextProvider, daoFactory, notificationService) {
    this.sessionProvider = sessionProvider;
    this.businessContextProvider = businessContextProvider;
    this.dao = daoFactory.create(meta);
    this.notificationService = notificationService;
    }
    !
    public Trade transit(Trade trade, TradeAction action) throws DataUpdateException {
    TradeTransition transition = TradeTransition.getTransitionFor(trade, action);
    checkOperationAllowed(transition);
    if (transition.needsBackup()) {
    dao.update(dao.clone(trade).invalidate());
    }
    Trade updated = dao.save(transition.transit(trade).validate());
    notifyTransition(updated, backup);
    return updated;
    }
    !
    private void checkOperationAllowed(TradeTransition transition) throws OperationNotAllowedException {
    businessContextProvider.getContext().map(new TradeTransitionChecker(getSession(), transition)).orThrow(…);
    }
    private void notifyTransition(Trade updated, Trade backup) {
    notificationService.notify(I18N.translate("message.trade.transit", updated.getTradeNo()));
    }
    !
    }
    ドメインロジックを
    使って遷移させる
    宣言的トランザクションは
    自前で実装
    セッション情報は
    Web とは関係ない

    View Slide

  62. application-base
    application-common
    application-web
    application-batch
    web
    REST
    service
    service-implementation REST-implementation
    persistence
    domain-model domain-model-meta
    REST

    View Slide

  63. 実装方針
    ・業務ロジックは書かない
    ・Web の API (IN / OUT) のみに専念する
    主に以下の機能を担うだけの薄いレイヤ
    !
    ・JSON や XML などの変換
    ・入力値のバリデーション
    ・業務例外を Web のレスポンスに変換
    !
    内部的にはほぼサービスへの委譲のみを行う

    View Slide

  64. JAX-RS
    基盤として JAX-RS (Jersey) を採用
    !
    ・Spring MVC : Spring は大き過ぎる …
    ・Dropwizard : J2EE サーバーは想定していない
    ・Play Framework : 無駄に複雑過ぎる …
    ・Ninja Framework : 悪くないけどわざわざ使わなくても……

    View Slide

  65. JAX-RS
    基盤として JAX-RS (Jersey) を採用
    !
    ・Spring MVC : Spring は大き過ぎる …
    ・Dropwizard : J2EE サーバーは想定していない
    ・Play Framework : 無駄に複雑過ぎる …
    ・Ninja Framework : 悪くないけどわざわざ使わなくても……
    API サーバーの実装をしようと考えた場合,
    いずれの WAF も大したことをしているわけではない
    JAX-RS の拡張性は非常に高いので,
    必要に応じて自作すれば十分

    View Slide

  66. JAX-RS を用いた Resource 例
    @Path("/branches")
    @AuthenticationRequired
    @AuthorityRequired(Authorities.BRANCH_REFERENCE)
    public interface BranchResource {
    !
    @NoCache
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    PageableCollectionDto get(@InjectParam BranchSearchConditionDto condition);
    !
    @NoCache
    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    @Nullable
    BranchDto get(@PathParam("id") Long id);
    !
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @AuthorityRequired(Authorities.BRANCH_REGISTRATION)
    BranchDto create(@ValidParam(BranchDtoValidator.Create.class) BranchDto branch);
    !
    }
    赤字は独自拡張した
    アノテーションなど

    View Slide

  67. Guice + JAX-RS
    public class WebAppModule extends JerseyServletModule {
    !
    @Override
    protected void configureServlets() {
    installModules();
    Map properties = ...
    serve(configuration.endpoint()).with(GuiceContainer.class, properties);
    }
    !
    private void installModules() {
    install(new ApplicationDescriptorModule());
    install(new SessionModule());
    install(new PersistenceModule());
    install(new TransactionModule());
    install(new ServiceModule());
    install(new ResourceModule());
    ...
    }
    !
    }
    jersey-servlet を使えば簡単
    JAX-RS 自体が DI を持っているのはちょっと使いづらい面も……

    View Slide

  68. Jackson
    ・jackson-annotations
    ・jackson-databind
    ・jackson-jaxrs-json-provider
    ・jackson-dataformat-yaml
    ・jackson-module-afterburner
    JSON・YAML 変換
    設定ファイルの読み込みにも使用

    View Slide

  69. Bean Validation (JSR-303)
    使いづらい……
    !
    ・複雑なことをしようとすると Bean が汚れる
    ・コードの見通しが悪い
    ・生産性/再利用性が低い
    ・DI コンテナとの親和性も低い
    皆どうしているのか知りたい……

    View Slide

  70. GuardMan
    import static meta.CredentialDtoMeta.*;
    !
    public class CredentialDtoValidator implements BeanValidator {
    !
    @Override
    public Violations apply(CredentialDto bean) {
    BeanValidationContext context = new BeanValidationContext<>(bean);
    context.property(userCode).required().validate(
    minLength(0),
    maxLength(10),
    alphaNumeric(false)
    );
    context.property(password).required().validate(DomainValidators.PASSWORD);
    context.property(version).required().eq(ApplicationDescriptor.getVersion());
    return context;
    }
    !
    }
    ・Jackson / JAX-RS で JSON deserialize 時のバリデーション
    ・GuardMan で deserialize 後の Bean のバリデーション
    https://github.com/monzou/guardman

    View Slide

  71. 気にしたところ ①
    Enum や Value Object のエンコード/デコード
    !
    ・永続化値や JSON 値への変換コンポーネントを定義
    ・クライアントはエンコードされた表現を元に記述する
    ・表示文字列に依存しないようにする
    Hibernate の UserType や Jackson の Module から
    専用のコンポーネントを利用して変換するようにする

    View Slide

  72. 気にしたところ ②
    REST 原理主義的になり過ぎない
    !
    ・あらゆる操作を全て HTTP メソッドに割り当てるのは無理
    ・ある程度の妥協を許した上で, 統一感のある API にする
    例えば強制削除 …
    !
    ・DELETE /resources/1
    ・PUT /resources/1/delete?force=true

    View Slide

  73. 気にしたところ ③
    クライアントを意識した API
    !
    ・バッチはアプリケーションモジュールが分かれているので,
     今のところは Web クライアントしか使用しない
    ・出来るだけ汎用的に設計しつつ, 場合によっては
     クライアントのリクエスト数を減らすための妥協もする
    HATEOAS ではないけれど……
    !
    ・例えば「約定」を取得したら「遷移情報」も同時に返すなど
    ・可能な遷移情報はドメインモデルの複雑な業務ロジックの中にしかない

    View Slide

  74. 改善したいところ
    JSON DTO と Entity の変換
    !
    ・ModelMapper を使ってみたがイマイチ
    ・素直に実装した方が良さそう
    !
    バリデーションの扱いが難しい
    !
    ・「どこで」「何を」バリデーションすべきか
    ・「入力値の検証」と「業務的なバリデーション」は違う
    遅い・重い
    結局テストで検証用の
    コードを書くことになる

    View Slide

  75. application-base
    application-common
    application-web
    application-batch
    web
    REST
    service
    service-implementation REST-implementation
    persistence
    domain-model domain-model-meta
    APPLICATION

    View Slide

  76. アプリケーションの組み立て
    ・モジュール定義
    ・ログ出力等の設定
    ・ディスクリプタ等の実装
    アプリケーションに応じて Guice モジュールを組み立てる
    セッションやトランザクションマネージャの実装の切り替えなど

    View Slide

  77. プロジェクトの
    アーキテクチャ説明
    アーキテクチャ
    サーバーサイド詳細
    クライアントサイド
    まとめ
    まとめ

    View Slide

  78. “ふつう” の延長線
    Web レイヤ以外は “ふつう”
    !
    ・ネイティブクライアントと変わらない
    ・業務レイヤを切り離せていれば SPA 化は難しくない
    JAX-RS で必要十分
    !
    ・REST API の実装だけなので大したことをするわけではない
    ・JAX-RS の拡張性は十分に高い
    ・無理して複雑な WAF を使わなくても……

    View Slide

  79. J2EE サーバーはツラいよ
    重いので開発には使用出来ない
    !
    ・開発環境との差異が生じてハマる
    サーバー固有の設定や拡張実装の存在
    !
    ・分散トランザクションするだけでも大変
    謎の挙動あれこれ
    !
    ・クラスローダー
    ・デプロイし直したら変な挙動……

    View Slide

  80. J2EE サーバーはツラいよ
    重いので開発には使用出来ない
    !
    ・開発環境との差異が生じてハマる
    サーバー固有の設定や拡張実装の存在
    !
    ・分散トランザクションするだけでも大変
    謎の挙動あれこれ
    !
    ・クラスローダー
    ・デプロイし直したら変な挙動……
    可能な限り J2EE サーバーの利用は避けた方が無難な印象 (※ 個人の印象です)
    Java App Servers are Dead !

    View Slide

  81. プロジェクトの
    アーキテクチャ説明
    アーキテクチャ
    サーバーサイド
    クライアントサイド詳細
    まとめ

    View Slide

  82. application-base
    application-common
    application-web
    application-batch
    web
    REST
    service
    service-implementation REST-implementation
    persistence
    domain-model domain-model-meta
    ココ

    View Slide

  83. 主な使用技術
    ・SCSS - Alternative CSS
    ・CoffeeScript - Alternative JavaScript
    ・Backbone.js + Backbone.Marionette - MVC FW
    ・Handlebars - Template Engine
    ・Bower - Dependencies Manager
    ・Grunt - Build Runner
    サーバーサイドとは全く独立して Grunt で管理
    ビルドの雛形は Yeoman で作成したものを拡張して利用

    View Slide

  84. Grunt
    ・各種アセットのコンパイル
    ・静的ファイルのビルド/圧縮
    ・ビルド/環境毎の設定情報の埋め込み
    ・Livereload
    gulp とかあるけど
    !
    今のところ敢えて Grunt 以外を使う必要性は感じていない
    SCSS → CSS
    CoffeeScript → JavaScript
    Handlebars → JavaScript
    i18n YAML → JSON
    !
    etc …

    View Slide

  85. CoffeeScript
    生 JavaScript は厳しい
    !
    ・Bad Parts の存在
    ・冗長で読みづらい
    !
    他の Alt JS 言語と比較して明確な利点がある
    !
    ・Bad Parts の隠蔽
    ・糖衣構文に過ぎない (学習コストの低さ)
    ・気持ち良いシンタックス
    !
    型は欲しいが …
    !
    ・複雑なロジックはサーバーサイド
    ・GUI は結局ランタイムバインドの世界
    経験の少ない JS プログラマが
    多い場合は非常に有用

    View Slide

  86. Backbone.js
    軽量 MVC “ライブラリ”
    !
    ・最低限の “構造” の提供
    ・黒魔術が無い
    ・軽量で自由に組み合わせられる
     ・デバッグが容易
     ・学習コストがほぼ無い
     ・既存ライブラリとの組み合わせが容易
    経験値の低いチームはまずここから始めると良いと思う

    View Slide

  87. Angular ? Ember ?
    重厚長大な FW が本当に必要か?
    !
    ・内部で何をしているか分からないほど複雑
    ・高い学習コスト
    ・デバッグも大変
    ・アーキテクチャを強制される (侵略的)
    ・いずれの FW もまだまだ改善の余地が大きい

    View Slide

  88. Angular ? Ember ?
    重厚長大な FW が本当に必要か?
    !
    ・内部で何をしているか分からないほど複雑
    ・高い学習コスト
    ・デバッグも大変
    ・アーキテクチャを強制される (侵略的)
    ・いずれの FW もまだまだ改善の余地が大きい
    Backbone は薄くて黒魔術が無いので挙動が全て把握出来る
    個人的には薄いライブラリを組み合わせる方が良いと思う
    !
    ・Backbone.js + Backbone.stickit / Rivets.js
    ・Vue.js + Superagent + Page.js

    View Slide

  89. Backbone.Marionette
    素の Backbone には足りないものを補完する
    !
    ・モジュール機構
    ・イベントバス
    ・ネストした View のライフサイクル管理
    ・ボイラープレートの排除

    View Slide

  90. Backbone.Marionette
    素の Backbone には足りないものを補完する
    !
    ・モジュール機構
    ・イベントバス
    ・ネストした View のライフサイクル管理
    ・ボイラープレートの排除
    Backbone だけだと結局これらを自作することになるので,
    Backbone を使うなら最初から導入した方が良い

    View Slide

  91. Marionette vs Chaplin
    Marionette は Backbone の “拡張ライブラリ”
    Chaplin は Backbone を使った “フレームワーク”
    Marionette は Backbone の部品を提供するだけだが,
    Chaplin はアプリケーションの作り方を規定する。
    素の Backbone だけだと結局足りないものに気付くので,
    経験値や好みによってどちらかを選択すると良い。

    View Slide

  92. Marionette vs Chaplin
    Marionette は Backbone の “拡張ライブラリ”
    Chaplin は Backbone を使った “フレームワーク”
    Marionette は Backbone の部品を提供するだけだが,
    Chaplin はアプリケーションの作り方を規定する。
    素の Backbone だけだと結局足りないものに気付くので,
    経験値や好みによってどちらかを選択すると良い。
    → 自前で作り込みたい人は Marionette
    → フレームワークが欲しい人は Chaplin

    View Slide

  93. Backbone 実装方針
    モジュールを分割する
    !
    ・共通コンポーネントを纏める
    ・サブアプリケーション毎にモジュール化する
    ・バックエンドとの連携を疎結合にする
    出来るだけ Promise 化する
    !
    ・バックエンドとの同期
    ・非同期なアプリケーションの初期化
    古典的な MVC とは違う
    !
    ・Backbone らしく
    ・Backbone.View は Controller

    View Slide

  94. Promise
    Show = Application.module "Branches.Show"
    Show.Controller =
    show: (id) ->
    $.when(Application.request "session:checkAuthorities", "BRANCH_REFERENCE")
    .then ->
    Application.request "branch", id
    .done (branch) ->
    view = new Show.View.FormView model: branch
    Application.mainRegion.show view
    Application.execute "set:layout", "application"
    ・Future みたいなもの
    ・Backbone.sync は Promise を返さないのでラップすると良い

    View Slide

  95. アプリケーションの初期化も
    class Application extends Marionette.Application
    !
    constructor: (options) ->
    super options
    @_asyncInitCallbacks = []
    !
    addAsyncInitializer: (initializer) ->
    @_asyncInitCallbacks.push initializer
    !
    start: (options) ->
    @triggerMethod "initialize:before", options
    @_initCallbacks.run options, @
    asyncCallbackPromises = _.map @_asyncInitCallbacks, (callback) -> callback()
    $.when(asyncCallbackPromises...).done =>
    @triggerMethod "initialize:after", options
    @triggerMethod "start", options
    ・起動時に必要なリソースを非同期に取り寄せる
    ・設定情報や translation など
    Marionette の Application を拡張して
    非同期なコールバックも登録出来るようにする

    View Slide

  96. Backbone.js の問題点
    バインディング
    !
    ・それほど動的な画面で無ければ無理に導入しなくても良い
    ・とはいえあった方が良いと感じる場面は多い
    ・Backbone.stickit / Rivets.js など
    !
    貧弱なイベント管理
    !
    ・ネストしたモデルのイベント監視が出来ない
    ・backbone-deep-model や backbone-nested など
    !
    ActiveRecord のように振る舞うモデル
    !
    ・ViewModel とバックエンドへの Facade は分離したい
    ・既存ライブラリの取り扱いが悩ましい

    View Slide

  97. モジュール管理
    Require JS (AMD) を廃止
    !
    ・やり過ぎ感が漂う……(結局最後は結合する)
    ・モジュール機構はあった方が良いが, 規模による
    ・ネームスペースなら Marionette.Module が提供してくれる
    !
    Browserify
    !
    ・プリプロセッサなので AMD より分かりやすい
    ・モジュール機構があった方がコードが構造化されやすい

    View Slide

  98. やってみた
    MVC の役割の明確化
    !
    ・Controller はアプリケーションのイベントを管理する
    ・View は DOM のイベントを管理する
    ・ViewModel は分離する
    !
    バインディング
    !
    ・Backbone.stickit を使ってみた
    ・Rivets.js は開発がアクティブではない印象
    ・Ractive.js は大き過ぎる
    https://github.com/monzou/backbone.marionette.example

    View Slide

  99. ViewModel
    _ = require "underscore"
    ViewModel = require "app/common/view_model"
    !
    module.exports = class UserViewModel extends ViewModel
    !
    view:
    fullName:
    observe: [ "firstName", "lastName" ]
    value: ->
    (_.filter [ @get("firstName"), @get("lastName") ], (name) -> not _.str.isBlank name).join (" ")
    ・View 用の Model を分離してみた
    ・computed プロパティを定義出来るようにしてみた
    ひっそりと Browsrify 化もしてみた …

    View Slide

  100. View
    Backbone = require "backbone"
    module.exports = class UserFormView extends Backbone.Marionette.ItemView
    !
    template: "#user-form"
    triggers:
    "click .save-button": "user:form:save"
    "click .cancel-button": "user:form:cancel"
    behaviors:
    binding: {}
    bindings:
    "#title": "title"
    "#firstName": "firstName"
    "#lastName": "lastName"
    "#email": "email"
    "#remarks": "remarks"
    !
    getEditingModel: ->
    @model.commit()
    ・Backbone.stickit でバインドしてみた (Behavior)
    ・DOM のイベントをアプリケーションのセマンティクスに
    DOM のイベントを
    アプリケーションのイベントに変換
    stickit の Binding 定義
    Marionette.Behavior を使って自動化

    View Slide

  101. View
    Backbone = require "backbone"
    module.exports = class UserFormView extends Backbone.Marionette.ItemView
    !
    template: "#user-form"
    triggers:
    "click .save-button": "user:form:save"
    "click .cancel-button": "user:form:cancel"
    behaviors:
    stickit: {}
    !
    getEditingModel: ->
    @model.commit()
    ・バインディングが明示的なのは良いが些か面倒 …
    ・標準で自動的にバインドするようにしてみた
    ココ
    もちろん追加で
    bindings の指定も出来る

    View Slide

  102. Controller
    module.exports = class EditController extends Backbone.Marionette.Controller
    !
    constructor: (@region) ->
    _.bindAll @, "save", "goToIndex"
    !
    show: (id) ->
    UserRepository.find(id).done (model) => @region.show @createView model
    !
    createView: (model) ->
    viewModel = new ViewModel {}, model: model
    view = new View model: viewModel
    @listenTo view, "user:form:save", @save
    @listenTo view, "user:form:cancel", @goToIndex
    view
    !
    save: (params) ->
    UserRepository.save(params.model.commit()).done @goToIndex
    !
    goToIndex: ->
    CRUD.execute "action:users:list"
    ・アプリケーションのセマンティクスでイベント処理
    ・DOM は意識しない
    アプリケーションのイベントを
    処理して画面を駆動する

    View Slide

  103. IE 9 対応の思い出
    モダンブラウザになりきれていない
    !
    ・Ajax を利用したファイルアップロード
    ・セレクタ数上限問題
    ・描画パフォーマンス
    !
    IE 9 のみをターゲットにして開発するのは悪手
    !
    ・殆どの機能で IE 9 はモダンブラウザ
    ・Chrome Dev Tools は大変優秀
    ・IE 固有問題は FW レベルで隠蔽しましょう

    View Slide

  104. クライアントのテスト
    自社のリソースとリリースサイクルを考える
    !
    ・Web 業界ほどリリースサイクルが早くない
    ・テスト工程とテスト用のリソースが確保出来る

    View Slide

  105. クライアントのテスト
    自社のリソースとリリースサイクルを考える
    !
    ・Web 業界ほどリリースサイクルが早くない
    ・テスト工程とテスト用のリソースが確保出来る
    本当に単体テストで吸収すべきか冷静に判断した方が良い
    場合によっては GUI のテストはコストに見合わないかも?

    View Slide

  106. 今回やっていないこと
    リアルタイム化
    !
    ・永続化 → XA → MQ → WebSocket / SSE で push
    ・もしまた Web のプロジェクトがあったらやりたい
    !
    オフライン対応
    !
    ・今回は考えなかった
    ・一部データは初期化時に localStorage に永続化したが …
    ・金融のプロ向けアプリだとあまり必要になることはなさそう

    View Slide

  107. プロジェクトの
    アーキテクチャ説明
    アーキテクチャ
    サーバーサイド
    クライアントサイド詳細
    まとめ
    まとめ

    View Slide

  108. SPA は採用すべき
    明らかなユーザーエクスペリエンスの向上
    !
    ・ネイティブクライアントに近づく
    ・業務アプリの方がむしろ採用しやすい
    !
    開発面でも利点がある
    !
    ・クライアントとサーバーの分離が促進される
    ・将来的なクライアントの拡張がやりやすくなる
    ・開発環境も整備されてきているので開発効率は悪くない

    View Slide

  109. SPA は採用すべき
    明らかなユーザーエクスペリエンスの向上
    !
    ・ネイティブクライアントに近づく
    ・業務アプリの方がむしろ採用しやすい
    !
    開発面でも利点がある
    !
    ・クライアントとサーバーの分離が促進される
    ・将来的なクライアントの拡張がやりやすくなる
    ・開発環境も整備されてきているので開発効率は悪くない
    クライアントアプリケーションに「構造」がもたらされるため,
    中途半端にリッチ化したクライアントより保守性や開発効率が上がる印象

    View Slide

  110. 開発環境
    SPA 開発用の FW は成熟していない
    !
    ・当面は流動的だと思われる
    ・まだまだ理想的な FW は存在しない
    ・今のところは Backbone.js ぐらい薄い FW がオススメ
    !
    SPA の開発環境は整備されてきている
    !
    ・Grunt や gulp のようなビルドツール
    ・Browserify や Require JS
    ・Chrome Dev Tools

    View Slide

  111. ネイティブにはまだまだ勝てない
    ネイティブアプリなら出来ることが出来ない
    !
    ・レンダリングに介入出来ない
    ・レイアウトマネージャが弄れない
    ・クリップボードが操作出来ない
     etc …
    !
    ネイティブアプリに比べると開発効率は劣る
    !
    ・特に CSS が貧弱過ぎる・闇が深い …
    ・多人数での開発に向いていない

    View Slide

  112. UI の設計が難しい
    業務アプリでの SPA 採用事例がまだまだ少ない
    !
    ・B2C の Web サービスと比べると非常に画面が複雑
    ・どのような画面が実現出来るのか/出来ないのか
    ・コモンセンスの確立
    !
    ネイティブアプリとは違う
    !
    ・似たようなエクスペリエンスを提供出来るが実はかなり違う
    ・CRUD ひとつとってもどんな UI が良いかは検討の余地がある

    View Slide

  113. UI の設計が難しい
    業務アプリでの SPA 採用事例がまだまだ少ない
    !
    ・B2C の Web サービスと比べると非常に画面が複雑
    ・どのような画面が実現出来るのか/出来ないのか
    ・コモンセンスの確立
    !
    ネイティブアプリとは違う
    !
    ・似たようなエクスペリエンスを提供出来るが実はかなり違う
    ・CRUD ひとつとってもどんな UI が良いかは検討の余地がある
    SPA の UI 設計のガイドラインが欲しい …
    UI デザイナーが欲しい …

    View Slide

  114. プロジェクトの
    アーキテクチャ
    アーキテクチャ
    サーバーサイド
    クライアントサイド
    まとめ

    View Slide

  115. おさらい
    SPA 開発は “ふつう” の延長線上にある
    !
    ・API とクライアントが違うだけ
    ・ドメイン設計やインフラ層の知見は流用出来る
    ・きちんとしたドメイン理解と開発を心がけましょう
    !
    !
    まだまだこれからの分野
    !
    ・開発環境は整備されてきているが今後ますます発展するはず
    ・ツールや UI 等を含めてまだまだ洗練される必要がある
    !
    Keep it simple !
    !
    ・関連技術は多様化し複雑化する一方 …
    ・出来る限り複雑な仕組みを避けて軽量に作った方が良いのでは?

    View Slide

  116. まとめ
    SPA はこわくない
    !
    ・これまでの延長線上にある
    ・きちんと設計出来れば経験値が少ないチームでも大丈夫
    !
    !
    継続的なキャッチアップは必要
    !
    ・Java もモダンに生まれ変わりつつある
    ・Web 開発は進歩のスピードが物凄い
    ・半年経てば主流技術が変わることも …
    ・アーキテクトは多種多様な最新技術を知る必要がある
    ・ナレッジの蓄積が出来る体制づくりを

    View Slide

  117. Thank you

    View Slide