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. 700+ BOOKMARKS 60+ STARS 40+ STOCKS 6 MONTHS AGO …

    https://gist.github.com/monzou/e355a2c425cdad109537
  2. 既存 Web システム の 問題点 “構造”の欠落 ! ・Ajax 化に伴うフラグメント化 ・業務レイヤの独立性が担保出来ていない

    クライアントの進歩についていけない ! ・クライアントとサーバーの密結合 ・独立して開発することが困難 ・将来的なネイティブクライアント対応が …
  3. SPA (Single Page Application) 未来のために ! ・B2C の Web サービスはリッチ化が進む一方……

    ・金融業界でもリッチなクライアントは増加傾向 ・今後のためにナレッジを蓄積したい クライアントとサーバーを分離 ! ・ステートフルな JavaScript Client + REST API ・ネイティブクライアントに近い
  4. 不安もあった…… 対策 2 週間ほど技術調査 ! ・jQuery の使い方を調べたり … ・Backbone.js のコードを読んでみたり

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

    … ・JS MVC FW を作ってみたり … ・WAF を作ってみたり … ノウハウの不足 ! ・Servlet ? Struts ? war ? WebLogic ? ・JavaScript?jQuery ? 問題なさそう! … だけど他メンバーは大丈夫かな?
  6. 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
  7. 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 から切り離し 独立性を保つ
  8. 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 の 提供のみ
  9. application-base application-common application-web application-batch web REST service service-implementation REST-implementation persistence

    domain-model domain-model-meta 静的ファイル群 HTML, JS, CSS … コンパイル時に 実装に依存しないように モジュールを分割
  10. 主な使用技術 ・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
  11. Gradle 知識ゼロでも使える敷居の低さ ! ・Maven と違って何も知らなくても使える ・自動的に良く出来たビルド環境が手に入る 全体は Gradle によるマルチプロジェクト構成 拡張性の高さ

    ! ・Groovy によるスクリプティング ・様々な自動化タスクを組み込んだり … ・動的に環境設定を読み込んでビルドタスクを作成したり …
  12. 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<Trade> trades = dao.findAll(); ImmutableSet<String> activeTradeNos = FluentIterable.from(trades) .filter(Predicates.not(Predicates2.fromFunction(deleted))).transform(tradeNo).toSet(); 今回は Grinder という 自作の APT を使っています https://github.com/monzou/grinder
  13. 副作用の無い業務ロジック 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) { ... } ! } 例えば業務日付計算などの計算処理はドメインに寄せやすい
  14. ポイント 「カレンダー情報」のような依存は切り離す → HolidayChecker 「日付計算方式」のような情報は設定ファイルに切り出す → BusinessDayConvention 切り離した依存コンポーネントはアプリケーション側で実装する ・依存を切り離すことで lazy

    な評価を可能にする ・依存の実装についての詳細を隠蔽する  (実装がキャッシュしてもしなくても良い) 依存を Push するのではなく Pull するように設計するのが大事
  15. 副作用を伴う業務ロジック ex) 約定の状態を遷移させて監査ログに記録 → 通知する ex) 約定の感応度を分散計算 → 永続化する 多少の妥協は許容する

    これらの一連のトランザクションは サービス層で実装した方が楽だし自然 実装コストや実行パフォーマンスを考えると, ドメインモデルに寄せすぎるのはツラいときも多い
  16. 業務知識を正確に反映する 例えば「通貨」や「ステータス」は単なるコード値ではない 業務的にはかなり重要な知識を含んでいる public final class Currency implements Serializable, Comparable<Currency>

    { ! public static final Currency BASIS_CURRENCY; public static final Currency DOMESTIC_CURRENCY; public static final Currency HEAD_OFFICE_CURRENCY; private static final Map<String, Currency> VALUES; static { // load from currency.yml } ! public int getOrdinal() {} public String getDisplayName() {} public RoundingMode getDefaultRoundingMode() {} public int getDefaultSignificantScale() {} ... ! protected Currency readResolve() {} ! }
  17. ORM の選択 Hibernate ! ・生 SQL / JPQL は避けたい ・ドメインが複雑なので

    ORM は必須 ・もともと Hibernate を利用していたので実績がある ・今のところ Hibernate (JPA) 以外の選択肢が無い http://tech.usopla.com/blog/2012/12/26/honeyant/ Type-safe extension ! ・自作のライブラリで型安全性と生産性を向上 ・Eclipse プラグインでメタクラスなどを自動生成
  18. エンティティメタクラス RepositoryMeta meta = RepositoryMeta.getInstance(); Criteria<Repository> 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 に変換)
  19. タイプセーフ DAO Dao<Repository> dao = daoFactory.createDao(meta); ! // Find by

    PK Repository repository = dao.find(id); ! // Update, Save, Delete etc … Repository updated = dao.update(repository); ! // Entity Query (エンティティ単位) List<Repository> repositories = dao.createQuery().orderBy(meta.id, Order.ASC).list(criteria); ! // Projection Query (射影) List<Projection<Repository>> 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 レイヤは作らず, 必要に応じてサービスレイヤで作成
  20. 継続クエリ 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
  21. 改善したいところ ・Eclipse プラグインは重いので APT ベースにしたい ・いい加減 JPA から脱却したいが選択肢が無い…… ちなみに !

    普段は WebLogic の TransactionManager で 分散トランザクションしようとしたら結構ハマりました……
  22. @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 とは関係ない
  23. 実装方針 ・業務ロジックは書かない ・Web の API (IN / OUT) のみに専念する 主に以下の機能を担うだけの薄いレイヤ

    ! ・JSON や XML などの変換 ・入力値のバリデーション ・業務例外を Web のレスポンスに変換 ! 内部的にはほぼサービスへの委譲のみを行う
  24. JAX-RS 基盤として JAX-RS (Jersey) を採用 ! ・Spring MVC : Spring

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

    は大き過ぎる … ・Dropwizard : J2EE サーバーは想定していない ・Play Framework : 無駄に複雑過ぎる … ・Ninja Framework : 悪くないけどわざわざ使わなくても…… API サーバーの実装をしようと考えた場合, いずれの WAF も大したことをしているわけではない JAX-RS の拡張性は非常に高いので, 必要に応じて自作すれば十分
  26. JAX-RS を用いた Resource 例 @Path("/branches") @AuthenticationRequired @AuthorityRequired(Authorities.BRANCH_REFERENCE) public interface BranchResource

    { ! @NoCache @GET @Produces(MediaType.APPLICATION_JSON) PageableCollectionDto<BranchDto> 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); ! } 赤字は独自拡張した アノテーションなど
  27. Guice + JAX-RS public class WebAppModule extends JerseyServletModule { !

    @Override protected void configureServlets() { installModules(); Map<String, String> 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 を持っているのはちょっと使いづらい面も……
  28. GuardMan import static meta.CredentialDtoMeta.*; ! public class CredentialDtoValidator implements BeanValidator<CredentialDto>

    { ! @Override public Violations apply(CredentialDto bean) { BeanValidationContext<CredentialDto> 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
  29. 気にしたところ ① Enum や Value Object のエンコード/デコード ! ・永続化値や JSON

    値への変換コンポーネントを定義 ・クライアントはエンコードされた表現を元に記述する ・表示文字列に依存しないようにする Hibernate の UserType や Jackson の Module から 専用のコンポーネントを利用して変換するようにする
  30. 気にしたところ ③ クライアントを意識した API ! ・バッチはアプリケーションモジュールが分かれているので,  今のところは Web クライアントしか使用しない ・出来るだけ汎用的に設計しつつ,

    場合によっては  クライアントのリクエスト数を減らすための妥協もする HATEOAS ではないけれど…… ! ・例えば「約定」を取得したら「遷移情報」も同時に返すなど ・可能な遷移情報はドメインモデルの複雑な業務ロジックの中にしかない
  31. 改善したいところ JSON DTO と Entity の変換 ! ・ModelMapper を使ってみたがイマイチ ・素直に実装した方が良さそう

    ! バリデーションの扱いが難しい ! ・「どこで」「何を」バリデーションすべきか ・「入力値の検証」と「業務的なバリデーション」は違う 遅い・重い 結局テストで検証用の コードを書くことになる
  32. “ふつう” の延長線 Web レイヤ以外は “ふつう” ! ・ネイティブクライアントと変わらない ・業務レイヤを切り離せていれば SPA 化は難しくない

    JAX-RS で必要十分 ! ・REST API の実装だけなので大したことをするわけではない ・JAX-RS の拡張性は十分に高い ・無理して複雑な WAF を使わなくても……
  33. J2EE サーバーはツラいよ 重いので開発には使用出来ない ! ・開発環境との差異が生じてハマる サーバー固有の設定や拡張実装の存在 ! ・分散トランザクションするだけでも大変 謎の挙動あれこれ !

    ・クラスローダー ・デプロイし直したら変な挙動…… 可能な限り J2EE サーバーの利用は避けた方が無難な印象 (※ 個人の印象です) Java App Servers are Dead !
  34. 主な使用技術 ・SCSS - Alternative CSS ・CoffeeScript - Alternative JavaScript ・Backbone.js

    + Backbone.Marionette - MVC FW ・Handlebars - Template Engine ・Bower - Dependencies Manager ・Grunt - Build Runner サーバーサイドとは全く独立して Grunt で管理 ビルドの雛形は Yeoman で作成したものを拡張して利用
  35. Grunt ・各種アセットのコンパイル ・静的ファイルのビルド/圧縮 ・ビルド/環境毎の設定情報の埋め込み ・Livereload gulp とかあるけど ! 今のところ敢えて Grunt

    以外を使う必要性は感じていない SCSS → CSS CoffeeScript → JavaScript Handlebars → JavaScript i18n YAML → JSON ! etc …
  36. CoffeeScript 生 JavaScript は厳しい ! ・Bad Parts の存在 ・冗長で読みづらい !

    他の Alt JS 言語と比較して明確な利点がある ! ・Bad Parts の隠蔽 ・糖衣構文に過ぎない (学習コストの低さ) ・気持ち良いシンタックス ! 型は欲しいが … ! ・複雑なロジックはサーバーサイド ・GUI は結局ランタイムバインドの世界 経験の少ない JS プログラマが 多い場合は非常に有用
  37. Backbone.js 軽量 MVC “ライブラリ” ! ・最低限の “構造” の提供 ・黒魔術が無い ・軽量で自由に組み合わせられる

     ・デバッグが容易  ・学習コストがほぼ無い  ・既存ライブラリとの組み合わせが容易 経験値の低いチームはまずここから始めると良いと思う
  38. Angular ? Ember ? 重厚長大な FW が本当に必要か? ! ・内部で何をしているか分からないほど複雑 ・高い学習コスト

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

    ・デバッグも大変 ・アーキテクチャを強制される (侵略的) ・いずれの FW もまだまだ改善の余地が大きい Backbone は薄くて黒魔術が無いので挙動が全て把握出来る 個人的には薄いライブラリを組み合わせる方が良いと思う ! ・Backbone.js + Backbone.stickit / Rivets.js ・Vue.js + Superagent + Page.js
  40. Backbone.Marionette 素の Backbone には足りないものを補完する ! ・モジュール機構 ・イベントバス ・ネストした View のライフサイクル管理

    ・ボイラープレートの排除 Backbone だけだと結局これらを自作することになるので, Backbone を使うなら最初から導入した方が良い
  41. Marionette vs Chaplin Marionette は Backbone の “拡張ライブラリ” Chaplin は

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

    Backbone を使った “フレームワーク” Marionette は Backbone の部品を提供するだけだが, Chaplin はアプリケーションの作り方を規定する。 素の Backbone だけだと結局足りないものに気付くので, 経験値や好みによってどちらかを選択すると良い。 → 自前で作り込みたい人は Marionette → フレームワークが欲しい人は Chaplin
  43. Backbone 実装方針 モジュールを分割する ! ・共通コンポーネントを纏める ・サブアプリケーション毎にモジュール化する ・バックエンドとの連携を疎結合にする 出来るだけ Promise 化する

    ! ・バックエンドとの同期 ・非同期なアプリケーションの初期化 古典的な MVC とは違う ! ・Backbone らしく ・Backbone.View は Controller
  44. 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 を返さないのでラップすると良い
  45. アプリケーションの初期化も 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 を拡張して 非同期なコールバックも登録出来るようにする
  46. Backbone.js の問題点 バインディング ! ・それほど動的な画面で無ければ無理に導入しなくても良い ・とはいえあった方が良いと感じる場面は多い ・Backbone.stickit / Rivets.js など

    ! 貧弱なイベント管理 ! ・ネストしたモデルのイベント監視が出来ない ・backbone-deep-model や backbone-nested など ! ActiveRecord のように振る舞うモデル ! ・ViewModel とバックエンドへの Facade は分離したい ・既存ライブラリの取り扱いが悩ましい
  47. モジュール管理 Require JS (AMD) を廃止 ! ・やり過ぎ感が漂う……(結局最後は結合する) ・モジュール機構はあった方が良いが, 規模による ・ネームスペースなら

    Marionette.Module が提供してくれる ! Browserify ! ・プリプロセッサなので AMD より分かりやすい ・モジュール機構があった方がコードが構造化されやすい
  48. やってみた MVC の役割の明確化 ! ・Controller はアプリケーションのイベントを管理する ・View は DOM のイベントを管理する

    ・ViewModel は分離する ! バインディング ! ・Backbone.stickit を使ってみた ・Rivets.js は開発がアクティブではない印象 ・Ractive.js は大き過ぎる https://github.com/monzou/backbone.marionette.example
  49. 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 化もしてみた …
  50. 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 を使って自動化
  51. 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 の指定も出来る
  52. 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 は意識しない アプリケーションのイベントを 処理して画面を駆動する
  53. IE 9 対応の思い出 モダンブラウザになりきれていない ! ・Ajax を利用したファイルアップロード ・セレクタ数上限問題 ・描画パフォーマンス !

    IE 9 のみをターゲットにして開発するのは悪手 ! ・殆どの機能で IE 9 はモダンブラウザ ・Chrome Dev Tools は大変優秀 ・IE 固有問題は FW レベルで隠蔽しましょう
  54. 今回やっていないこと リアルタイム化 ! ・永続化 → XA → MQ → WebSocket

    / SSE で push ・もしまた Web のプロジェクトがあったらやりたい ! オフライン対応 ! ・今回は考えなかった ・一部データは初期化時に localStorage に永続化したが … ・金融のプロ向けアプリだとあまり必要になることはなさそう
  55. SPA は採用すべき 明らかなユーザーエクスペリエンスの向上 ! ・ネイティブクライアントに近づく ・業務アプリの方がむしろ採用しやすい ! 開発面でも利点がある ! ・クライアントとサーバーの分離が促進される

    ・将来的なクライアントの拡張がやりやすくなる ・開発環境も整備されてきているので開発効率は悪くない クライアントアプリケーションに「構造」がもたらされるため, 中途半端にリッチ化したクライアントより保守性や開発効率が上がる印象
  56. 開発環境 SPA 開発用の FW は成熟していない ! ・当面は流動的だと思われる ・まだまだ理想的な FW は存在しない

    ・今のところは Backbone.js ぐらい薄い FW がオススメ ! SPA の開発環境は整備されてきている ! ・Grunt や gulp のようなビルドツール ・Browserify や Require JS ・Chrome Dev Tools
  57. UI の設計が難しい 業務アプリでの SPA 採用事例がまだまだ少ない ! ・B2C の Web サービスと比べると非常に画面が複雑

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

    ・どのような画面が実現出来るのか/出来ないのか ・コモンセンスの確立 ! ネイティブアプリとは違う ! ・似たようなエクスペリエンスを提供出来るが実はかなり違う ・CRUD ひとつとってもどんな UI が良いかは検討の余地がある SPA の UI 設計のガイドラインが欲しい … UI デザイナーが欲しい …
  59. おさらい SPA 開発は “ふつう” の延長線上にある ! ・API とクライアントが違うだけ ・ドメイン設計やインフラ層の知見は流用出来る ・きちんとしたドメイン理解と開発を心がけましょう

    ! ! まだまだこれからの分野 ! ・開発環境は整備されてきているが今後ますます発展するはず ・ツールや UI 等を含めてまだまだ洗練される必要がある ! Keep it simple ! ! ・関連技術は多様化し複雑化する一方 … ・出来る限り複雑な仕組みを避けて軽量に作った方が良いのでは?
  60. まとめ SPA はこわくない ! ・これまでの延長線上にある ・きちんと設計出来れば経験値が少ないチームでも大丈夫 ! ! 継続的なキャッチアップは必要 !

    ・Java もモダンに生まれ変わりつつある ・Web 開発は進歩のスピードが物凄い ・半年経てば主流技術が変わることも … ・アーキテクトは多種多様な最新技術を知る必要がある ・ナレッジの蓄積が出来る体制づくりを