こんにちは!フロントエンドエンジニアの平奥です!
これは TECHSCORE Advent Calendar 2018 の 19 日目の記事です。
いつもは興味を持っている技術的な記事を書いていましたが、今回は開発に携わっている弊社のプロダクトである Synergy!について書いていきたいと思います。
Synergy! は 2005 年 6 月にリリースされた企業と顧客の「関係構築」を支援するコミュニケーションを網羅した CRM システムとして誕生しました。ですので誕生してからもうすでに 13 年もの月日が流れています。Synergy! は SaaS 型のシステムであり、継続的に機能追加や改善を繰り返し行い、常にお客様に価値あるものを提供するように努めてまいりました。
しかし基盤となる部分はどんどんメンテナンスコストがあがり、 EOL などの問題も抱えながらこのままでは破綻してしまう状態になりかねないと考え、抜本的な改革を行っておりこれからも改善を予定しております。
過去に行った対応の内容は以下の記事を見ていただければと思います。
ここで書かれている記事は全体的な設計思想(アーキテクチャー)やチーム運営、またバックエンド側に焦点を置いたものとなっています。ですので今回は私の方で Synergy! のフロントエンドの軌跡と、これからの展望について書いてみようと思い立った次第です。
フロントエンドの軌跡
■ フロントエンド領域とバックエンド領域の誕生
2015 年 12 月 Synergy! に新機能である 「リターゲティングメール機能」 が搭載されました。この機能は今までの機能とアーキテクチャが異なり、画面側は AngularJS を使ってシングルページアプリケーション( SPA )で実装しました。バックエンド側とは REST API を使って連携し、フロントエンドとバックエンドを切り離すという、マイクロサービスの考え方に基づいたアーキテクチャになっています。(なお、従来の機能の画面実装は JSP を使っていました。)
今までは JSP で実装していましたので、フロントエンド、バックエンドという概念がありませんでしたが、この新機能を実装するにあたり Synergy! ではこの概念が導入されました。またさらに次の新機能である、「アプリプッシュ通知機能」に関しても同じく AngularJS で実装され、それ以降の機能も同じ構成で実装されました。
これによりフロントエンドとバックエンドの開発が分離され、並列で開発できることにより開発効率は大幅にアップしました。
■ AngularJS の終焉と Angular の台頭
「リターゲティングメール機能」、「アプリプッシュ通知機能」のリリースが無事終わり、さらに新機能の企画がありそれらをすべて AngularJS で実装していました。そんな中 AngularJS の新しいバージョンがリリースされるという噂が流れました。それは AngularJS2 や Angular2 へと名称を変え、最終的にはAngular という名称になりました。
Angular は当初は AngularJS と互換性のあるものだと言われていましたが、実際にはほぼ互換性のないもので、AngularJS と Angular を動作させるにはそれぞれのフレームワークが動作するような環境(ハイブリッド環境)を作成する必要がありました。
我々はこのまま AngularJS のみで実装していくことも可能でしたが、既存の機能の拡張や新機能の実装などが待ち構えていました。AngularJS にはいずれ EOL が来ることもわかっていましたし、その状況で AngularJS で実装するのは負債を作ることになるということも理解していました。また今まで作ったディレクティブを一気に Angular に置き換える時間や人的リソースもない状況であり、最終的にハイブリッド環境を作成し、今後作る機能は Angular で実装するという選択を取ることにしました。
■ 現在のフロントエンド
過去の経験と現在のフロントエンドを取り巻く環境を鑑みて、一つの JS フレームワークに絞って実装していくといつか使えなくなり、リプレイスなどの作業が発生したりするデメリットの方が多いと考えています。JS フレームワークの栄枯盛衰のスパンは非常に短く、またプロジェクトの規模などにより、それに最もあったフレームワークを選定することが賢明だと考えています。
現在機能ごとに依存関係をなくしていく作業を行っています。依存関係がある状態では、万が一フレームワークを入れ替えたいと思っても、いろいろなしがらみがあり一筋縄では行きません。依存関係のない機能ではそのようなしがらみがなく、実装を変更しようとするときも検討する時間が大幅に短縮されるので、すごく小回りの効く構成になっています。既存のある機能では、機能間のフレームワークの依存関係をなくしたことによって、ベースとなる画面を Angular で動作させ、 関連する画面(モーダルのようなもの)を React で動作させることもできるようになりました。これも依存関係のないシンプルな実装に変えた恩恵です。
また依然存在するモノリシック機能に対しても、少しずつモダンなアプリケーションにリプレイスを行っています。この時も機能を適切な単位で切り出し、その切り出した機能の規模、特性によってフレームワークの選定を行い適用していこうと考えています。
また新たに機能を作成する場合も同じく、その機能の特性によってそれに最もあったフレームワークを選定できるようになりました。
ここで現在 Synergy! のフロントエンドで使用しているフレームワークやライブラリを紹介したいと思います。
- AngularJS
- Angular
- React
- TypeScript
- styled-components
- MobX
AngularJS は新規機能を開発する当時、一番有力な JS フレームワークであったため採用しました。それから Angular が出てきてからは AngularJS を Angular にリプレイスしたり、さらに新規機能を実装するときには Angular で実装しています。また新たに React も採用し始めました。上記の「現在のフロントエンド」の章でも書いたとおり、複数の JS フレームワークを採用できる環境になっていますので、今後も他の JS フレームワークも使っていこうと考えています。
TypeScript は Angular と相性がよかったのと、JS の動的型付けによる不具合が軽減されるので採用しました。 styled-components と Mobx は React で採用しています。他のプロダクトで使用したものでもあり、実績もあったので採用しました。 styled-components はデザイナーが直接プロダクトの CSS を変更することが少ないことと、JS っぽい書き方ができるので採用しています。Mobx は State 管理はしたかったものの Redux でガチガチに管理するまでもなかったので採用しました。
今後も新機能を実装していく予定ですので、その機能にあったフレームワークを検証しています。
次章では上記のような環境を作るための技術やフロントエンドで現在採用している、または今後採用したい技術などに触れていきたいと思います。
現在取り組んでいる活動
■ ユーザビリティ向上の活動
弊社では以前からユーザビリティの向上の目的のため、 HCD (人間中心設計)を取り入れています。それは Synergy! でも例外ではありません。新たに作成する画面やリプレイスする画面に対してユーザビリティテストを実施し、ユーザビリティの向上を進めています。
ただユーザビリティテストの実施にはそれ相応の人的リソース、時間、費用などがかかります。そのためユーザビリティテストは安易に行えないイメージがありました。しかし我々はアジャイル型のユーザビリティテスト、そして DIY テスティングを実施することによりその問題を解決しました。
つまり我々は自分たちでユーザビリティテストを行うことにしました。被験者のリクルーティングからテスト設計、実査、分析・レポートまで行うことにしました。社内の会議室を使い簡易ラボを作り、被験者もテスト対象に最もふさわしそうな社内の人間から選定して行うようにしました。
小さく反復してテストすることによって最小限の時間で最大限の利益を得れるように常日頃心がけながら実施することで知見も貯めることができさらに効率よくテストを行えるようになりました。
今ではユーザビリティテストを行っていない画面をリリースするのは怖いと感じるくらいにまでになっており、ユーザビリティテストを行うことによる効果を実感しています。
我々はユーザビリティ向上のためにいろいろな手法を用いることを検討しています。その一つにマイクロインタラクションがあります。マイクロインタラクションとは HCD (人間中心設計)を行う上でのアプローチ方法の一つです。ユーザのアクションや現在の状態を正確に伝えることを目的としており、文字通り 「とても小さなやりとり」 です。しかしこの小さなやりとりはとても大きなユーザ体験をもたらします。まだ本格的に導入はしていないのですが、積極的に採用していきたいと考えています。
■ Backends For Frontends ( BFF )
Backends For Frontends ( BFF )とは、文字通りフロントエンドのためのバックエンドになります。BFF に関しては弊社ではまだ採用実績がなくこれから進めていこうと考えています。バックエンドサービスの呼び出しが多いインターフェイスによく採用されているものとしては、サーバ側の集約エンドポイント ( API ゲートウェイ) を用意して解決を図っているものがあります。しかしこの解決方法はシステムがスケールしていくとどんどん肥大化していき、この部分だけでモノリシックなサービスができあがってしまいます。
そこでそれを解決するために BFF を使う解決法があります。BFF は特定の UI に専念し、また通常のバックエンドの仕事をするのではなく、あくまでもバックエンドのサーバ側に置いたほうが都合がいい UI の部品を展開するという考え方です。ですので、 BFF にバックエンドに責務のあるロジックを埋め込んではいけません。埋め込んでしまうとそこでまたモノリシックなサービスができあがってしまいます。
BFF のアーキテクチャーを採用することによって得られるメリットとしては、フロントエンドに特化したサービスを用意することで UI を構築しやすくなることとバックエンドのサーバ側の処理でそれぞれフロントエンドとバックエンドとの境界を設けてそれぞれの責務を明確にして、独立して開発を進められるようになるという効果があると考えています。
BFF の採用においては上記の内容を念頭に置きつつスケールしていかないといけません。でないとバックエンド側の責務である処理を BFF にもたせてしまう恐れがあり、 BFF の負荷が上がってしまいます。またバックエンド側で行っている処理も見直しすることができます。BFF がない構成では、UI に関連する処理をバックエンドで実現していましたが、それを見直すことも可能です。一例をあげるとすれば、バックエンド側で管理しているセッションなども BFF で管理するべきではと考えています。
また BFF は UI のためのサーバでありますので、フロントエンドエンジニアが開発、メンテナンスを行うべきであると考えています。
■ マイクロサービスアーキテクチャーとフロントエンド
Synergy! のバックエンドは以前まではサブシステム化されている構成でしたが、ここ数年マイクロサービスアーキテクチャー化してきています。フロントエンドではバックエンドのマイクロサービス化に伴い、どのように変化すれば、バックエンドのマイクロサービス化を最大限活かせるかを検討しています。
バックエンドがマイクロサービス化するということは、 API のコール数が増えてしまう懸念があります。今までは1回の API コールで必要なデータを取得できたものが、サービス分割により、複数回 API をコールしないといけない状況も出てくると思います。その場合は状況に応じて、レスポンスがさほど変わらなければ、 BFF 側で複数回の API を呼び出して、フロントエンド側に返すとか、レスポンスが遅くなる場合はバックエンドのレスポンスに必要なデータを付加して返してもらうなどの対応が必要になってきます。いずれもトレードオフで一番最適な方法を検討する必要があります。
■ マイクロフロントエンド
マイクロサービスアーキテクチャをさらにフロントエンドにまで踏み込んだものとして昨今「マイクロフロントエンド」という考え方が出てきました。
概念や思想などは割愛しますが、もっと詳しい内容を知りたい方はマイクロフロントエンド マイクロサービスのフロントエンドへの応用やTechnology Radarなどを見ていただけいればと思います。
ここでは異なる JS フレームワークで構成されている機能を同じ Web アプリで動作させるというマイクロフロントエンドを実現させるための方法として2つ紹介します。ひとつは iframe を利用する方法。もう一つは Web Components を利用する方法です。
iframe を使う方法
子コンポーネントと親コンポーネントは別の JS フレームワークで動作しています。 子コンポーネントと親コンポーネントは、イベントリスナを使ってデータの受け渡しを行うようにします。以下は子コンポーネントから親コンポーネントへデータを送信するためのソースのサンプルです。
子コンポーネント
1 2 3 4 5 6 7 |
function receiveMessage(event) if (event.data === 'init') { event.source.postMessage( initData, event.origin); } window.addEventListener("message", receiveMessage, false); |
親コンポーネント
1 2 3 4 5 6 7 8 9 |
ngOnInit() { this.bindHandleEvent = this.handleEvent.bind(this); window.addEventListener("message", this.bindHandleEvent, false); } ngOnDestroy() { if (this.bindHandleEvent) { window.removeEventListener("message", this.bindHandleEvent); } } |
この方法は Synergy!でも React と Angular を同時に動作させるときに使っています。
Web Components を使う方法
先にWeb Comoponents について概要だけ説明しておきます。Web Components は W3C で仕様が検討され、正式に HTML5 の仕様として追加されました。まだ対応していないブラウザもあるので使用には注意が必要です。
策定状況などは WEB COMPONENTS CURRENT STATUS で確認できます。
仕様としては主に以下をベースとしています。
Custom Element
Custom Element は新しいタイプの DOM 要素の設計と使用する基盤となります。
Shadow DOM
Shadow DOM の仕様は、Web Components のカプセル化のスタイルとマークアップを使用する方法を定義しています。
ES Modules
ES Modulesの仕様では、標準ベースのモジュール化された方法でJSドキュメントを組み込み、再利用することを定義しています。
HTML Template
HTML Template 要素仕様では、ページ読み込み時には使用されないが、実行時に後でインスタンス化できるマークアップのフラグメントを宣言する方法を定義しています。
詳細は公式サイトを見てもらえばと思います。
簡単に言うと JS フレームワークなしに、Angular や React、Vue でいうコンポーネントが独自のHTMLタグでネイティブの API で作れるということですね。
Web Components を使うと異なる JS フレームワーク間でデータの受け渡しが可能になります。
それでは以下に Web Components を使った マイクロフロントエンドのソースを説明していきたいと思います。
ソースはマイクロフロントエンドで紹介されているものを使います。
■ 親から子へのデータの受け渡し
1 2 3 4 5 6 7 8 |
class BlueBuy extends HTMLElement { constructor() { super(); this.innerHTML = `<button type="button">buy for 66,00 €</button>`; } disconnectedCallback() { ... } } window.customElements.define('blue-buy', BlueBuy); |
上記のソースで Custom Elements の定義をしています。
呼び出す場合は
1 |
<blue-buy></blue-buy> |
で呼び出せます。
受け渡す方法は以下のようにプロパティを書き換えることで可能です。
1 |
document.querySelector('blue-buy').setAttribute('sku', 't_fendt'); |
React のような DOM の変更を検知するフレームワーク上ではこの変更によって、表示が再描画されます。
また Web Components では自前で検知する仕組みも実装できるので、その方法に関しては公式サイトを見てもらえばと思います。
■ 子から親へのデータの受け渡し
子から親へのデータの受け渡しは以下のように実現できます。
子コンポーネントはカートに追加されたタイミングでメッセージを発行します。親コンポーネントはイベントを購読してメッセージを受信したときに内部のデータをリフレッシュしています。
子コンポーネントのソース
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class BlueBuy extends HTMLElement { [...] connectedCallback() { [...] this.render(); this.firstChild.addEventListener('click', this.addToCart); } addToCart() { // maybe talk to an api this.dispatchEvent(new CustomEvent('blue:basket:changed', { bubbles: true, })); } render() { this.innerHTML = `<button type="button">buy</button>`; } disconnectedCallback() { this.firstChild.removeEventListener('click', this.addToCart); } } |
親コンポーネントのソース
1 2 3 4 5 6 7 8 9 10 11 12 |
class BlueBasket extends HTMLElement { connectedCallback() { [...] window.addEventListener('blue:basket:changed', this.refresh); } refresh() { // fetch new data and render it } disconnectedCallback() { window.removeEventListener('blue:basket:changed', this.refresh); } } |
■ スタイルガイド
スタイルガイドとは、昨今の Web サイトの構築において1人でデザインすることはほとんどない状態です。そんな中でサイトを作成するにあたって、それぞれがビジネスの目的や一貫したデザインによって快適なユーザ体験を得られるようなサイトを作り出す必要があります。それぞれのチームメンバーが共通の認識を持つために、スタイルガイドが必要になります。スタイルガイドがあることによって、不必要なコミュニケーションは必要なくなり、デザインがブレた場合にもそれが指標になってそのブレを補正することが可能です。
Synergy! では以前からスタイルガイドは存在しています。ですがあまりメンバーに浸透しておらず、古くもなっており、メンテナンスが必要な状態です。そのため上記のようなスタイルガイドのメリットが活かされていない状態です。スタイルガイドのメンテナンスは急務であり、現在運用できるようにしようと対応中です。
今後の展望
弊社もモノリシックなシステムから、サブシステム化したアーキテクチャー、さらにマイクロサービスアーキテクチャーへと移行を進めていっています。そんな中フロントエンドも、マイクロサービスに適応しやすくするにはどうしたらよいかと試行錯誤しています。またチーム体制も徐々にですが変わっていっています。
有名なコンウェイの法則「組織はアーキテクチャに従う」でもあるように組織とアーキテクチャーの関係は切っても切り離せないものです。確かにチームのような小さなまとまりであればコミュニケーションも効率的に行うことができます。ドメイン駆動設計でもユビキタス言語が及ぶ範囲は境界づけられたコンテキスト内であると言われています。境界づけられたコンテキストとはマイクロサービスの一つのサービスと捉えても差し支えないと考えています。そういう意味ではコンウェイの法則とドメイン駆動設計は相性がいいのかもしれません。
ですのでユビキタス言語が及ぶ範囲でチームを作るのが理想ではないかと考えています。しかし一つのマイクロサービスに一チームを割り当てられるほど人的リソースが潤っているわけでもないので、そのあたりは同じようなマイクロサービスを複数受け持つとは思いますが、このあたりはもう少し検討する必要があるのかと考えています。
チーム体制が徐々に変わっているのは逆コンウェイ戦略に書かれているように、アーキテクチャーが組織を変えているのかもしれません。全体がマイクロサービスアーキテクチャーをやろうと決定したことで、それに伴いチーム編成が変化しているのだと考えています。これは受動的に変わっていっているような感じなので、能動的に変えていけばもっと効率が上がるのではと考えています。
終わりに
いかがでしたでしょうか。当初ブログを書く予定ではなかったので、あまり考えておらずまとまりのない記事になってしまいましたが、いろいろ言及できたので、これはこれでいいかなと感じました。
早いもので 2018 年ももう年の暮れ。皆様も体調に気をつけ良い年末をお過ごしください。それではまた!