Tech Do | メディアドゥの技術ブログ 

株式会社メディアドゥのエンジニアによるブログです。

電子書籍流通システム「DB4」の フロントエンドについて(Next.js、SWR) 〜アーキテクチャ編〜

2022年6月1日に開催したエンジニア向け勉強会「Next.js(SWR・Web3認証)勉強会」。多くの方にご参加いただいたのですが、残念ながら都合が合わなかったり、後でイベントを知ったという方もいらっしゃるのではないでしょうか。

そうした方々向けに、テキスト起こし版を作成しました。スライド資料と合わせてご覧ください。

DB4のフロントエンドにおける使用技術やアーキテクチャ、実践しているテスト手法等をご紹介します。本記事では特に「DB4のアーキテクチャ」に関して取り上げています。

登壇者紹介

二俣 雅紀

新卒入社2年目。主にDB4のフロントエンドの開発を担当。MediaDoでバリューに沿った行動をとったエンジニアに贈られるBest Engineering Values賞を受賞。小旅行が好き。


現在の流通システムと、刷新するDB4について

まず電子書籍流通システムについて紹介します。日本には2,200以上の出版社と、150以上の電子書店が存在します。そうした中で、出版社はなるべく多くの電子書店でコンテンツを配信したいと考えています。しかし、電子書店に応じてコンテンツのデータを変換したり、売り上げを管理するのは大変です。その労力によって、出版社の本業であるコンテンツ制作に力が割けなくなってしまいます。これは電子書店側にも同じことが言えます。

そこで出版社と電子書店双方が本業に集中できるように、仲介を行う仕組みが作られました。それが電子書籍の取り次ぎです。MediaDoはこの取り次ぎを担っており、これを電子書籍流通システムと呼んでいます。電子書籍流通システムは様々なシステムと連携し合う機能を持っており、複雑なものになっています。

現在稼働している電子書籍流通システムはeBookは、オンプレで10年以上稼働しています。eBookはPHPで書かれたモノリシックなシステムであり、テストコードもありません。長年の開発による部分最適化や、改修の積み重ねによって複雑化しています。MediaDoではeBookの改修を続けるだけでは根本的な事業課題の解決は困難であると判断し、現在DB4として刷新を行っています。

この刷新は、大きく分けてリホストとマイグレーションの2つのフェーズに分かれています。リホスト、つまりオンプレからAWSへの移行については既に完了しています。現在はPHPからGoとReactへの置き換え、および新機能を追加するマイグレーションのフェーズとなっています。

DB4のシステム全体像について

DB4システムはAWS上で動いており、HTMLやJavaScriptといったフロントエンドで使用するファイルはAmazon S3に置いています。それをCloud Frontで配信しています。

プライベートAPIサーバーに関しては、Amazon ECS上で動作しています。フロントエンドとAPIサーバーはJSONを使ってやりとりしています。DB4のフロントエンドでは、TypeScriptを採用しています。フレームワークとしてはNext.js、UIライブラリはMaterial UIを使っています。API周りでは後半で紹介するSWRを使っています。

DB4のコンポーネント設計について

現在、DB4のフロントエンドチームは3つあります。チームによってコンポーネント設計の方針が若干異なりますので、今回は私が所属しているAチームのコンポーネント設計方針について紹介します。

まずディレクトリ構成についてです。src配下にpages・components・services・repositoriesの4つのディレクトリを切っています。内容は次の通りです。

  • pages
    • 各機能毎のルートコンポーネントを配置
  • components
    • 共通で使用するコンポーネントを格納しているcoreディレクトリを配置
    • 内部でさらに各機能毎にディレクトリを分けて、pages・hooks・apiディレクトリを配置
  • services
    • 共通で使用するHooksなどを配置
  • repositories
    • APIとのやりとりを行う処理を配置

依存関係について

pages配下のルートコンポーネントからはコントローラーコンポーネントを呼んでいます。このコントローラーコンポーネントは、画面遷移を制御するコンポーネントです。pagesとcomponentsで処理が分散されてしまうのは良くないため、pages配下のルートコンポーネントには処理を書かずに、ただコントローラーコンポーネントを呼ぶだけとしています。

コントローラーコンポーネントからは、各機能毎のコンポーネントが呼ばれています。

ロジック周りはHooksとして管理しています。APIサーバーとやりとりする処理についてはapiディレクトリにまとめているため、Hooksからapiディレクトリ内の関数を経由して、repositoriesからAPIサーバーとやりとりしています。

Hooksから直接repositoriesを呼んでいないのは、repositoriesに依存する処理(複数のAPIを使って1つのオブジェクトを作成する、リクエストパラメーターを作成するなど)をHooksから切り離すためです。このように切り離すことでコードの可読性が上がり、APIサーバーのインタフェースが変更された時でも修正範囲が限定されるので、コードの保守性が上がると考えました。

DB4のフロントエンドのテストについて

単体テストはJestを使っており、汎用的な関数やカスタムフックをテストするコードを書いています。なお、カスタムフックについてはこれから実装を進める予定です。この単体テストはテーブル駆動ライクなテストとなっています。E2Eテストについては、手動で行っています。一部の機能についてはCypressを使って試験的に書いていたのですが、メンテナンスコストが高いということで採用しないことになりました。先月(2022年5月)QAチームが発足しましたので、今後はこのQAチームが自動テストのノーコードツールを導入する予定です。

次に、これから実装を考えているカスタムフックのテーブル駆動ライクなテストを紹介します。例としてuseInputという、入力値を管理するカスタムフックがあるとします。このuseInputの中ではvalueとerrorというステートを持っていて、onChange関数でステートを更新できます。このuseInput、つまりカスタムフックでテストしたいことは、返値のメソッドを実行して内部ステートが想定通りに更新されているかどうかだと考えました。

そこで、上記図の右下にあるようなテストテーブルを作成します。これを基に、Jestを使ってテストをしようと考えています。このテストテーブルですが、describeにはテストの概要、hookPropsにはこのカスタムフックに渡す引数、テストテーブルにはactionとexpectedを持った配列を渡しています。ここで、このactionにはカスタムフックの返り値の内、メソッドを実行する関数(今回の場合ではonChangeを実行する関数)を渡しています。

expectedには、このactionが実行された後に想定されるvalueとerrorの値を渡しています。このようなテストテーブルを使うことによって、この返り値を実行した後の内部ステートをテストできると考えています。

DB4のフロントエンドの課題

現状の課題を2点ほど紹介します。まず1つ目は、プロジェクトとしてスタイリングを実現する手段が定まっていないことです。プロジェクト初期は、Material UI ver.4を使っていました。その時はMaterial UIが用意しているmakeStylesでスタイリングを統一していました。プロジェクトの途中でver.5にアップデートしたのですが、ver.5からmakeStylesが非推奨となってしまいました。そのため、現在は変わらずmakeStylesを使っている部分、emotionを使っている部分、さらにver.5で追加されたsxを利用している部分があるなど、プロジェクトとして統一されていません。

2つ目の課題として、Storybookのようなコンポーネントカタログがないことです。新しくジョインしてきたメンバーが共通コンポーネントを見つけづらかったり、使い方が分からずに手間取ってしまうという課題があります。