こんにちは。Eightでエンジニアをしている藤野です。最近、電動の燻製機を買ったのですが、思った以上に煙が出るので普段使いできそうになくタンスの肥やしになりそうなのが悩みです。
さて今回は、Web版Eightをリニューアルした話と、リリースにあたって障害懸念を減らすために行ったアプローチについて話します。
Web版Eightリニューアル!
EightにはWeb版とモバイルのアプリ版がありますが、今まではアプリ版を中心にリニューアルが進められていきました。そのためWeb版のUIに関しては、検索の操作性や機能性のアップデートが遅れ気味でした。
ですが今回、数年越しにWeb版のアップデートを行えました🎉
今回のリニューアルでは特に検索の操作性向上に力を入れています。トップページをフィード画面から連絡先画面に刷新し、ログインしてすぐに検索をすることが可能になりました。 また新たに名刺交換日での絞り込みが可能になったり、任意の条件で検索した名刺の情報に一括でタグを付与できるようになったりと、アプリ版では手が届きにくいWeb版ならではの操作性を実現しました。1
リリースに向けての動き
先の画像の通り、画面として大幅なアップデートであるためリリース前の計画は綿密に行いました。 今回はその中でも重要だったものをいくつか紹介します。
Feature Toggle
大規模なリリースにおいて、課題となるのがブランチ戦略です。特にUI部分が変わるような変更の場合、間違ってリリースされてはいけないのでmainブランチにマージするかを判断する必要が出てきます。 Web版Eightの開発チームではブランチ戦略にgit flowを採用しているのですが、複数チームが並行して開発するとなった場合には大きなfeatureブランチからブランチを切っていくことになります。そうなるとブランチ間の依存関係などを考慮しながら開発を進める必要があるため、非常に厄介です。 開発生産性を高めるためにはこれらを考える必要がないようにするのが重要でした。
そこで打ち手としてFeature Toggleを実装しました。Feature Toggleとは、任意の環境・任意のユーザにおいてのみ特定のフラグ(状態)を持たせるように制御するものです。ただし、注意が必要で機密性の高いリリースには適さないということです。なぜならJavaScript側でUIを切り替える戦略は、コードから内容が読み取れてしまう可能性があるからです。しかし、今回の刷新においては機能の整理が主目的であり、機密性はそれほど重要ではありませんでした。そのため、フロントエンドでUIを出し分けるという選択を取りました。
次は実際にFeature Toggleを利用する例を示します。Feature Toggleは以下のようなymlファイルで管理する方針にしました。
available_features: user: test_feature: public: true # 全体公開 web_renewal: person_ids: - 8 # tanaka - 33 # sato
これらは環境別に分けられているので、開発サーバではFeature ToggleをONにするといった出し分けが可能です。
Feature Toggleを利用したReactのコードイメージは以下の通りです。
const useAvailableFeature = (featureKey: AvailableFeatureKey) => { // ログインしているユーザに対して公開されている機能がavailableFeaturesに入る // e.g. loginUser.availableFeatures => ['webRenewal'] return loginUser.availableFeatures.include(featureKey); }; // Feature ToggleがONになっているかを判定するhooks const isAvailableFeature = useAvailableFeature('webRenewal');
このようにFeature Toggleで表示を判定できるようになったので、煩雑なブランチ戦略を考える必要がなくなりました。
CSSでの画面切り替え
今回のWeb版の刷新では、主にトップページの検索画面に関してのみ手を加えており、他の画面に関してはヘッダーや背景色をのぞいて既存を踏襲したものになっています。 そのため既存のコンポーネントを再利用して刷新を進めていったのですが、Feature Toggleを使ってコンポーネントを出し分けていく方針にはデメリットがありました。それは、単に出し分けるだけではファイル数やコード量が増えていく一方だということです。
出しわけのコードイメージは以下の通りです。
const Profile = () => { const isAvailableFeature = useAvailableFeature('webRenewal'); return isAvailableFeature ? <NewComponent /> : <OldComponent />; }
置き換えること自体には問題はないのですが、これでは刷新後に旧コンポーネントを削除するためのリファクタリングに大きな工数を割くことが目に見えています。そこで今回、body要素に対してFeature ToggleがONになっている場合にのみ特別なクラス名を付与することでCSS経由でUIを変更する方法でアプローチしました。
まずはマウント時にコンポーネントの最上位でbodyタグに任意のクラス名を付与します
useEffect(() => { if (isAvailableWebRenewal) { document.body.classList.add('web-renewal-layout'); } }, [isAvailableWebRenewal]); // => <body className='web-renewal-layout'>
これによりbodyにはweb-renewal-layoutというレイアウトが当たるようになりました。次にこのクラス名を用いて既存のコンポーネントのスタイルを変更しやすいようにするmixinを用意します。
@mixin web-renewal() { .web-renewal-layout { @content; } }
このmixinはCSSの詳細度を利用することで既存のスタイルを上書きするためのものです。利用例を示すために、以下のようなprofileコンポーネントのCSSファイルがあったとします。
.component-profile { &__container { padding: 16px; } &__name { font-size: 14px; } }
今回はcontainerのpaddingを16pxから24pxにしたいと仮定します。この時mixinを用いると以下のような書き方ができます。
.component-profile { &__container { padding: 16px; } &__name { font-size: 14px; } } @include web-renewal { .component-profile { &__container { padding: 24px; } &__name { font-size: 14px; } } }
このように、同一のファイル内で既存と同名のクラス名をweb-renewalで括ったスタイルを用意します。これにより、CSSの詳細度でbodyにweb-renewalがついている場合にはpadding: 24pxが優先されることになり、Feature ToggleがONになっているユーザにのみスタイルを当てられるようになりました。
もちろん今あるスタイルをそのままコピーすることになるのでコード量は増えるというデメリットはありますが、一時的です。以下にコンポーネントを切り替える方法とのメリデメを比較した表を用意しました。
コンポーネントでの切り替え | スタイル内での切り替え | |
---|---|---|
メリット | ファイルを柔軟に切り分けられる | 既存のスタイルファイル内のみで完結するのでリファクタが容易 |
デメリット | 旧コンポーネントと関連するファイルを削除するのに時間がかかる | 一時的だがスタイルファイルが肥大化する |
上記を鑑みた結果、スタイル内での切り替えを採用しました。
Bookmarkletでの本番確認
Feature Toggleで任意の人物に対して機能を本番でも公開できるようになりましたが、万が一ymlの設定ファイルで誤ったユーザIDを書いてしまった場合には取り返しがつきません。特に今回のような大規模な刷新の場合においてはその万が一が起きると重大なインシデントにつながる恐れがあるため、二重に対策を講じました。方法としては、先ほどのFeature Toggleのフラグを見るhooksに加えて、localStorageで任意のキーで値が入っている場合にのみ表示する対応をとりました。これにより、間違ったIDが公開されてしまった場合にも通常では表示されることがなくなりました。
const useAvailableWebRenewal = () => { const isAvailableFeature = useAvailableFeature('webRenewal'); const isAvailableLocalStorage = window.localStorage.getItem(WEB_RENEWAL_KEY); return isAvailableFeature && isAvailableLocalStorage; }
ただこれには問題点がありました。それはエンジニアではないPdMやビジネスサイドも本番で進捗を確認したいケースです。非エンジニアにlocalStorageをいじってもらうのは難易度が高く、方法が必要でした。それを解決するために今回Bookmarkletに着目しました。Bookmarkletとは、ユーザのブラウザのブックマークから任意のJavaScriptのコードを実行できるようにする方法です。
以下は日付をalertで出すBookmarkletのサンプルです
javascript:alert(new%20Date())%3Bvoid(0);
このテキストをブラウザのブックマークのURL部分に貼り付けることで、任意のJavaScriptを実行できます。今回は具体的な実装などは省きますが、これを使うことで非エンジニアでも簡単に刷新版との切り替えを行うことが可能になりました。
まとめ
今回のような大規模なUI刷新は、私にとって初めてのものだったのでとても良い経験になりました。特に安全な大規模リリースについて向き合えたことは貴重でした。今後もこの経験を活かし、Web版Eightの改善ができるよう日々邁進していきます💪