Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

WHY ?

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

実例を知りたい

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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 から切り離し 独立性を保つ

Slide 23

Slide 23 text

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 の 提供のみ

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

主な使用技術 ・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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

DDD ? DCI ?

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

副作用の無い業務ロジック 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) { ... } ! } 例えば業務日付計算などの計算処理はドメインに寄せやすい

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

業務知識を正確に反映する 例えば「通貨」や「ステータス」は単なるコード値ではない 業務的にはかなり重要な知識を含んでいる 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() {} ! }

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

エンティティメタクラス 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 に変換)

Slide 55

Slide 55 text

タイプセーフ 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 レイヤは作らず, 必要に応じてサービスレイヤで作成

Slide 56

Slide 56 text

継続クエリ 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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

@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 とは関係ない

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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); ! } 赤字は独自拡張した アノテーションなど

Slide 67

Slide 67 text

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 を持っているのはちょっと使いづらい面も……

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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 を返さないのでラップすると良い

Slide 95

Slide 95 text

アプリケーションの初期化も 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 を拡張して 非同期なコールバックも登録出来るようにする

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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 化もしてみた …

Slide 100

Slide 100 text

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 を使って自動化

Slide 101

Slide 101 text

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 の指定も出来る

Slide 102

Slide 102 text

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 は意識しない アプリケーションのイベントを 処理して画面を駆動する

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

Thank you