DMM.comの、一番深くておもしろいトコロ。

DMM動画サービスの問題を解決しようとしている話(コンポーネント編)

DMM動画サービスの問題を解決しようとしている話(コンポーネント編)

はじめに

みなさん、こんにちわ。動画配信事業部でサービスの開発効率改善チームの小谷野です。

今回も長期運用中のサービス「DMM動画」の改修に着手した話「フロントエンドエンジニアの取り組み」をお話ししたいと思います(前回のデザイナーの取り組み=「DMM動画サービスの問題を解決しようとしている話/スタイルガイド編」を読んでいると一層楽しめますので未読の方は是非、ご一読ください)。 今回は特に、長期運用中のサービスのフロントエンド改修をしなければいけないものの、どのようにアプローチしていけば良いのか悩んでいる方の参考になればと思って書きました。

というわけで、この記事では、

をお送りします。


フロントエンド側の問題と方針

デザイン側のUIインベントリが始まってある程度形が見えてきたところで、次はフロントエンド側の問題点を洗い出し、どのように取り組んでいくか決めました。

フロントエンド側の問題(20年の長期運用の結果、蓄積されたもの)

  • 使用しているのかわからない、詳細度が高いセレクタだらけとなったstyleファイル
  • 保守/使用されているのかわからないjavascriptファイル
  • 同じデザインや機能なのに実装が別々(複製/改変が繰り返された)になったパーツ類

などなど

これらの問題を解決しようとすると、実態を把握するための調査だけでものすごく時間がかかります。 また、1からサービスを作り直すにしても規模の大きさから同様に時間がかかってしまいます。

それならば、既存のものはそのままにして、まずは小さなブロック単位で新しく作ったものに置き換えながら、いずれすべてを置き換えることを目指していこうと決めました。

UIインベントリでパーツ単位でデザインが作られているので、それに合わせて作りやすいですし、何より対応範囲が小さいので、仕様・実装確認(調査)→作成(実装)→置き換え(リリース) の流れを短い期間で実行できます。

これは、ちょうどアジャイル開発をしていた自分たちのチームにマッチしており、開発しやすいメリットがありました。ちなみに、システムの運用年数が長くコードが複雑化しているため、影響範囲や仕様把握の調査に1スプリント使うこともあります。

f:id:koyano-satoshi:20180611155117p:plain

というわけで以下の方針と目的で進めることになりました。

  • デザインパーツと同じくコンポーネントとして管理する(資産化) ※工数削減
  • 動作している場所はそのままにする(修正による不具合を防止する)
  • 小さい範囲(UIインベントリのパーツ単位など)で、調査→実装→リリースを繰り返して書き換えていく

規模感の話

長年運用しているだけあって、バックエンドを含めての大きな変更には、多大な労力が必要に思えました。 そこで今回の改善では、フロントエンジニアとデザイナーで完結できる規模感で収まるように範囲を決めました。

結果、javascriptのフレームワークメインでいけないか検討することにしました。


技術選定の話

上記で決めた内容をもとに、実現するためにどの技術を使用するか、自分たちのチームでは以下を基準に選定しました。

候補に上がった技術

選定基準

内容 結果
学習コストが低い Vueが一番だった
ネット上の事例数 React Vue Angular はたくさん
社内の導入事例 Vue と React の事例が多数
Githubのstarの数 Vue React Angular
コンポーネント化+javascript/styleのカプセル化 php以外はできる
既存のシステムに組み込みやすい Vue
将来的にマイクロサービス化したい やり方次第

選定基準で特に重視したものは、学習コストです。 新卒やフロントエンドを触ったことがないエンジニアが、すぐに仕組みを理解できることはとても大事です。 また、DMM動画のデザイナーには、フロントエンドのコーディングもできる人がたくさんいます。新たに導入する技術のせいで、それができなくなってしまうのは非常にもったいないと感じていました。

そこで実際に、jQueryを触れるデザイナーと新卒のサーバサイドエンジニアに、社内事例の多かった二つ、VueとReactのチュートリアルを実施し、その後に簡単なパーツを作成してもらいました。

Vue

  • forやifといった、Javascriptをかじっていると馴染みのある単語が多く親しみやすかった
  • データへのアクセスが簡単で楽しかった
  • サクサク作れる!!

React

  • チュートリアルが難しい
  • 理解が進まなかった
  • JSXが・・・

と感想をもらいました。

同時に開発者の意見を参考にしつつ、

  • デザインに合せたコンポーネントの作りやすさ
  • コンポーネントの資産化
  • 既存のシステムへの組み込みやすさ

からVueを選択しました。


作成環境はstorybook for vue

デザイン側と同じく、フロントエンドでも作成したコンポーネントを管理していく必要があります。

コンポーネントの管理には、コンポーネントを分類でき、実際のコードやパラメータをブラウザ上で編集、レンダリング結果を確認できるStorybook for vueを採用しました。

f:id:koyano-satoshi:20180611155146p:plain

Storybook は、登録したコンポーネントを表示したりテストしたりできるUI開発環境です。 もともと React用に開発されたものですが、現在はVueやAngularなどのいろいろなフレームワークにも対応しています。機能追加も可能でたくさんの種類のAddonが公開されています。

f:id:koyano-satoshi:20180611155137p:plain

動画サービスで導入しているAddon

Addon 説明
@storybook/addon-notes 作成者がコンポーネントの説明などを記載する
@storybook/addon-knobs/vue コンポーネントの値をstorybook上から動的にプレビューできる
storybook-addon-vue-info コンポーネントのcodeやプレビュー、propsなどの情報を表示する

また、storybookを静的ファイルとして出力できるので、コード管理は Github Enterprise で行い、プルリクエストやmasterへマージしたタイミングで静的ビルドを実行、AWSのS3にアップすることで、いつでもブラウザ上で確認できるようにしています。

storybookはコンポーネントのAPIドキュメントやスタイルガイドとして利用していますが、

  • デザイナーは、コンポーネント作成後のデザインやインタラクションの確認
  • フロントエンドエンジニアは、開発環境として利用
  • サーバサイドエンジニアは、コンポーネントに渡すデータの確認

といった感じで、それぞれの目的に合わせて使用しています。


パーツの管理方法

コンポーネントは、AtomicDesign をベースにした粒度とカテゴリで管理しています。

とはいえ、AtomicDesign を厳密に守ろうとすると製作者間の認識の違いで悩むことがよくあります。 そこで、一応以下のような簡単なルールを決め、簡単に移動できるようにすることにしました。

簡単な粒度とルール

粒度 ルール
atoms 他で使い回すパーツの最小単位
molucules atomsや複数の要素で構成されるパーツ
organisms ページに配置した際にメインコンテンツとなるパーツ

フォルダは、src/components/粒度/カテゴリ/コンポーネント名 で管理 カテゴリはUIインベントリで出したパーツリストのカテゴリに合わせています。

f:id:koyano-satoshi:20180611155141p:plain

例: ページャーの現在情報を表示するパーツの場合 atoms/pager/pagerInfo/

粒度を移動する場合はフォルダを移動して、stories.jsのstory名を変更するだけ


カスタムコンポーネント作成のルール

新規でコンポーネントを作る際は、 分類・粒度・カテゴリ・コンポーネント名 と順番に入力することで、下記のファイル類をテンプレートから自動生成するスクリプトを導入してあります。

f:id:koyano-satoshi:20180611155127p:plain

フォルダの中身に以下ファイルが自動生成される

f:id:koyano-satoshi:20180611155114p:plain

ファイル 説明
.stories.js storybookのstories設定 + addon Knob の設定を記載する
.vue カスタムコンポーネント本体
.spec.js jestによるunitテストファイル ※近々導入予定
readme.md コンポーネントに関する説明ファイル

ドキュメント作成からテストまでをコンポーネント製作者が責任持って作れるようにしたかったので

  1. readme.md に、何をするためのパーツなのかなどの説明
  2. *.stories.js に、knobで操作するパラーメータ類を設定する
  3. *.vue に、実際のコンポーネントの作成
  4. *.spec.js に、テストの記載 ※vue-cliで導入できるjestを使用

という流れをルール化しました。


ビルドの話

build時に src/components/ の中身を捜査して、作成されているコンポーネントすべてをimportで読み込むVueのプラグインファイル(Components.js)を出力するようにしています。

Components.js

//作成したカスタムコンポーネントを読み込む
import FormDropmenu from './components/atoms/forms/formDropmenu/FormDropmenu.vue'
import ButtonBase from './components/atoms/buttons/buttonBase/ButtonBase.vue'

const components = [
  FormDropmenu,
  ButtonBase,
]

const install = function (Vue, opts = {}) {
  components.map(component => {
    Vue.component(component.name, component)
  })
}

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

export default {
  version: '20180517123658',
  install,
  ...components
}

出力した Components.js は src/main.js 内で Vue.use()を使って読み込みます。 コンポーネント全体で使うような common.scss もこのファイルで読み込んでいます。

main.js

import style from './assets/sass/common.scss'
import Components from './Components.js'

Vue.use(style)
Vue.use(Components)

Vue.config.productionTip = false

new Vue({
  el: '#app'
})

ビルドしたファイルの使い方

ビルドしたファイルは、使用するページで読み込みます。現時点では、カスタムコンポーネントをまとめたパーツライブラリ的な使い方をしています。

<head>
  <link href="./static/css/app.css" rel="stylesheet">
</head>
<div id="app">

    <!-- #appで囲まれた中なら作ったカスタムコンポーネントが使い放題 -->
    <pagination-pagerinfo :item="120" :max="3" :page="2"></pagination-pagerinfo>
    <other-reviewrate :review-point="10" :review-total="0"></other-reviewrate>

    <!-- 通常DOMと合わせて使える -->
    <div>
        ボタン
        <button-addtobasket @add-to-basket-callback="function(){}" floor="videoa" pid="1star00866dl" button-size="M" :is-in-basket="false"></button-addtobasket>
    </div>

    <!-- PHPの中にも埋め込める -->
    <?php for($i=1;$i<3;$i++){ ?>
        <button-update text="更新<?php echo $i;?>" icon-position="left" size="M" @click="function(){}"></button-update>
    <?php } ?>


</div>
<script type="text/javascript" src="app.js"></script>

f:id:koyano-satoshi:20180611155132p:plain

id="app"で囲まれた要素内であれば、自由にカスタムコンポーネントを使えますし、phpのようにサーバサイドで動的にhtmlを生成する際に使用することもできます。既存システムへも非常に組み込みやすかったです。

当たり前ですが、ビルドしたjs/cssファイルは、ページに読み込んでおけば、どこでもカスタムコンポーネントが使用できます。カスタムコンポーネントを修正した際にも一斉に更新できます。この仕組みが今回一番欲しかったものです。


SEOの話

Vueでパーツ管理する場合、考えなければいけない問題の一つに検索エンジンへの対策があります。

クローラのレンダリングエンジン = 最新ブラウザと同じ挙動 ではない

クローラのレンダリングエンジンは、chrome41(2015年ごろ) がベースになっています。 Rendering on Google Search

Chrome41のリリース時期のVueはv1.0。2018年5月の時点でVueはv2.5ですので、かなり開きがあります。そのままの実装では正常にレンダリングされない可能性が高いと考えられます。

※ 実際にクローラーのレンダリング画面とエラーは、[Fetch as Google]を使うことで確認することができますが、DOM構造などの詳細は見れません。 Search Console

f:id:koyano-satoshi:20180611155122p:plain

実際にテスト(既存ページの一部をカスタムコンポーネント化、新規ページをカスタムコンポーネントで作成など)を行ってみたところ、

  • 既存の検索ランキングが落ちてしまう。
  • Vueのカスタムコンポーネント内のリンクが拾われない

などの問題がありました。

結論として、サービス全体にVueのカスタムコンポーネントを実装する場合は、クローラへの対策として、バックエンド側でブラウザレンダリングを再現するSSRなどの仕組みを導入する必要がありそうです。

ただ、最近クローラのレンダリングエンジンに変化があったようで、順位が下がっていたページが復活したり、インデックスされたりするなどの傾向が見られました。他にも発表がありましたので、もしかすると、サーバーサイドレンダリングなどの対策なしでも正常にインデックスされるようになるかもと期待しています(※OGPタグはまだまだ未対応)。


まとめ

今回、フロントエンド側の改修を進めたことで、デザインガイドラインができ、パーツ管理の仕組みができ上がり、開発効率が上がる仕組みが整ってきました。

コンポーネント化のメリットは

  • 流用による制作スピードの短縮/工数削減
  • カプセル化されることによるメンテナンスの容易さ
  • コンポーネント化の過程で生まれる副産物

この三点です。

コンポーネントを作成してしまえば他のページで同じ機能のブロックの置き換えも容易にできますし、Nuxt.jsなどのフレームワークと組み合わせることもできるので、別サービスのモック作成などへの流用もできます。 でき上がったコンポーネントはサービスの資産として残っていきます。 ※将来的にWebComponentへコンバートする際にも容易にできると考えています。

カプセル化されているので、コンポーネント自体のstyleやjsが既存のサービスや他のコンポーネントへ影響を与えませんし、作り方を工夫することで、既存サービスからのStyleやJavascriptの影響を防ぐことができます。既存のサービスを動かしながら改修を進めていくのに最適です!

また、コンポーネント化を進めるうえで、嬉しい副産物もたくさん生まれます。

  • 機能不足やUXの不満の抽出
  • 複雑化したシステムの実態把握や最適化 a などなど

特にシステムの最適化は工数削減に繋がり、これまで難しかったUX周りの改善を行う時間を生み出します。 長年運用しているシステムの改善には、簡単な機能でさえ一筋縄ではいかないことが多々ありますが、UIの整備、コンポーネント化を足がかりに、できるところから進めていきたいと思います。 便利で使いやすいDMM動画へと、より早く改修していきたいと思いますので今後もどうかよろしくお願いいたします。

採用情報

現在、動画配信事業部では、フロントエンドエンジニアを募集しております! 興味のある方はぜひ下記募集ページをご確認下さい! dmm-corp.com

www.wantedly.com