読者です 読者をやめる 読者になる 読者になる

React + ReduxではないSPAフロントエンド事情

丸山です。

弊社も最近はiOSアプリやAndroidアプリだけではなく、いわゆるSPAなWebApplicationを開発しています。

SPAを採用した背景

その背景には

  • サーバーサイドで複雑なビジネスロジックを実装することになるので、サーバーサイドはScalaを利用したい
  • ScalaでHTMLを吐いてもいいのだけれど、そういうのは動的型付け言語のほうがやっぱり楽な気がする
  • だったらScalaでJSONを喋るAPIだけ作ろう
  • そのAPIと喋ってwebのユーザーインターフェイスを実現する部分はRoRにするか?
  • それともいっそSPAにしてブラウザから直接APIとコミュニケーションすればよくないか?

と、いろいろ考えたところで、まずは社内向けのツールで技術検証も兼ねてSPAでScalaなAPIとコミュニケーションするのを試してみることにしました。

まだまだ開発中なのですが、意外とうまくいっているな、というのが現在の感想です。

採用したフレームワーク

最近SPAといえばだいたいReact + Reduxというのがトレンドであるように思います。しかし弊社ではVue.jsとvue-routerを利用しています。

これには、まだまだ開発メンバーの少ない弊社では、iOSアプリケーションやAndroidアプリケーションを開発しているメンバーがSPAアプリも開発するというようなことが多発することが予測できることが影響しています。

弊社ではiOSアプリの設計についてはだいぶ方向性が見えてきていて、その内容はこの前のヤパチーでも発表させていただきました。

techblog.reraku.co.jp

上述の発表内容を見ていただければわかると思いますが、弊社ではかなり古典的なMVWな設計でアプリケーションを実装しています。

さて、上述の通り、弊社には「JavaScriptバリバリ専任マン」のような開発者はおらず、iOSアプリなどを書いている開発メンバーがJavaScriptも書くことになります。そのようなビジネス上の制約があるとき、「なるべくiOSやAndroidで使っていた考え方と同じような考え方でSPAも開発することができる」というのは無視できないメリットです。そのため、「なるべく普通のMVW」が実現できて、なおかつModelの設計については何も強制してこないフレームワークが必要になったわけです。そのようなことを考えた結果白羽の矢が立ったのがVue.jsでした。今の所この選択は間違えていなかったな、と思っています。

Vue.jsを利用したSPAフロントエンド開発事情

使っている技術スタックとしては、JSまわりにVue.js、vue-router、Babel、CSSはsass、これらを webpack でビルドして使っています。

タスクランナー的なものは採用しておらず、npm scriptを利用しています。package.jsonの一部を抜粋するとこんな感じ。

  "scripts": {
    "build": "npm run build_js && npm run build_html",
    "build_js": "webpack --progress",
    "build_html": "cp src/index.html build/index.html",

    "watch": "npm run build_html && webpack -d --watch --progress",

    "clean": "npm run clean_js && npm run clean_html",
    "clean_js": "rm -rf build/js/*",
    "clean_html": "rm -rf build/index.html",

    "test": "mocha-webpack --colors --webpack-config webpack.config-test.js \"test/**/*.js\""
  },

タスクランナーを利用しないという選択にも「専任のJavaScriptマンがいない」ということが影響していて、「なるべく覚えなければいけないことを減らそう」という判断の結果このような運用になっています。「ビルドツールがCLIインターフェースを持ってるならそれをshellから叩いてしまえばいいではないか」というのはまあ自然な発想という気はします。

テスト事情

PDSを意識してアプリケーションの動きをModelレイヤーにどんどん寄せて書いているので、UIテストは今の所行っていません。nodeを利用してApplicationServiceのテスト(あとDomainModelのテストも)をすればそれでだいたい事足りています。

というのも、ヤパチーで発表した連打マシンと同じような感じの設計で、UI上の状態もほとんどAppliactionServiceが保持するModelたちが持っていて、その状態を書き換えたいときはApplicationServiceのメソッドをdispatchする、そしてその結果状態が書き換わったらObserver経由でUIに反映する、という一方向のデータフローを守っているので、ApplicationServiceへのテストがE2Eテストに近い役割を担っていてくれている感じです。

実際Vue.jsに依存するコードはプロジェクトのうちごく一部だけとなっているため、今の所このやり方でも大きな問題は出ていません。リファクタも快適に行えています。

ただ、どこかでUIテストのノウハウとReduxのノウハウもきちんと得ておきたいところではあります。このあたりは今後の課題ですね。

まとめ

弊社で最近行っているSPA開発事情について、「こんな感じだよ」というのを書いてみました。

最後に「また宣伝かよ!」という感じでアレなんですが、株式会社リラクでは「一晩でReduxに書き換えてやるぜ!むしろ俺がReduxのメリットの伝道師になってやる!」という気概のある開発者や、あるいは「これはこれで合理的な選択!一緒に開発していきたい!」という気持ちを持ってくれる開発者、あるいは「まだ自信はないけど、設計についての話ができる仲間たちといろいろ学びつつやっていきたい!」と思ってくれるような開発者を募集しています。マジで。ほんとに無限にやることがあるんです。助けてくれ!少しでも興味があるひとは是非 @ が誰からでもDMを受け付けるようになっているので気軽にコンタクトを取ってください。ほんとに待ってます。

YAP(achimon)C::Asia Hachioji リラク参加の御礼

こんにちは。ヘルステックチームの泉原です。

YAP(achimon)C::Asia Hachioji
リラクも、スタッフとしてお力添えさせていただきました。

また、ヘルステックチームの丸山・近藤は、トークでも参加させていただいております。

参加メンバーは各自、ブログで感想を書いています。
丸山ブログ
近藤ブログ
泉原ブログ

丸山・近藤は、ブログ内でトーク内容も公開していますので、
今回参加できなかった方も、トーク内容が閲覧できます。
ぜひご確認くださいませ!

今回主催のuzullaさん
会場提供のMicrosoftさん スタッフさん、参加者の皆様
皆様本当にありがとうございました!!

YAP(achimon)C::Asia Hachiojiに参加しました

こんにちは。ヘルステックチームの泉原です。

先日こちらのブログで報告した通り、ヤパチーにスタッフとして参加させていただきました。
http://yapcasia8oji-2016mid.hachiojipm.org/

初参加で、非プログラマの泉原ですが、とっっっても楽しかったです。

登壇した方も、見に来る方も、皆さん本当に純粋にプログラミングが好きな方々で、
話の内容が分からなくても熱意を感じるだけでも凄く楽しい場でした。
また、やはりプログラマーの方の話は構造化されていて、とっても分かりやすい!
知識がなくても説明が分かりやすいので、ついつい引き込まれて話を聞いてしまいます。

プロダクト管理やハードウェアの話、お肉の話など・・。
プログラムを愛する人たちから、プログラミング以外のトークも聞けるイベントです。
実際にコードが書けなくても、プログラミングが好きな方は、次回以降ぜひ参加してみていただきたいです。

ボランティアで開催してくれた主催の方々、スタッフの方々、登壇された方々、参加してくださった方々、
皆様のおかげでとても楽しい2日間を過ごすことができました。
本当にありがとうございます!

次回は私も「プログラマ向けセルフストレッチ」という内容で、LT参加表明しようと目論んでいます。
次回も楽しみです!

#yapc8oji 得票数4位トーク「あの日見たM V WhateverのModelを僕たちはまだ知らない」実況中継

丸山です。ヤパチー、最高に楽しかったですね。スライドの公開はしたのですが、正直重要なことは全部口頭で説明していて、スライドには情報が少ない。しかし、動画を見るのは正直だるい。40分もPCの前で集中して映像を観れないですよね。知ってる。というわけで、受験参考書の「実況中継」シリーズ(知ってます?)方式で、プレゼンをブログで再現します。長いぞ。でも多分動画見るよりははやく読み終われる。ぜひ読んでいってください。

アバンパート

f:id:nkgt_chkonk:20160706013649j:plain

今日はこういうトークをします。

f:id:nkgt_chkonk:20160706013650j:plain

簡単に自己紹介すると、こういうものです。

f:id:nkgt_chkonk:20160706013651j:plain

息子紹介です。かわいいですね。

f:id:nkgt_chkonk:20160706013652j:plain

WEB+DB Press 91号では特集を、92号ではPerlHackersHubに記事を書かせてもらいました。もう原稿料いただいてるので、もうこれ買ってもらってもわたしには一銭も入らないんですけど、いい雑誌なので買ってないひとはぜひ買って読んでください。

f:id:nkgt_chkonk:20160706013653j:plain

まずMVWってなに?って話からしていきたいんですけど、

f:id:nkgt_chkonk:20160706013654j:plain

MVCとかMVPとかMVVMとかみなさん聞いたことがあると思います。これらを総称してM V Whateverと呼んだりします。

f:id:nkgt_chkonk:20160706013655j:plain

f:id:nkgt_chkonk:20160706013656j:plain

違う名前ってことはそれぞれに違いがある一方、MVWとひとくくりにされるのだから共通点もあるわけですね。

共通点としては、これらは全部PresentationDomainSeparation(以下PDS)を実現するためのパターンである、ということが挙げられます。一方、それをどうやって実現するのか、という部分についてはそれぞれに違いがあります。しかし今回はその違いについては立ち入りません。

今さらっと「PDS」といいましたが、ではそもそもPDSとはなんでしょうか?

f:id:nkgt_chkonk:20160706013657j:plain

Martin Fowler先生が書いてるんですけど、

f:id:nkgt_chkonk:20160706013658j:plain

f:id:nkgt_chkonk:20160706013659j:plain

要するにプレゼンテーションとドメインの分離ですね。

「いやそれじゃあそのままじゃん」って感じなんですけど、プレゼンテーションとドメインとはなにかの前に、

f:id:nkgt_chkonk:20160706013700j:plain

Martin Fowler先生が「PDSってのはMVCの最も重要パートなんだよ」と言っていることを確認しておきましょう。そう、MVCはPDSと切っても切れない関係である、ということをここでは確認しておきましょう。

で、じゃあプレゼンテーションとドメインってなに?って話になるんですけど、まあプレゼンテーションはなんとなくイメージがつきそうですね。ユーザーが見れて触れる部分、みたいな感じだよな、って思いませんか。まあだいたいそんなもんです。

f:id:nkgt_chkonk:20160706013701j:plain

一方、ドメインって聞くと、「あっDDDでやったところだ!」ってなりがちなんですけど、PDSの文脈におけるDomainとDDDにおけるDomainってのはたまたま名前が同じなだけで、指してるものは違います。

f:id:nkgt_chkonk:20160706013702j:plain

じゃあPDSにおけるドメインってなに?って話になるんですけど、

f:id:nkgt_chkonk:20160706013703j:plain

MVWのModelに相当するのがドメインです。

f:id:nkgt_chkonk:20160706013704j:plain

とはいえですね、これって言葉を言い換えただけで、「じゃあMVWにおけるモデルってなんなんだよ」という問題は依然として残るわけですね。

f:id:nkgt_chkonk:20160706013705j:plain

よく言われる説明としてはこんなのがありますね。ただですね、

f:id:nkgt_chkonk:20160706013706j:plain

そうすると「ビジネスロジックって結局なんなのだ、宇宙とは、人生とは」みたいな哲学に迷い込むわけです。この会場で「ビジネスロジック」の定義言えるひとっています?……あ、いないですよね。わたしも言えません。

f:id:nkgt_chkonk:20160706013707j:plain

死って感じですね。お疲れ様でした。

でもこれみなさんが悪いわけじゃないと思うんですよ。そもそもビジネスロジックということばで指してるものがみんな違ったりするんですよね。そんな状態で、「ビジネスロジック」って言葉を使っちゃって名前がついちゃうと「なんだかわかったつもり」になったまま実はなにもわかってない、みたいなことになりがちなんですよね。もうだから今日は「ビジネスロジック」って言葉忘れちゃった方がいいと思います。

何が言いたいかっていうと、「モデルとは何か」ってのを言い表すのはすごい難しいんですよ。

f:id:nkgt_chkonk:20160706013708j:plain

というわけでですね、わからないときは分かりやすいところから考えましょう。

f:id:nkgt_chkonk:20160706013709j:plain

まあViewはわかりやすいですよね。iOSだったらUIViewがあるし、AndroidにもViewがある。webAppだったらHTMLとCSSで表現されるのがViewですよね。

f:id:nkgt_chkonk:20160706013710j:plain

実はWの部分もわかりやすくて、こういうやつらです。これらが何を担っているかっていうと、ViewをレンダリングしたりViewに値突っ込んだり、あるいはユーザーからの入力を受け取るみたいなこともやりますよね。

f:id:nkgt_chkonk:20160706013711j:plain

で、PDSに立ち返ってみると、ファウラー先生は「(the user interface)」っていってますよね。UIって見た目だけじゃなくてユーザーがボタン押したら云々みたいなユーザーインタラクションも含むものだってのは開発者のみなさんはよくご存知だと思うんですけど、それと照らし合わせて考えると、

f:id:nkgt_chkonk:20160706013712j:plain

VとWの担ってる責務こそが「プレゼンテーション」であるってことなんですよね。で、VとWでプレゼンテーションを実現するわけです。で、WにはCとかPとかVMとかいろんなものが入ってくるんですけど、これは要するに「どうやってプレゼンテーション層を書くか」のやり方をいろいろパターン化してくれてるわけですね。

f:id:nkgt_chkonk:20160706013713j:plain

じゃあ残りの、MVWでいうところModel、PDSでいうところのDomainっていうのはどんな責務を担当するんだろう、って話についに切り込んでいきます。

f:id:nkgt_chkonk:20160706013714j:plain

PDSにみたび登場してもらうとですね、赤字で書いたところ注目してください。「the rest」って書いてありますよね。

f:id:nkgt_chkonk:20160706013715j:plain

まあこういうことです。

f:id:nkgt_chkonk:20160706013716j:plain

ちーん、結局なんなのかわかんない!って感じですよね。

でもそれは当たり前なんですよ。そもそもPDSってのは、「プレゼンテーション層とそのほかを分けよう」って考え方だし、この「その他」が何を担うべきなのかってのはアプリケーションによって変わるわけです。つまり、これ大事なことなので何度も強調しますが、

f:id:nkgt_chkonk:20160706013717j:plain

そもそもMVWは「プレゼンテーション層をどう設計するか」のパターンであって、Modelをどう設計するべきかについては何も語っていないパターンなんですね。

f:id:nkgt_chkonk:20160706013718j:plain

はい、ここまでがアニメとかでいうところのアバンパートです。というわけでオープニング、タイトルコールどん!

Aパート

f:id:nkgt_chkonk:20160706013719j:plain

はい、こういうタイトルでしたね。MVWはModelの設計指針についてなにも語っていないことは確認できました。そうすると次の疑問として「じゃあModelはどうすんのよ、なんの指針もなしにどうやって設計すんの」って話が出てきます。そういうときに力になるのが様々なデザインパターンです。デザインって設計のことですよね。そういうパターンをたくさん知ることで、「ははーんこれはxxパターンを適用すべき事案ですね?」とか見えてくるようになるわけです。

f:id:nkgt_chkonk:20160706013720j:plain

今日はそんな中でみっつほどパターンを紹介しましょう。

f:id:nkgt_chkonk:20160706013721j:plain

まずはActiveRecordパターンです。これRailsのおかげでみなさんも結構よく知ってると思うんですけど、

f:id:nkgt_chkonk:20160706013722j:plain

こういうパターンですよね。

ただ、Railsで使われてるActiveRecordライブラリを使えば自動的にARパターンが実現できるわけではないんですよ。

f:id:nkgt_chkonk:20160706013723j:plain

たとえばこういうコード見てみましょう。Controller で AR::Base 継承したNotificationから、unreadなものを引いてきて、それを全部readに更新してます。いわゆる「mark all as read」ってやつですね。

ところで、ControllerってPDSでいうとPとDどっちに属すやつでしたっけ?プレゼンテーション層ですよね。でも、この「DBからレコード引いてきて更新する」って、全然ユーザーインタラクションじゃないですよね。そこで、ARパターンを適用してみます。

f:id:nkgt_chkonk:20160706013724j:plain

はい、テーブルに対する操作なのでクラスメソッドとして実装しました。いいですね。(あっ、これActiveRecord::Base継承しわすれてますね、してると思って読んでください)

注目すべきはcontrollerです。これ、ユーザーからリクエスト、つまり入力を受け取って、Modelに「あとはまかせた!」ってやってますよね。これはユーザインタラクションの実装で、プレゼンテーション層の責務の範囲に収まってると言っていいと思います。

f:id:nkgt_chkonk:20160706013725j:plain

こうしたことによって、なにが嬉しいかというとですね、実はプレゼンテーション層ってのはだいたいテストしにくいんですよね。RailsだったらRack経由でテストしないとダメだし、iOSアプリとかだとViewとかViewControllerが絡むと一気にテストしにくくなりますよね。でも、Cの責務をユーザーインタラクションだけに絞ったことで、あとはRackとかそういうテストしにくいところ経由しないでテストが書けるようになりました。嬉しいですね。

あと、もう一個いいことがあります。Cにいろいろ書かれちゃうとそこ再利用するの難しいんですけど、こうやってmodelに書いておけば、controllerからだけじゃなくてrakeタスクとかからも呼べるようになりますよね。これって、「プレゼンテーション層を入れ替えてる」ってことなんですよ。このアプリケーションを使うクライアントからみると、RailsのViewとかControllerで実現してるのってHTTPを喋るプレゼンテーション層なんですよね。一方で、CLIのインターフェイスを実現するrakeタスクなんかからこのModelを使えば、rakeタスクの層が「CLIを喋るプレゼンテーション層」になるってわけです。こうやってプレゼンテーション層をぽこぽこ入れ替えることができるのも、PDSに沿うことで得られる強みですね。

f:id:nkgt_chkonk:20160706013726j:plain

とはいえ、現実は厳しくて、「これでめでたしめでたしやで!」とはなかなか行かないんですね。よくある課題としては、複数のテーブル触るようなユースケースをどっちに書くのか問題ってのがありますよね。あるいは、ARを継承したモデルの中でトランザクション開始しちゃうと、そのメソッドをほかのところからも使おうとすると多重にトランザクションが開始されちゃってうわあみたいな感じになったり、みなさんも経験ありませんか?

そういうのむりしてActiveRecordパターンで扱おうとしていろんなところにぐちゃーっと詰め込んでいくと、

f:id:nkgt_chkonk:20160706013727j:plain

まあメンテ不可能なRailsアプリが出来上がるわけですね。はい、お疲れ様でした。

f:id:nkgt_chkonk:20160706013728j:plain

じゃあ、何がいけなかったのか考えてみましょう。そもそもActiveRecordパターンってのはどんなパターンでしたっけ?「テーブルと」クラスが1:1なんですよね。そもそもテーブルとクラスが1:1なんだから、複数テーブルにまたがった関心を扱うには「食い合わせのわるい」パターンであるってことが言えるんじゃないでしょうか。実際、実践DDDっていう最高の本があるんですけど、この中でも「単なるCRUDアプリならRailsでいいかもね」って言ってて、要するにActiveRecordパターンが対象にするのってCRUDの範囲だけなんですよね。つまり、

f:id:nkgt_chkonk:20160706013729j:plain

ARパターンだけでこの世のすべての問題を解決しよう、というのは無理があるわけです。

f:id:nkgt_chkonk:20160706013730j:plain

ちなみになんですけど、RubyのARライブラリってARパターンをもとにしたライブラリなんですけど、単なるARパターンじゃなくてもっといろんなことやってくれるんですよね。だから、RoRの界隈ではARライブラリベースでやっていこうという考え方もあるにはあるっぽいんですけど、あまりこれでうまくいったという話を聞かないような気がします。逆にARライブラリベースで上記みたいな問題を解決したぜっていうのは興味ある話なので、そういう話知ってる人はぜひ教えてください。皮肉とかじゃなくてまじで聞きたいです。

f:id:nkgt_chkonk:20160706013731j:plain

ええと、話を戻して、ARパターンですべての問題が解決できるわけではないということを見てきました。そこで、別のパターンとしてTransactionScript(以下TS)ってのを見てみましょう。

f:id:nkgt_chkonk:20160706013732j:plain

TSってのはユースケースと一対一になるメソッド定義して、そのメソッドの中に全部手続きを書いていくやつです。

たとえば、航空機の座席予約について考えてみましょう。まず、

  • その席が誰かに先に予約されてないかチェックして
  • ビジネスクラスの料金でファーストクラス予約しようとしてないかチェックして
  • 場合によってはパスポートのvalidityチェックして

とか、いろいろとやるべきことはあるわけです。それを全部上から下に書き下していくスタイルですね。

これを適用すると、こんな感じのコードが出来上がってきます。

f:id:nkgt_chkonk:20160706013733j:plain

いろんなモデルを使って、ユースケースをがんがん書いていくわけですね。トランザクションもこのメソッドの中で開始してcommitすればいいです。

ちなみに、これはTSとARの合わせ技になってますね。

ここでもcontrollerに注目してください。入力を受け取ってTSのメソッドをdispatchしてるだけです。TransactionSctiptパターンで作ったこのNyanTransactionクラスもModelの一種であることに気をつけて下さいね。再度確認しますが、Modelというのは「プレゼンテーション以外全部」なのだから、当然ARを継承してないこのNyanTransactionもModel層のものです。だから、この場合もCは入力を受け取ってMの処理をdispatchしてるだけなわけです。

f:id:nkgt_chkonk:20160706013734j:plain

で、こういうふうにすると何がうれしいのかって話なんですけど、ARだけでは実現できてなかった複数の関心をうまくまとめられてるし、トランザクションもここに書いておけばバッチリ良さそうですね。

f:id:nkgt_chkonk:20160706013735j:plain

とはいえやっぱり現実は厳しくて、まあ一定以上複雑なアプリケーションをTSパターンでとこうと思うと、似たようなロジックがあちこちにコピペされて死んだり、ひとつのメソッドの行数数えたら10億行あってそれ読むのに5億年かかるしもはやテスト不能みたいな状態になるんですよね。

f:id:nkgt_chkonk:20160706013736j:plain

はいお疲れ様でした、という感じです。

f:id:nkgt_chkonk:20160706013737j:plain

とはいえですね、そこまで複雑でないアプリケーションならば、TSってすごく見通しがいいんですよ。どっかがバグったら、そのユースケースと1:1になってるメソッド読みに行けばいいって一瞬でわかるし、そもそもそのメソッドも「あれしてこれしてそれする」ってことしか書いてないから、複雑じゃなかったらむしろ読みやすいんですよ。あと、余計な設計しないで済むんで、初期開発速度も速いわけです。

そうなってくると、たとえば「とにかくバギーでもいいから早くリリースしてくれ!そんである程度までユーザー数伸びたらどっかに売ってあとメンテしてもらおう!そんで遊んでくらそう!」みたいなビジネス要請がある場合にはまあ倫理的にどうかって問題はあるにせよ、TSでドバーって書いちゃうほうがよかったりするんですよね。一方で、「複雑な内容だから急がないんだけど、ずっと直しながら使っていくシステムをしっかり作ってほしい」っていうようなやつをTSで設計したらちょっとまずいわけですよね。

そんな感じで、モデルをどう設計するのかってのは実は「どういうビジネス要請があるのか」みたいなところと深く関わっているわけですよ。だから、モデルを設計するときには各パターンのメリットデメリットを知って、そしてそのパターンが何を解決しようとしているのかを知って、その上で「こういう要請があるから今回はこのパターンを使っていこう」というように設計を進めていくべきなんですね。

だから「TSはクソ、時代は○○」みたいな話ではないんだよ、ってのを強調しておこうと思います。

f:id:nkgt_chkonk:20160706013738j:plain

まあとはいえですね、TSが適さない問題領域、ビジネス要請っていうのはあるわけですよね。複雑でなおかつ堅牢でなければならないみたいな。

そういう領域を解くときには、Layered Architcture(以下LA)を検討してみたらどうでしょうか。

f:id:nkgt_chkonk:20160706013739j:plain

どういうものかっていうと、こんな感じです。プレゼンテーション層はいいですよね、その下が全部モデルになるわけですけど、この層を、みっつに分けます、まずはCがdiapatchする窓口となるApplicationServiceっていう層があって、あとDBアクセスとかそういう汎用的な技術を実現するレイヤーをInfrastructureって定義して、それ以外をすべてDomainModelという層にやってもらう。そういうパターンです。

これ何がいいかっていうと、InfrastructureがDBアクセスとかやってくれるから、DomainModelはほんとに解きたい問題に集中できるんですよ。

たとえば、タスク管理システムを考えてみましょう。

f:id:nkgt_chkonk:20160706013740j:plain

そのタスク管理システムは、一回だれかがアサインされたタスクに関しては二度と「だれもアサインされた状態」には戻せないってことにしましょう。これはマネージャーはだれかをアサインしたと思って安心してたらだれかそれをさし戻しちゃって、誰も誰がボールを持っているのかわかんない宙ぶらりんのままのタスクができるのを避けるためです。あと、一回クローズされたらもう二度とオープンできないようにしちゃいましょう。「一回クローズしたのは終わったんだからそれは終わったものとして扱う!!!再オープンしたい?それは修正ではなくて変更です!新しいチケットを切ってください!」ってことですね。

まあそういう「このアプリケーションが解きたい問題領域」ってのをDomainModelにOOでコードでモデリングしていきます。

f:id:nkgt_chkonk:20160706013741j:plain

そんで、さっき定義したTaskStatusを利用するTaskクラスをこんな感じで定義してみましょう。

このシステムではタスクはマネージャしかオープンできません。権威主義的で最悪のサービスですけど、まあ例として許してください。そういう知識をTask.openメソッドの中にこうやって書いていきます。あとはまあ普通にオブジェクト作って返してますね。

あるいは、Taskをだれかにアサインするメソッドみてください。やっぱり権威主義的で最悪のサービスなんですけど、だれかにタスクをアサインできるのはマネージャだけです。あと、Taskの状態遷移はできる遷移とできない遷移がありましたよね?それをモデリングしたTaskStatusを利用して、「ちゃんとこれは許された遷移かな?」とかをチェックしたりしてます。

ここで注目してほしいのは、TaskとかTaskStautsみたいなDomainModelに定義されてるクラスは、自分がどこに永続化されてるとか気にしてないことです。そういう技術的な詳細を避け、「複雑な問題」に集中して丁寧にOOで設計して責務分割していくわけです。

f:id:nkgt_chkonk:20160706013742j:plain

そうやって丁寧に責務分割したとしても、オブジェクトがメモリに乗ってるだけではまああまり意味ないですね。永続化はどうすんの?という話があります。今回はリポジトリーパターンってのを使ってみましょう。リポジトリパターンってのはどういうやつかっていうと、なんちゃらリポジトリにDomainModelで定義したなんらかのオブジェクトを渡して永続化してもらったり、永続化されてるところから情報引っ張ってきてそれを使ってDomainModelで定義したクラスのインスタンスを返してくれたりするようにするパターンです。今回だったらTaskをRDBに保存するTaskRepositoryってのを定義してみました。

f:id:nkgt_chkonk:20160706013743j:plain

で、ApplicationServiceがDomainModelとInfrastructureを利用して、ユースケースを実現します。ここに定義したメソッドがControllerとかからdispatchされるわけですね。

f:id:nkgt_chkonk:20160706013744j:plain

こういうパターンを利用することで、TSのときは一枚岩でメンテ不可能だったような複雑な問題を適切に分割して統治することができそうです。

一枚岩のスクリプトをテストするのは非常に困難ですが、今回はDomainModelをきちんと設計しているので、単体テストも非常にしやすいですね。

あと、DBアクセスをInfrastructureに追いやったことで、複雑な問題を解く部分、すなわちDomainModelの層はDBなしでテストできるようになりました。最高ですね。

f:id:nkgt_chkonk:20160706013745j:plain

とはいえやっぱり銀の弾丸はないわけで、これって非常に難しいんですよね。そもそもLAに慣れてないプログラマにとっては「これどの層に書けばいいの?ApplicationService?DomainModel?」みたいな話になりがちです。

まあそれだけならともかく、OOって本質的に難しいと思うんですよ。さっき私が書いたTaskStatusとTaskの責務分割もほんとにあれでよかったんですかね?遷移先を渡すとそこに遷移できるかどうかをboolで返すんじゃなくて、TaskStatus#transit_to_assignedみたいなメソッドにして、そいつが繊維先の状態であるTask::Assingedのインスタンス返すようにする。そんで、もし行けないステータスに行こうとしてたら例外吐くみたいなほうが「チェック忘れ」とかなくなるしいい設計なのでは?

そんなわけで、とにかくやっぱりOOって難しいんですよ。そうするとドメイン層に関してはプログラマとしてのレベルを上げて物理で殴るとか、あるいは複雑な問題領域に対する深い理解とかが必要になってくるんですよね。

f:id:nkgt_chkonk:20160706013746j:plain

ほんとにつらいですね。はい、お疲れ様でした。

f:id:nkgt_chkonk:20160706013747j:plain

とはいえですよ、われわれはもともと「複雑な問題」に立ち向かうためにここまでやってきたわけです。そもそも難しい問題を相手にしているのだから、そこはがんばって、歯を食いしばってやっていくしかないんですよね。こういう複雑な問題を解くときにこそ、OOへの深い理解とか、あるいはDDDへの理解みたいなのが役にたつのだと私は思います。やっていきましょう。

f:id:nkgt_chkonk:20160706013748j:plain

ちなみにFAQなんですけど、聞きかじりでこういう話を「これって要するにDDDでしょ?」っていう向きもあるんですけど、DDDとLAは同一視しないほうがいいです。DDDの中でLAに触れることはありますが、DDDはそれだけではなくてもっといろんな概念を含んだ、より広い概念です。むしろLAはおまけというか、DDDを生かすための前提みたいなところがあると思います。単なるLAをDDDと呼んだりするとDDDの怖いひとにはてブとかTwitterで怒られるみたいな光景をよく目にしますので、気をつけてやっていきましょう。

f:id:nkgt_chkonk:20160706013749j:plain

はい、ようやくAパート終了です。というわけでアイキャッチどん!

f:id:nkgt_chkonk:20160706013750j:plain

あー、かわいいですね。

Bパート前のアイキャッチもどん!

f:id:nkgt_chkonk:20160706013751j:plain

あー、かわいいですね。不審なお兄さんもいますね。

Bパート

f:id:nkgt_chkonk:20160706013752j:plain

はい、つづいてBパートです。Aパート結構概念的な話多かったんで、GUIアプリを例に実際になにかを作ってみましょう。

f:id:nkgt_chkonk:20160706013753j:plain

こういう連打マシーンはどうでしょう。ボタンをタップすればカウンタが1進んでいくのでガンガン連打してくれ!!ってやつです。ただひとりでやってても面白くないので、ネットワーク越しにいろんな人が同時に連打します。で、手元のカウンタには自分の連打数じゃなくて、「みんなでどれだけ連打したか」が表示されます。

f:id:nkgt_chkonk:20160706013754j:plain

ちょっと実装方針を考えてみますが、ボタンを一回タップしたごとにHTTPリクエストとか飛んだらまじで目も当てられないという感じなので、クライアント側では連打をバッファリングしておいて、一定時間ごとにそれをflushするようにしましょう。

とはいえ、タップした瞬間にカウンタが動かないとUXとしては最悪なんで、タップすると、「実際にはまだサーバに送ってないんだけど、見た目の都合でカウンタは1増える」というようにしましょう。

あとは、サーバーから定期的に「現在のみんなの総連打数はこれだよ」って送られてきて、それを受け取ったらカウンタをその数字まで進める、という実装方針でいきましょう。

f:id:nkgt_chkonk:20160706013755j:plain

単なる連打マシンなんですけど、実装がちょっと複雑になりそうですね。そもそもRDB関係ないアプリなんでARパターンはマッチしなそうです。バッファリングとかいろいろあるからTSもマッチしなそうですね。LAっぽく考えてみましょう。

f:id:nkgt_chkonk:20160706013756j:plain

まずはModelを考えていきます。

f:id:nkgt_chkonk:20160706013757j:plain

なにはともあれ、カウンターを表すクラスが必要ですよね。タップされたときは1増えるのでincrementメソッド生やしておきましょう。あと、サーバーから数字送られてきたらそこまでいっきに飛ばないとだめなんで、setCountメソッドも生やしておきましょう。こいつらが内部に保持したcountを操作します。

f:id:nkgt_chkonk:20160706013758j:plain

はい、こんどはタップ数をバッファしてくれる君です。タップされたらカウントをインクリメントして、flushメソッドが呼ばれたらflushします。今回はプレゼンの時間の都合上サーバーにアクセスする部分は省略しますが、本来ならもっと真面目に書かないとダメです。

f:id:nkgt_chkonk:20160706013759j:plain

で、あとはサーバーから送られてくる値をさっき作ったカウンターにセットしてあげる君ですね。connectToServerすると、一定時間おきにサーバーから値が送られてきて、それをcounterにセットしてます。

これもプレゼンの時間の都合上バッサリ実装カットしてますが、もっとまじめに書かないと本来はダメです。

f:id:nkgt_chkonk:20160706013800j:plain

さて、あとは、これらを束ねてユースケースを実現するApplicationServiceを書きましょう。

プレゼンテーション層からconnectToServerが呼ばれたらfetcherを起動します。起動しておけばあとは勝手にcounterがアップデートされます。これはコンストラクタに書いてもいいかもですね。

ボタンがタップされたらこのtapメソッドを呼びます。そうすれば、カウンタは1増えるしバッファもされます。

あとは、プレゼンテーション層が一定時間おきにflushBufferメソッドを呼んであげれば、サーバーに値が送信されます。

f:id:nkgt_chkonk:20160706013801j:plain

さて、これでアプリケーションの挙動はモデリングできました。

f:id:nkgt_chkonk:20160706013802j:plain

こんどはプレゼンテーション層を書いていきましょう。

f:id:nkgt_chkonk:20160706013803j:plain

こんな感じで、Viewがロードされたら、さっき作ったapplicationServiceをセットアップします。あとはタイマーイベント仕込んで、定期的にapplicationServiceのメソッドを叩いてあげます。また、tapされたというイベントが起こったらapplicationServiceのtapメソッドをdispatchしてあげましょう。

タイマーイベントってここに書くの?という疑問も上がりそうですが、タイマーってアプリケーションに対する一種の入力だと考えることができると思いませんか?そう考えるとPresentation層にあるのが自然な気がしますね。あとこのほうがModelのテストしやすい。

f:id:nkgt_chkonk:20160706013804j:plain

まあこんな感じで、プレゼンテーションからイベントを受け取ってapplicaitonServieをinvokeするところまでできました。

f:id:nkgt_chkonk:20160706013805j:plain

ただですね、これひとつ問題があって、これだけだとタップしてもタイマーイベントが走ってもサーバーから値が降ってきても、Viewが描き変わらないんですね。内部でModelの状態が変わりまくっていくだけで、画面が一切動かない。これはあかんですね。

f:id:nkgt_chkonk:20160706013806j:plain

で、よく考えると、今回はCounterモデルの値が書き換わったときに、それに合わせてUILabelも書き換えればそれだけでよさそうです。

f:id:nkgt_chkonk:20160706013807j:plain

f:id:nkgt_chkonk:20160706013808j:plain

なんかの状態が変わったことを検知して、なにかをしたい。

f:id:nkgt_chkonk:20160706013809j:plain

Observerパターンでやったところですね!

f:id:nkgt_chkonk:20160706013810j:plain

というわけで、CounterをObservableにしてあげましょう。

値が書き換わったら、notifyメソッドを読んで、自分を監視してるひとたちに状態を通知してあげるわけです。

f:id:nkgt_chkonk:20160706013811j:plain

さて、それに対して、VC側ではこのCounterをobserveして、状態変化を検知して、その結果をUILabelに反映するようにします。

f:id:nkgt_chkonk:20160706013812j:plain

いいですね。これでひとまず完成です。

f:id:nkgt_chkonk:20160706013813j:plain

これ、Observerパターンを利用してModelの状態をViewControllerに開示したことが結構ポイントです。CounterモデルがViewControllerやUIViewを保持して、Counterの値が書き換わったときにそれらのメソッドを読んでViewControllerやViewに変更を伝える形にしてしまうと、CounterがViewModelやViewに依存してしまします。つまり、CounterをテストしようとしたときにViewControllerやViewが絡んできて、めっちゃテストしにくくなりますよね。

でもObserverパターンを使えば、「Modelの値や変更をPresentationに開示はするが、Presentationに依存しない」という状態を保つことができます。実際さっきのCounterクラスってimport Foundationしてるだけでしたよね。テスタブルです。

あとですね、モデル層がCocoaTouchフレームワークに依存しなくなったことで、モデル層は熟練のiOSアプリプログラマ以外にも読めるし書けるような内容になってますよね。これっていいことですよね。レビュー可能な人間が増えるし、分業もしやすいし。実際さっきの連打アプリのモデル層、iOSアプリ書けない人でも内容は理解できたと思います。

f:id:nkgt_chkonk:20160706013814j:plain

実際ファウラー先生も、冒頭で紹介した記事でこんなこと書いてて、「あー、PDSが実現できて最高だね、よかったよかった」って感じですね。

f:id:nkgt_chkonk:20160706013815j:plain

というわけで感動のエンドロールにいきましょう。このspecial thanksって書かれてるのはプレゼンの練習に付き合ってくれた友人たちです。あ、あと聴いてくださってるみなさんにもspecial thanksを。いやー、よかった、とか思ってたらですね。

f:id:nkgt_chkonk:20160706013816j:plain

ディレクターから割り込みが入るわけですよ。エンドロール止めましょう。最悪です。

f:id:nkgt_chkonk:20160706013817j:plain

なにかっていうと、今の実装だと、サーバーから数字が送られてきたときにカウンタの数字が4から10000とかにいきなり飛んだりするわけですよ。まあそれは「間違い」とか「バグ」とはいえないんだけど、最悪のUXだし、まあユーザはびっくりして「これバグじゃん」って思っちゃいますよね。たしかによくない、でもディレクタさん、あなたはプロなんだから「バグ」じゃなくて「よくないUI」くらいに言ってくれ、たのむ、という感じですね。

f:id:nkgt_chkonk:20160706013818j:plain

まあそんな感じで、現実はあまくないですね、エンドロールなんて見てないでまだ仕事しないとダメそうです。お疲れ様でした。

f:id:nkgt_chkonk:20160706013819j:plain

さて、とはいえ、やっぱりいいアプリ作りたいんで、UIを改善しましょう。これってUIの話なんで、プレゼンテーション層でやっていきますよ。

f:id:nkgt_chkonk:20160706013820j:plain

まず、UILabelをサブクラッシングしたカスタムなCounterLabelってのを定義します。これは内部に「ほんとのカウント」と「今表示してるカウント」を持ってて、ほんとのカウントがセットされたらタイマー起動して表示してるカウントのほうをタイマーで1ずつupdateしていきます。で、表示してるカウントがほんとのカウントにおいついたらタイマー止めます。

これ、ロジックですよね。「どう見せるか」っていう関心なんで、Presentationの関心ですよね。いわゆる「プレゼンテーションロジック」です。これはViewに閉じ込めてしまいましょう。

f:id:nkgt_chkonk:20160706013821j:plain

で、VCで直接UILabelに値をセットしてた部分も、この変更に合わせて変更しておきましょう。これで、サーバーから値が送られてきたときにはいきなり数字が飛ぶんじゃなくて、見た目上はすごい勢いで数字が増えてるみたいに見えるようになったでしょう。めでたしめでたしですね。

ちょっと話がずれますが、今回はViewにプレゼンテーションロジックを書きました。MVPならPresenterにこういうロジック書けば、そこがテスタブルになっていいですね。

f:id:nkgt_chkonk:20160706013822j:plain

さて、MVPにするのがさっきみたいな古典的MVCにするのかはともかく、今回、UI上の問題を解決するときにはPに手を入れただけで、アプリケーションの本質的な問題をモデリングしたDには一切手が入らなかったことに注目してください。こんな感じで、PDSを守っているといろいろいいことがありますね。

とはいえ、実はこのアプリもまだまだぜんぜん完璧ではありません。

f:id:nkgt_chkonk:20160706013823j:plain

たとえば、今回ってサーバーから送られてきた数字をカウンタにセットする君をModelに書いちゃいましたけど、これって本当にそれでよかったんですかね……?

タイマーイベントがアプリケーションへの入力だと見なすのが自然なように、サーバーからの入力もアプリケーションへの入力であると見なすのが自然なのではないでしょうか。そうするとこれはプレゼンテーション層で管理すべき関心の気がします。

f:id:nkgt_chkonk:20160706013824j:plain

それを考えると、プレゼンテーション層にもう一個、ActionProviderみたいな層があってもいいかもしれませんね。ここには、タイマーイベント発行してくれる君だとか、サーバーからpushしてくる値を受け取ってイベントを発行してくれる君みたいなやつを定義しておいて、VCがこいつらを利用してモデルに処理をdispatchしたほうがいいかもしれません。

f:id:nkgt_chkonk:20160706013825j:plain

あと、今回Observerパターンを結構重要な要素として使っていますが、実はObserverパターンってmutableな世界が前提のパターンなんですよね。それは当たり前で、変化しないものは監視する必要がないからです。しかし、よく言われるように状態のmutationは問題を複雑にします。最近はだから「なるべくimmtableに寄せていこうぜ」というのがトレンドになってきてますよね。それを考えるとObserverパターンを素朴に使ってるのはまだまだ改善の余地がありそうに見えます。

あと、今回API呼び出しダミー実装でDomainModelに置いちゃいましたが、API呼び出しって本来Infrastructureがもつべき関心ですよね。DBとのやりとりと同じく、外部システムとのやりとりっていう技術的な詳細ですし。そう考えるとやっぱりAPI呼び出しの部分はInfrastrucreに持つべきなんじゃないかな、とかいろいろ考えるわけです。

f:id:nkgt_chkonk:20160706013826j:plain

あと、これは今回のGUIアプリの例とはあまり関係ないんですけど、Repositoryパターンってクエリと相性最悪なんですよね。「いろんなテーブルをjoinした上で特定のカラムしか必要ない」みたいなやつって、DomainModelで定義したオブジェクトに綺麗にマッピングできないことが多いです。あと、クエリの種類によってRepositoryにファインダーメソッド定義しまくっていくと、異常な量のメソッドがRepositoryに生えてきて破滅します。

テーブルと1:1で設計できたARのときはまあよかったんですけど、Repositoryパターンを使ったことで、ここにきていわゆるOOとRDBの間のインピーダンスミスマッチ問題に悩まされるようになったわけです。つらいですね。

f:id:nkgt_chkonk:20160706013827j:plain

で、こういうことを考え始めると、いろんなことが気になってきます。さっきのActionProviderとか、immutableな設計とか考えると「おっこれってFluxアーキテクチャに近づいて行っていないか?いっそFluxにするべきか?」とか、「リポジトリパターンでうまくクエリが扱えないなら、コマンドとクエリを別にモデリングしちゃうか、あ、それってCQRSだよな」とか「じゃあいっそEventSourcingまで持って行ってしまうか?」とか、まあいろいろと本当に考えることが増えてくるんですね。これは完全に設計沼です。

そういうときには、「わたしたちはどんなビジネス要請によってアプリケーションを開発していて、このアプリケーションは何を解決するためのものなのか」ということに立ち返ってみましょう。単なるCRUDアプリケーションに対してCQRSを適用するなんて馬鹿げた話です。今からとても大事なことを言いますが、世の中には「最高の設計」なんてなくて、問題や課題に対する「最適な設計」があるだけなんです。だから、最初の話に戻りますが、「万能なモデルの正しい設計」なんてないんです。

f:id:nkgt_chkonk:20160706013828j:plain

そんなわけで、ここから先は、みなさんの作るアプリケーションがどのような課題に対するものなのかによって物語が変わってきます。「ここからさきは君自身の目で確かめる」しかないわけです。つらい。つらいですが、ここで唐突にゼルダの伝説に出てくるトライフォースの話をしましょう。

トライフォースはみっつに分かれているのですが、そのみっつとは、力のトライフォース、知恵のトライフォース、勇気のトライフォースです。

様々なパターンを知り、そのパターンのメリット、デメリット、そして何を解決するパターンなのかを知れば、それはあなたの力になります。力のトライフォースですね。

ただ、力があるだけではダメで、今度は課題に対してどのようにその力を使っていくのかを知恵を絞って考える必要があります。これが知恵のトライフォースです。

そして、正直いって、複雑な現実に立ち向かうのはかなり厳しいです。勇気を持って、力を知恵を運用することで初めて、良い問題解決が生まれるのではないでしょうか。

f:id:nkgt_chkonk:20160706013829j:plain

まとめます。

  • PDSを心がけることでいいことがたくさんあります
  • MVWはPについては設計指針をくれます
  • Mについては設計指針をくれません
  • Mについてはあなたがどのような課題に向き合っているのかによって適切な設計が変わってきます。
  • 大変な旅になりますが、トライフォースがあなたの力になるでしょう。

f:id:nkgt_chkonk:20160706013830j:plain

最後に宣伝させてください。わたしが働いている株式会社リラクでは、普段からこういう話をプログラマ同士でやりながら開発を進めています。みなさんの会社もそうかもしれませんが、正直いって無限にやるべき仕事あるので、仲間が欲しいです。一緒に、課題に対して最も適切な設計を追求していきたい仲間を募集しますので、「あっなんだか雇用が流動化しそうになってきた」みたいな方は是非声をかけてください。一緒に悩みながらやっていきましょう。

f:id:nkgt_chkonk:20160706013831j:plain

宣伝で最後終わるのちょっといやなんで、またまとめのスライド写して、トークを終えさせていただきます。ご清聴ありがとうございました。

Q & A

  • Q「ActiveRecordパターン使ってるとViewで複数テーブルから必要な値だけ引いてきてみたいなときにつらいことがあるんだけど相性悪いの?」
  • A 「そのクエリとARパターンの相性がわるいと言っていいと思います。わたしはそういうときは、クエリに関してはSQL直接書くレイヤーである「ReadLayer」っての作っちゃって、そこでSQL組み立ててそのViewに特化したDTOにレコードぶっ込んでPresentation層に渡しちゃったりしてますね。

結び

以上です。どうだったでしょうか。最後にちょろっと出てきましたが、弊社ではプログラマを募集しています。興味のある方は是非ブコメなどに「興味あるな〜。@your_twitter_screenname まで連絡くれないかな〜」とか書いておいてください。あるいは @neko_gata_s にリプライをくれてもいいです。一緒に悩みながらやっていく仲間がほしくてたまらないんです!よろしくおねがいします。

YAP(achimon)C::Asia Hachioji 2016 mid in Shinagawa

こんにちは。リラクヘルステックチームの泉原です。
エンジニアではないのですが、リラクでの店舗経験を元に、未来のヘルステックメンバーのアシストをさせて頂いております。
リラクゼーション業が、ITテクノロジーとの融合で新しい文化を創る場に立ち会えて、とても嬉しく感じています。
これからちょこちょこブログも更新するかと思いますので、よろしくお願いします!

今回は、イベントのご案内です。
題名の通り、技術者による、技術者向けのイベント・YAPCに、リラクヘルステックチームの丸山・近藤が登壇させて頂くことになりました!

YAPCについて

YAPCとは、Yet Another Perl Conference の略称です。
カンファレンスというと、高い参加費が必要な場合や、マーケティング寄りのものが多くなりがちですが、YAPCは高い参加費を払うことなく、技術者向けのトークを技術者から聞ける、Perl関連の人なら誰でも気軽に参加できるカンファレンスとしてはじまりました。
エンジニアのお祭りという位置付けでもあり、世界各国で開催されています。

過去の開催

日本でのYAPCは、 YAPC::Asia Tokyo が過去10年間開催をしていました。
大変人気のあったカンファレンスでしたが、開催10周年を目処に一区切りとし、昨年の開催が最後、ということで公表されています。

今回のYAPC

今回のYAPC主催者様は、YAPC::Asia Tokyoに、聴講/登壇/運営において全て参加経験のある方が、YAPCへの敬意を持って、ボランティアで企画をしてくれたイベントです。
YAPC::Asia Hachioji 2016 mid in Shinagawa

イベントは、7/2(土)・7/3(日)の2日間、品川のマイクロソフトジャパン社のセミナールームで開催されます。
イベント開始まであと3週間もありますが、既に一般参加(抽選)の応募数が来場最大数を超える日程もあるほど、期待値の高いイベントです。

そんなイベントに、リラクからは丸山と近藤が、登壇者として参加させて頂きます。
トーク内容は以下の通りです。

トーク内容

あの日見たM-V-WhateverのModelを僕たちはまだ知らない

トーク概要

MVCやMVP、MVVMなど、様々なアーキテクチャがとにかくいろんなところで語られ尽くされ、「もうM-V-Whateverの話はいいよ」となっているように思われる昨今ですが、M-V-Whateverをめぐる議論は混乱のさなかにあるまま「トレンド」から外れ、忘れ去られて行っているように感じます。このトークでは、Presentation Domain Separationの基本に立ち返った上でM-V-Whateverを再度確認し、その結果として当然でてくる次の疑問「ではModel自体のアーキテクチャはどう考えたらいいの?」に切り込んで行こうと思います。

概念的なトークだけではなく、私が今まで実際に作ってきたプロダクトの経験をもとに具体的な話を交えてM-V-WhateverとModelについてお話しできればと思います。

想定聴講者

「M-V-Whateverの話ってなんかいろんなひとがいろんなこと言ってるしRuby on RailsのActiveRecordとM-V-Whateverの話がうまく繋がってこないしそのあたりの議論がなんかぐちゃぐちゃしててわけわかんないんだけど‥‥」という感じのひと

登壇者

しんぺい a.k.a. 猫型蓄音機(丸山)
GitHubアカウント

登壇日

7/3(日)

ScalaでつくるInvalid stateのない世界

トーク概要

アプリケーションを作るとき、避けては通れないバリデーション。

バリデーションに関しては一家言ある方も多いと思いますが、私が実際にScalaで開発している中で得た「こういう感じでバリデーションというものを捉えれば良さそう」という知見や、「で、実際にこんな風にしてみました」というのを紹介します。

想定聴講者

非破壊オブジェクトを善しとする環境(Scalaなど)でのバリデーションに興味がある方

登壇者

takkkun(近藤)
Twitterアカウント

登壇日

7/2(土)

最後に

まだまだ一般参加の応募も募っております。
ぜひお越し頂き、一緒にYAPCを盛り上げましょう!

Scalaの変位指定をすると、何が嬉しいのか。反変編

こんにちは、ヘルステックチームの近藤です。

今までScalaのコードを読んでて「これってどういうときに使って、何が嬉しいんだろう?」と思ってきたもののひとつに、変位指定があります。言わんとすることは分かるのですが、いつどのときに使えば良いのか自分の中でなかなか落としこめず、分かりそうで分からないというもやもやした感じがずっとありました。特に反変。共変はまあそうだよなあ、と思うのですが、反変は脳が理解を拒む感じです。

しかしですねー、先日ついに分かったんですよ。ということで嬉しさのあまり筆を取りました。

なお反変編と銘打ちましたが、共変編は未定です。

反変についてのおさらい

変位に関してはWikipediaにも載っています。

共変性と反変性 (計算機科学) - Wikipedia

ここで反変(contravariant)は:

狭い型(例:float)から広い型(例:double)へ変換すること

と説明されています。たしかにその通りです。

もう少し具体的にしてみましょう。以下のような継承関係の型があるとします。

// 何かしらの名前を表す型。nameプロパティを持つ
trait Name {
  val name: String
}

// 人物の名前を表す型。givenNameプロパティ、familyNameプロパティと、Nameから継承されたnameプロパティを持つ
trait PersonName extends Name {
  val givenName: String
  val familyName: String
}

この場合、継承関係はName -> PersonNameとなります。難しい話ではないと思います。

そして反変を用いたコードです。反変の指定は[-A]と、マイナスを型パラメーターの先頭に付与します。

class Container[-A]()

これでどうなるかと言うと:

val personNameContainer: Container[PersonName] = new Container[Name]()

この代入ができるようになります。

分かりにくい点とはどこか

(個人的に)反変の理解を妨げているのは:

val personNameContainer: Container[PersonName] = new Container[Name]()

というコードです。よく例として取り上げられていると思いますが、これが出来て何が嬉しいのかよく分からないからです。

これが出来て何が嬉しいのかが分からないと、反変はどういうときに使えば良いのかが分からず、「変位指定って何なんだ……」みたいになります。なってました。

実際に反変を使ったケースを説明

「変位指定って何なんだ……」とは思いつつも、それがないと書けないコードがあることも事実。私も先日そのケースにぶち当たり、試行錯誤してたら出来たコードがあるので、そのときのことを順を追って説明します。

そのときの私は任意の型の値をひとつ受け取り、その値をバリデーションして結果を返すクラスを作ろうとしていました。まずはトレイトを定義します。

trait Validator[A] {
  // 型パラメーターAの値を受け取り、バリデーションした結果、不正ならエラーメッセージをSome(errorMessage)として返す。正常ならNoneを返す
  def validate(value: A): Option[String]
}

そして実際にどうバリデーションを行うかを記述した各バリデーターを実装しました。

object NameCannotBeBlank extends Validator[Name] {
  def validate(value: Name) =
    if (value.name.isEmpty) Some("name cannot be blank") else None
}

case class NameMustBeLessThanOrEqualTo(characters: Int) extends Validator[Name] {
  def validate(value: Name) =
    if (value.name.length > characters) Some(s"name must be less than or equal to $characters characters") else None
}

object FamilyNameMustBeKondo extends Validator[PersonName] {
  def validate(value: PersonName) =
    if (value.familyName != "Kondo") Some("family name must be Kondo") else None
}

次に私はこんなことを考えました。「各バリデーターを組み合わせて大きなバリデーターを組み上げたい」と。つまりバリデーター同士の合成です。以下のように、Validatorトレイトに&&メソッドを追加しました。

trait Validator[A] {
  def validate(value: A): Option[String]

  // 自身と引数に与えられたバリデーターを合成する。評価は短絡的(validator1の結果が不正なら、validator2のバリデーションを実行しない)
  def &&(other: Validator[A]): Validator[A] = {
    val validator1 = this
    val validator2 = other

    new Validator[A] {
      def validate(value: A) =
        validator1.validate(value) orElse validator2.validate(value)
    }
  }
}

これで:

NameCannotBeBlank && NameMustBeLessThanOrEqualTo(10)

とすると、「空っぽではなく、10文字以下の名前であるかを確かめるバリデーター」を作り上げられます。良いですね。

しかし、「空っぽではなく、名字がKondoであるかを確かめるバリデーター」を作りたいと思ったとき、今までのコードではそれが出来ません。なぜならNameCannotBeBlankValidator[Name]であるので、&&メソッドが求める値もValidator[Name]か、それを継承したものでなければいけません。よってValidator[PersonName]を継承したFamilyNameMustBeKondoは与えられないわけです。

これは型パラメーターAが非変のために起こる問題です。ということで変位指定をしましょう。

今回は共変と反変、どちらを指定すれば良いのでしょうか。「空っぽではなく、名字がKondoであるかを確かめるバリデーター」がどの型であれば良いかを考えてみます。

  • Validator[Name]型の場合: 与えられた値をNameとして扱うとfamilyNameが取得できなくてダメな気がする
  • Validator[PersonName]型の場合: familyNameも取得できるし、PersonNameNameを継承しているため、nameも問題なく取得できる

答えが出ました。Validator[Name]Validator[PersonName]&&メソッドで合成したら、Validator[PersonName]が出来上がれば良さそうです。子の型に変換したいわけですから、今回は反変です。以下のようにValidator[A]型を書き換えます。

trait Validator[-A] {
  def validate(value: A): Option[String]

  def &&[B <: A](other: Validator[B]): Validator[B] = new Validator[B] {
    // 省略
  }
}

型パラメーターAに反変を指定しました。そして新たに型パラメーターBを&&メソッドの利用時に取るようにしました。「型パラメーターBは必要なの?型パラメーターAじゃダメなの?」という疑問がありそうですが、結論から言うと型パラメーターAではダメです。型パラメーターAを利用してしまうと、NameCannotBeBlank && FamilyNameMustBeKondoとした場合、与えられたFamilyNameMustBeKondoValidator[PersonName]にはならなさそうと、なんとなく思いませんか?よって型パラメーターAではなく、与えられた値の型に応じなければいけないため、新たに型パラメーターBを取る必要があります。

それと型パラメーターBには上限境界を指定しています。この指定の理由は後ほど説明します。

これでNameCannotBeBlank && FamilyNameMustBeKondoとした場合、Validator[PersonName]なバリデーターが出来るようになりました。

どのように動くのか

『「Validator[PersonName]バリデーターが出来るようになりました」じゃあないんだよ」と言われそうです。実際私も随分投げ遣りだと思います。正直さきほどのコードは「なんかいじくってたら出来ていた」という代物で、「どうしてこれで動くのか」が分かりません。これが変位指定の難しいところかなーと思います。

そこで私はこれで動く理由、原理、理屈をひたすら考えました。そしてついに腹に落ちたのです。

NameCannotBeBlank && FamilyNameMustBeKondoの動き

「分からないときは具体的な例を出せ」が私のスタンスです。よって実際にどう動くのか、具体的な型を当てはめて考えてみました。まずはNameCannotBeBlank && FamilyNameMustBeKondoとしたときです。

この場合、型パラメーターAはNameとなります。NameCannotBeBlankValidator[Name]ですからね。そして型パラメーターBはPersonNameとなります。よって&&メソッドの戻り値はValidator[PersonName]になります。定義ではValidator[B]になっていますからね。上限境界の方はどうでしょうか。型パラメーターB = PersonNameは、型パラメーターA = Nameを継承しているため、こちらも問題ないですね。

あれ、すごく簡単ですね。

FamilyNameMustBeKondo && NameCannotBeBlankの動き

ではレシーバーと引数を入れ替えたケースも考えてみましょう。つまりFamilyNameMustBeKondo && NameCannotBeBlankとする、ということです。&&メソッドは決して交換可能ではありませんが、いずれのケースにせよ、Validator[PersonName]を返す必要があるため、わざと入れ替えた場合も確かめてみます。

この場合、型パラメーターAはPersonNameとなります。そして型パラメーターBはNameとなります。しかし、これでは上限境界に違反しています。そこで反変指定が効いてきます!Validatorの型パラメーターAは反変が指定されているので、型パラメーターB = Nameは、Nameを継承した型のいずれかに変化できるのです。いずれかに変化できるけど、さてどの型に変化すれば良いのでしょうか?B <: Aという上限境界を満たしてNameを継承した型…… PersonNameしかありえないと思いませんか?

つまり、FamilyNameMustBeKondo && NameCannotBeBlankとした場合、NameCannotBeBlankValidator[PersonName]に変化しているのです!その変化を許すための反変指定なのです。代入のときは「何が嬉しいの?」と思いましたが、引数として与えるときに変化する必要があると言われたら「なるほど!」となりました。たしかに変数も仮引数も似たようなものですからね(ちょっと雑な説明)。

そして変化の道筋を作るために上限境界があります。これが上限境界を指定する理由です(と私は理解しています)。

結果FamilyNameMustBeKondo && NameCannotBeBlankValidator[PersonName]同士を組み合わせていることになるので、Validator[PersonName]になって当然です。

最後に

いかがでしたでしょうか?私のように「変位指定分っかんねー」という方がいるかは分かりませんが、もしあなたがこの記事を読む前にはそうであって、今は理解が進んだとあればとっても嬉しいです。この記事が役に立ったとかそういうことではなく、「ねー変位指定ってこういうことなんだよ!すっごいね!」という感情を共有できそうなので。何かが分かるってすごい楽しいことだと思います。この記事を読んでも分からないという方は是非コメントなどいただければと思います。

にしてもこれを得心したときは本当心躍りました。共変については今回触れていませんが、今後同様に得心することもありそうなので、そのときはまた記事にしようと思います。それでは。

非プログラマー向けにプログラムを実行する環境をAmazon ECSで提供したお話

こんにちは。ヘルステックチームの近藤です。

先日、こんなお話を社内の方からいただきました。「月末にこれこれこういう作業をやっているのですが、これを短縮、単純、便利にする方法ってありますか?」と。まあ細かい話はともかくとして、これはいわゆる自動化案件です。プログラマーのおでましです!

しかし、このお話をした方はプログラマーではありません。ですので、「じゃあその作業を自動化したスクリプトを書いたので、ターミナルを立ち上げて、このコマンドを実行してください。あ、更新があったらGitHubからpullしてくださいね」なんて言うのははばかられます。

そこで私はAmazon EC2 Container Srvice(以下ECS)を用いることにしました。今回はECSでどういったものを提供したのかご紹介します。

その前にそもそも何故ECSなのか。以下のような思案がありました。

  • 前提として、提供したい解決法はスクリプトひとつを実行すれば十分なものであった
  • それに対し、ウェブアプリケーションでリッチなUIを提供するのはオーバースペックだし、サーバー費用もかかる
  • かといってローカルの環境で実行するとなると、動作する環境がない可能性がある(処理系が入っていない、ライブラリが入っていない、それらが古くなる、etc...)
  • ならばDockerを用い、仮想環境でそのスクリプトを実行すれば良い
  • しかしOS Xの環境ではDockerのためにVirtualBoxをインストールする必要があり、それなりにディスク容量を喰う
  • ならばコンテナーの環境はECSに委ねよう!

いやーAmazonには頭が上がりませんね。

クラスターを作成し、ECSインスタンスを登録する

ECSを利用するにあたり、ECSとは何なのか。そしてどのような仕組みで動作しているかを先に説明します。

ECSはコンテナー型仮想化ソフトウェアであるDockerを用いて、Dockerイメージからコンテナーを実行するための仕組みです。いわゆるクラウド的に。

それだけ聞けばなるほどと言う感じかもしれませんが、ECS独自の用語がいくつかあります。まずはタスク定義とタスクです。タスク定義とは、どのDockerイメージを用いてコンテナーを実行するか、という定義です。ポートマッピングなどのオプションも設定できます。そしてその定義を元に実行したものがタスク。つまりECSでは実行しているコンテナーをタスクと呼びます。

そしてこのタスクを実行する場所。それがクラスターと呼ばれるものです。ですので、タスクを実行するときは、「どのクラスター上で」「どのタスク定義を元にタスクを実行するか」を選ぶ必要があります。

ということでクラスターの作成から始めていきます。マネジメントコンソールから作成してももちろん良いのですが、クラスターがひとつも無いとウィザードによる諸々の作成が始まってしまうため、クラスターの作成はAWS CLIから行ってみます。

$ aws ecs create-cluster --region us-east-1 --cluster-name reraku-tools

{
   "cluster": {
       "status": "ACTIVE",
       "clusterName": "reraku-tools",
       "registeredContainerInstancesCount": 0,
       "pendingTasksCount": 0,
       "runningTasksCount": 0,
       "activeServicesCount": 0,
       "clusterArn": "arn:aws:ecs:us-east-1:******:cluster/reraku-tools"
   }
}

リージョンはどこでも良いのですが、後述するECRを使えるリージョンが限られており、今回はそのリージョンに合わせたかったので、とりあえずバージニア北部(us-east-1)を選びました。

これでクラスターは作られましたが、これだけではタスクを実行できません。実際にタスクを実行するためのEC2インスタンスが必要になります。それをECSインスタンスと呼び、そのインスタンスにはAmazon ECS Container Agentをインストールする必要があります。

ECSインスタンスのためのAMIもあるのですが、Amazon ECS Container Agent自体がDockerイメージになっているため、そのAMIを用いなくとも導入は比較的簡単です。詳しくはaws/amazon-ecs-agentをご参照ください。

なおAmazon ECS Container Agentはデフォルトだとdefaultという名前のクラスターに登録しに行こうとするようなので、/etc/ecs/ecs.config で以下のように設定しておいた方が良いでしょう。

# /etc/ecs/ecs.config
ECS_CLUSTER=reraku-tools

登録先のクラスターが正しく設定されており、Amazon ECS Container Agentがきちんと動作していれば、マネジメントコンソールで対象のクラスターのRegistered Container Instancesが1になっているはずです。

f:id:k0nd0:20160504190927p:plain

Dockerイメージを作成し、Amazon EC2 Container Registryに配置する

クラスターの準備が整ったら次はタスク定義…… と行きたいところですが、タスク定義はどのDockerイメージを使用するかを設定する必要があります。ですので、先にDockerイメージを作成し、ECSがアクセスできるところに配置します。

ECSがアクセスできればどこでも良いとは思いますが、ECSにはAmazon EC2 Container Registry(以下ECR)というおあつらえ向きのものがあるので、そちらを利用します。

ひとつ注意です。クラスターの作成の項でも言いましたが、すべてのリージョンでECRが使えるわけではありません。マネジメントコンソールのECSのメニューにRepositoriesがあればECRが使えます。

それではDockerイメージを格納するためのリポジトリを作成します。マネジメントコンソールから適当な名前で作成してください。すると下記のような画面が表示されます(一部の箇所は伏せています)。

f:id:k0nd0:20160504204812p:plain

あとは記載の通りの手順を手元で行えば、Dockerfileを元にDockerイメージを作成し、さきほど作成したリポジトリにDockerイメージが格納されるはずです。なおDockerfileの詳細は割愛します。

タスクを定義する

タスクを実行するためのクラスターおよびECSインスタンスを作成しました。タスクの元となるDockerイメージも配置しました。これであとはタスクを定義してあげれば実行のための準備が整います。

タスク定義はマネジメントコンソールのTask Definitionsから作成できます。さまざまな設定項目がありますが、Container nameおよびImageが必須なのでそちらを埋めます。Container nameは適当(今回は "resolve-the-problem" とします)、Imageは "******.dkr.ecr.us-east-1.amazonaws.com/resolve-the-problem:latest" のように入力します。あとはDockerイメージに合わせてWorking directoryやEntry pointを入力します。

なお、タスクの実行時にファイルの出力が伴う場合も当然あると思います。その場合、そのままだとコンテナーの中にだけそのファイルが出力され、取り出すことが出来なくなってしまうので、ECSインスタンスとコンテナーのファイルシステムを対応付けておく必要があります。

まずVolumesからAdd volumeをクリックし、NameおよびSource Pathを入力します。Source PathはECSインスタンス側のパスとなります。

f:id:k0nd0:20160504211818p:plain

Addボタンをクリックし、ボリュームを作成します。その上でコンテナーの設定のSTORAGE AND LOGGINGにあるMount pointsを設定します。入力するパスはコンテナー側のパスとなります。

f:id:k0nd0:20160504212213p:plain

これでコンテナーの /root/app/result ディレクトリへ書き出されたファイルはECSインスタンスの /home/ec2-user/resolve-the-problem/result ディレクトリから得られるようになります。

タスクを実行する

あとはタスクを実行するだけです。タスクの定義までがプログラマーが準備するところで、タスクを実行するところが利用者の方になります。ですので、その方の環境で何を用意すれば良いかも併せて説明します。

まずはAWS CLIです。結局CLI使うのかよ、という感じですが、マネジメントコンソールで操作してもらうのも難しい話ですし、ECSのタスクを実行するスクリプトだけ書き、それに実行権限を与え、ダブルクリックして使ってね、という方がよっぽどシンプルかなと思います。

ですので:

  1. IAMで利用者の方のユーザーを作成する
  2. AccessKeyIdおよびSecretAccessKeyを取得する
  3. AWS CLIをインストールする
  4. $ aws configure でAccessKeyIdおよびSecretAccessKeyを設定する

これでECSのタスクを実行することが出来るはずです。あとは:

$ aws ecs run-task --cluster reraku-tools --task-definition resolve-the-problem

を実行するだけで、resolve-the-problemタスク定義を元にしてreraku-toolsクラスターでタスクを実行します。あとはこれを:

#!/bin/bash
aws ecs run-task --cluster reraku-tools --task-definition resolve-the-problem

としてファイルに保存し、実行権限を付けておけば、手軽にタスクを実行できるようになりました。

タスクの実行結果の取り出し方あれこれ

これでめでたし、と言いたいところですが、さきほどまでで述べたのはタスクを実行するところまでです。まだタスクを実行した結果を取り出せていません。

今回私はコンテナーのボリュームを用い、出力されたファイルをECSインスタンスからも取り出せるようにしましたが、これでは利用者の方に「このアイコンをダブルクリックしたらプログラムが動くので、しばらく経ったらこのソフトウェア(SCPクライアント)を立ち上げてファイルが出力されているか確認してみてください」みたいなことを言うしかありません。というか実際そう言いました。実はECSを用いると決めるよりも前にスクリプトを書いていたため、出力がローカルファイルシステム前提になっており、それが足を引っ張った感じです。

結局利用者の方には上記のような説明をしましたが、後でいろいろと考えてみました。とりあえず思い浮かんだものは、「タスクの実行結果はメールで知らせる」というものです。例えば:

  • タスクが終わった時点でメールが送られ、いちいち利用者から結果を確認する必要がない
  • 実行結果の成否をメールに記載できる。失敗時はログを記載したメールをプログラマーに送れる
  • 出力されたファイルはメールに直接添付する。ファイル容量が大きければS3へアップロードし、ダウンロード用のURLを記載するなりで対応

ということが出来ます。スクリプトの結果がメール前提になってしまいますが、悪くはないかなと思います。

最後に

ひとまずECSを用いることによって、「じゃあその作業を自動化したスクリプトを書いたので、ターミナルを立ち上げて、このコマンドを実行してください。あ、更新があったらGitHubからpullしてくださいね」と説明するところが、「このアイコンをダブルクリックしたらプログラムが動くので、しばらく経ったらこのソフトウェア(SCPクライアント)を立ち上げてファイルが出力されているか確認してみてください」にはなりました。いつかは「このアイコンをダブルクリックしたらプログラムが動くので、そのうちメールが届いて結果を知らせてくれます」にしたいですね。

いかなる環境下でも統一された環境を提供できる仮想環境と、その仮想環境をホスティングできるサービスによって今回は実現し、その手順を紹介したわけですが、「他にもこういう方法があるよ」などがあれば是非教えていただければと思います。それでは。