DMMのサービスを支える最近のフロントエンド技術
千葉弘太郎氏(以下、千葉):こんばんは。長丁場になりますが、最後までお付き合いください。DMMからは「DMMのサービスを支える最近のフロントエンド技術」ということで、Next.jsを今年の前半でがっつり使っていたので、その技術共有と、このあと話しますが、弊社のカオスな実情にも少し突っ込んで話したいと思います。
私は合同会社DMM.comのプラットフォーム開発部にいる千葉と申します。一応Webエンジニアという括りです。
弊社のWebを見ていただくと、だいたいのサービスに共通ヘッダと言われる、スタイルが同じヘッダを使っているんですが、そういうものを扱っています。あと、いわゆるルートで入ってきた時の、トップページの保守・運用とかを現在はしています。
この業界にはデザイナーで入って、いろいろあってフロントエンドエンジニアをやっています。フロントエンドをやっているんですが、その間にサーバサイドの言語を触ったりとか、去年はインフラとかネットワークもがっつり触っていて、今年はAWSも触ってるみたいな感じです。
個人でも受託でWebを作っていたりしたので、上流から、コンセプトから仕事をするのが好きです。最近は関数型言語がちょっと気になっているので、Haskellとかをこっそり勉強しています。
本題に入りますが、2本立てでいきます。
まず、1個目が「最近のDMMのフロントエンド事情」。もう1個が「Next.jsを実践利用してみた」というところのご紹介をしたいと思います。
最近のDMMのフロントエンド事情
1個目ですが、最近のDMMについてです。もともとフロントエンド開発部というものがあったんですが、今年の頭にその組織が解体されまして、各サービスをやってる事業部さんにエンジニアが配置となりました。
アジャイルの1つでスクラムがありますが、けっこうスクラムを取り入れている事業部が多くて、その中の一員になります。なので、事業部によってはフロントエンドというよりは、私もそうですが、Webエンジニアという括りでやっているところが多いです。
事業部によって、触る技術やエンジニアの持ち回る守備範囲がぜんぜん違うので、そこがおもしろいなと個人的には思っています。
社内にはオンプレがまだありますが、クラウドの利用も増えたため、サーバサイドやネットワーク、インフラの設計に携わるフロントエンドも増えました。なので、今はいい意味でミックスされている感じです。
(スライドを指して)こんな図になっています。
もともとデザイン部の中にフロントエンド開発部があって、デザイン寄りの部署だったんですが、それが解体されて各事業部でがんばってねみたいなかたちです。
実際にDMMを支えているサービスなんですが、先ほども話したようにオンプレなどの古き良き時代のものと、NextでしたりNuxtでしたり、わりと最新の技術が両立しているハイブリッドなものが多いです。
いいところをたくさん話してもいいんですが、弊社は古き良き時代のもので運用されていることが多くて、私が今触っているものとしては、GulpとSCSS、CompassでいまだにCSS管理をしたりとか、フロントのモックはEJSで作るんだけど、最終的な埋め込みはPHPとか、そういうことが多いです。
あと、基幹サービスはほとんどjQuery依存しています。しかもjQuery1.8とかで、レガシーの対応もしてるのでそんなもんです。すごくはないです。
ヘッダやトップページのすごい量の……すごい量がどのくらいかと言うと、パーシャル600行あるファイルが30ほどあって、それが1つのCSSに集まっています。例えば、今の総合トップとかのスタイルをそのCSSでハンドリングしてたりします。
最新は事業部によって個性が出ているというか、事業部によって扱っているサービスなどが、コンシューマー向けなのか、BtoBなのかとかで変わってくるので、そこは各事業部の裁量に任せていることが多いです。統計はぜんぜん取っていませんが、パッと見た感じ、Vue.jsの方がReactより多いです。
古き良き時代のものに関してもう少し話します。弊社はPVも多いので、月間数十億のPVに影響するUIの変更は容易に行えないんです。事業部の決済とかもありますので、ハンドリングをエンジニアとかデザイナーだけで行うのが、なかなか容易じゃない時代が続いていました。
しかし、最近の動きとして「古きを捨てて新しきにつく」という思想が社内に広まっているので、どんどん刷新していこうというモチベーションは高い状態です。
直近ですと、弊社のCTOに松本という者が新しくグノシーから来まして、そこも主体になって、「オンプレを捨ててクラウドでやっていこう」とか、「SREチームを立ち上げてGitOpsにしていこう」とか、そういう動きもすごく盛んです。なので、ここ数年でもっとDMMは変わるかもしれません。
DMMの技術スタック
では、「最新の技術は何を使ってるの?」というところですが、これもけっこうカオスです。どこの事業部かは言えないんですが、(スライドを指して)仲良いエンジニアに聞いたらこんな感じで使っていました。
Nuxtが多いです。あとはHeadless CMSを使っていたり、static generatorを使っているところもありますが、例えばGoogleアプリケーションエンジンに、Nuxtをそのまま乗っけて運用しているものもあります。
Vue.jsを使って、サービスページのUIコンポーネント化を試みている事業部もあります。このへんは「DMM inside」の記事にもなっています。最近だと、Angularを使ってフロントのリプレイスをしようという動きも出ています。
弊社のフロント寄りのエンジニアが、やっていることをいっぱい書いてるので、もし気になるところがあれば「DMM inside」を見ていただければと思います。
(スライドの記事を指して)Vueとかはこの『スタイルガイド編』と『コンポーネント編』で書かれています。あとは『Nuxt×Firebase』とかで、下2つはReact関連です。
今いろんな技術が使われているんですが、課題もあります。「風呂敷を広げたままでいいのか」みたいな話もけっこう社内であって、そこらへんをどうしていくかというのは、DMMのフロントの今後の課題かなと思っております。
Next.jsの実践と利用
ということで、弊社の最近の実情はここらへんにします。次に、私がほぼ1人でやったんですが、「社内向けの案件でNext.jsを実践利用してみた」というところを話していきたいと思います。
「なんでNextにしたの?」というところですが、今のチームの前の仕事で、社内業務用の管理画面を作ってほしいと言われまして、「どういうことですか?」と聞いたら、「先にサーバサイドで管理対象のAPIをもう作ってます」という話でした。
API自体がGoとAWSのECS、これはFargateが東京リージョンに来る前で、オハイオとバージニアでまだ試験運用されたばっかりのを使って、イケイケなのを作っている感じでした。
社内の管理画面ですが、認証とかフォーム画面で、オペレーションミスがないように作り込むことが必要だったのと、完成のデッドラインがすでに決まっている案件でした。「またかよ」と思ってたんですが、とりあえず社内向けだし、実験的にどこまでできるかなということで、この技術選定にしてみました。
Nextを選んだ理由は、DMMで事業導入事例が1個もなかったからです。
NuxtとかVue、Reactも単発ではいろいろなプロダクトに入っていたんですが、Next全体でなにかを作ろうという動きはなかったので、やってみようかなと思いました。
あと、個人的にNextは触っていたので、もっと深いところまで見極めたいなと思っていました。このあともしゃべるんですが、Next自体に、サーバサイドレンダリングやStyled-jsxなど、デフォルトで付いているものがけっこう多くて開発が楽です。
Nextを開発しているSaaSなどを使えば、単体でもアプリケーションを簡単に作れるところが、2、3年前くらいから非常に魅力的だと思っていました。
Nextの1番いいところは、Reactを書くことに集中できるということです。例えば、Webpackの設定だったり、バンドラー、その他諸々の設定とかを飛ばして、いきなりReactから書き始めることができて、そこは非常に魅力かなと思います。とにかく楽なので、Nuxtがすごく流行ってますけど、Nextもみなさんいかが? というプレゼンです。
Next.jsの3つの特徴
Nextの特徴を挙げるとキリがないんですが、3つくらいおもしろいところがあります。1つ目はgetInitialPropsというReactにはない機能を持っています。
これは何かと言うと、サーバサイドレンダリングだけではないんですが、主にサーバサイドレンダリング時にAjaxとかFetch APIで外部と通信できる優れものです。なので、componentDidmountとかでAPI通信をする必要はないです。
2つ目はファイルシステムを利用したURL設計ができるところです。例えばwww.dmm.com/event/hoge/……みたいな感じだと、こっちの開発のディレクトリにpagesというディレクトリと、配下に/event/hogeというディレクトリを作ってあげて、index.jsって書くと、その通りにURL設計ができて、かなり画期的かなと思います。
ただ/user/:idとか、クエリパラメータに付くやつをURLに含めるということは、実はNext単体だとできないので、そこは工夫をしました。
3つ目はLinkコンポーネントです。の代わりなんですけど、Stateと別にNextはURLの状態を別途管理してくれるので、例えばReact Routerとか、扱いが難しいものもありますけど、いちいちそういうものについてURLのState管理をしなくてもいいです。
(スライドを切り替えて)これはgetInitialPropsの例ですが、実際にこんな感じでgetInitialPropsをstaticで書くんですが、このコメントアウトしているところでいろいろできます。
Koa.jsを選んだ理由
次に「Koa.jsをなんで選んだか?」というところです。
まず1つがさっき言ったRoutingの話です。/user/:idみたいなことができないんですが、Node.jsのWebサーバなんでもいいんですが、Node.jsのCustom RoutingをNext.jsのCustom Routingにマッピングしてあげることで解消できます。
つまり、/:idで来たやつを、?id=userIdみたいな形のクエリパラメータに変換して、Nextに渡してあげることができるようになります。
あと、個人的な所感になってしまうんですが、Session管理とか認証はStateで管理するよりアプリケーションの外側、サーバ側で制御したいなと思っていました。
Koa.jsは私自身、去年WAFのログ集計アプリを作った時に、Koa.jsで作ったので使いやすかったというところがありました。
みなさんExpressとか使ってる方も多いかもしれませんが、Expressよりいい意味で機能落ちしていて、必要なものを自分で足していく感じですので、目的に叶った実装が自分でできるかなと思います。みんな使ってくれませんが、Koaを推してます。
AWSを採用する利点
最後にAWSについてですが、こちらもgolangの方でAPI開発が進んでいたので、どうしても合わせざるを得なかったという背景がありますが、いろいろ利点はあります。
ECSはDockerでアプリケーションを動かしているので、ローカルでDocker開発をして、そのままデプロイすれば、ほぼ開発環境が変わらずAWS側とローカル側で差異を減らせます。
ALBからECS経由でコンテナのバランシングができるというのは、もともと知っていたので、ひょっとするとローカルでやったものがデプロイできて、さらにバランシングも簡単にできてしまうのではないかと思って、ちょっと試したいなという感じで入れてみました。
(スライドを指して)イメージとしてはこんな感じです。
DockerのコンテナにNodeのイメージでKoaがあって、Nextがその上に乗っかって、そのままコミットしてCircle CI経由でECS/Fargateにデプロイって感じです。
今日はこっちの話はあまりしません。ECSのコンテナ管理はTerraformとかも使って毎回デプロイするんですが、毎回状態を一緒にするようにやってました。
(スライドを指して)参考程度にですが、ネットワーク構成図としてはこんな感じです。
CloudFrontから、ALBで、APIとその中の必要なコンテナみたいな感じでした。
今回採用した技術
設計するにあたって、主に使ったのがこの4つです。
Recompose、Redux、あとRedux-formを今回使いました。Styled-jsxはNextに入っていますね。結果的になにがいいかって、全部JSで書けるということです。CSSファイルとかHTMLファイルは一切いらない。全部JSで書く。もちろんBabelで書きました。
「なんで選んだのか?」というところで、Recomposeは、もともとクラスで書く文化がJSになかったと思うんですが、個人的にもいちいちclass extends React.Componentと書くのは面倒くさかったので、楽に書ける、直感的にDOMとかを書けるものはないかと思って探していたところ、こいつがいました。
実際にFunctional-Stateless-Componentで、setStateとかを無理やり呼び出さないようにも設計できるので非常にいいなと思って使いました。Redux/Redux-sagaについてですが、API呼び出しが絶対に発生しますので、どこで副作用を吸収するかという時に、いろいろ探してsagaにしました。
あとはRedux-formです。これはかなり良いので、みなさんにもぜひ使ってほしいと思います。Styled-jsxは、とりあえずJSでCSSも書けるから楽です。
実際にRecomposeには、pure、withHandlersなどのAPIが沢山あるんですが、例えばwithHandlersだと、あるコンポーネントがあって、編集画面みたいなのがあって、新規登録時と編集時でまったく同じ画面を使いたい時や、Event Handlerの中身だけ変えたい時に、withHandlersを使って、最終的にコンポーネントに渡ってくるpropsの関数だけを変えることができます。そういう切り離しの作業が容易にできるので有用でした。
Redux-formを使ってみて
ちょっと寄り道になりますが、Redux-formを使ってみて、かなり良かったところがいくつかあります。
フォームの実装や状態管理って面倒臭いと思うんですが、Redux-formのStateで管理してくれるので、そもそも自分のStateで入力情報とかを管理する必要がないんです。
実際にsubmmitする時だけ、Redux-form側のStateから入力情報を取ってきて、APIにぶん投げることができるので非常に使い勝手がいいです。
バリデーションが各フォーム要素に個別でできるので、ここもすごく魅力的です。今回はファイルアップロードがあったので、React-dropzoneを使ったんですが、他のライブラリもRedux-formの機能の一部として取り入れることが可能なので、ぜひフォーム画面を作るのが大変だと思っている人がいたら試してみてください。
(スライドを指して)これは実装例ですが、上の青い四角の中で、Redux側はRedux-formのreducerをマウントするだけです。
例えばコンポーネントのコンテナ層があった場合、myFormStateみたいなかたちでやると、フォーム画面ではその名前でState管理をしてくれるという優れものです。
もう1個はけっこう当たり前のことなんですけど、ディレクトリに機能制約を設けました。
あと、Redux-sagaにビジネスロジックを集中しました。ActionとReducerにはなにもさせずに、ただの純粋関数として扱うような縛りを設けたり、Plant UMLを使用して、処理を一旦書き出してから実装に入りました。
アーキテクチャを解説
実際に今回やったアーキテクチャはこんな感じです。
Action名は上記の感じです。
実際に起こったことを命名して、値の操作をしないとか、sagaのタスクを起動するとか、Reducerの更新のためのActionを渡すだけという縛りにしました。Reducerは単純にActionから来たタイプによって、どのStateを更新するかという仕事しかさせてないです。値の保守も一切しないです。
それをやると、sagaのタスクにすべてのビジネスロジックが集中するかたちになりますが、さらにその中でも制約を設けて、Actionで必ずタスクを開始するとか、非同期処理はcall APIを使うとか……実際使わざるを得ないんですが。
あとはタスクの終わりです。ActionでStateを管理するのか、NextのRoutingを使うか、どっちかしかやらないようにすると、だいぶすっきりした感じになります。
いきなりこれを書き始めるのもなんなので、これはバックエンドの人とコミュニケーションを取るためにやってみた施策なんですが、Plant UMLでこのシーケンス図を、実際にviewで起こっている状態とタスクとReducerとStateが、どのように時系列で変わってくるのかというのを一旦かまして、設計した上で実装に入りました。
これは「DMM inside」に詳しく書いてあるので、ぜひ興味があれば読んでください。(スライドを指して)全体的にこんな感じになってます。
実装してみて
実装してみてですが、ビルド等の手間がない分、Reactの設計や実装に注力できたのは、Nextの作りがいいからかなと思います。
ReduxのMiddlewareにビジネスロジックを集中させちゃうのは、ある意味ありだと思います。その理由としては、副作用のスコープを極限まで絞ると、意外と作りがシンプルになるという感じです。
UMLは書くのおすすめなので、ぜひやってみてはどうかと思います。あと、Sessionとかの管理もKoa.js側で受け持って、Stateで無理しないというのも使えます。
最後に、上手くいかなかった例として、ECSのバランシングがどうしても上手くいきませんでした。
Next.jsだと、pingでアプリケーションの状態を常に管理しているんですが、どうしてもバランシングで、そのpingの行き先が切れてしまって、スティッキーヘッダとかを有効にしてもダメだったので、ここは改善の余地というか、もう少し深く突っ込んでいきたいと思います。
時間の関係で駆け足になってしまい、すみませんでした。ご静聴ありがとうございました。
(会場拍手)