トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

マイクロサービス・リアーキテクト

こんにちは、トレタ VPoEの北川です。

先月にモバイルオーダー「トレタO/X」の新機能として、NECモバイルPOS連携をリリースしました。今回の記事ではその時の開発を振り返り、開発の裏側について紹介しようと思います。

toreta.in

外部POSとの連携

モバイルオーダーと切っても切れない関係にあるのが「POSレジ」です。POSレジとはいわゆる会計のレジのことです。

トレタO/Xではモバイルオーダー上から会計ができるオンライン決済と、内製のモバイルPOSであるO/X-POSでの対面決済が行えるようになっています。 とはいえ、多くの飲食店には既にPOSレジが入っているケースが大半で、キャッシュドロワー付きの筐体を導入している場合は多額の初期費用をかけているため、原価償却もありPOSレジを入れ替えるという判断はとても難しいです。

そのためトレタO/Xの導入を検討していただいている飲食店様からは、既に利用しているPOSレジと連携してくれないか、という声をたくさんいただいておりました。 そこで外部POSとの連携開発を行い、第1弾としてNECモバイルPOSとの連携のリリースに至ったわけです。

データ連携のアプローチ

POSレジではお会計をするものなので、伝票データを持っています。伝票データは請求金額だけではなくもちろん注文内容も含んでおり、注文を入れるための商品マスター情報をPOSレジは持っています。 つまり、モバイルオーダーとしてPOSレジと連携するということは、データのマスター情報を外部POSレジ側に委ねることを意味します。 そこで、トレタO/XとPOSレジ間での連携のアプローチは2つ考えられます。

1.モバイルオーダーで受けた注文データ等を随時POS側に送る

このアプローチは既存のシステムの構成を保ったまま、連携部分を追加しやすいというメリットがありますが、デメリットはやはり分散トランザクションとなってしまう点です。注文データなのでデータロスやタイムラグは業務要件においてクリティカルな問題となり許容できるものではありません。

2.モバイルオーダー側は注文データ等を保持せず、POSレジ側のAPIにに注文を送る

1のアプローチが非機能要件的に難しかったため、今回はこちらの方式を取りました。店舗が利用しているPOSが内製のOX-POSか、外部POSかで注文の送り先を切り分ます。

マイクロサービスの差し替え

2のアプローチをとると、トレタO/Xのアーキテクチャは以下の図のようになります。緑の部分がもともとのトレタO/Xのマイクロサービス群です。内製POSを使う場合と外部POSを使う場合で利用するマイクロサービス群は大きく分かれます。 共通で利用するのはモバイルオーダーに表示するためのメニュー管理システムと、管理システム用のアカウントの認証・認可システムです。 そして、外部POSを利用する場合は注文システムやオンライン決済システムなど今までコアなデータを扱っていたシステム群は切り離されます。

これほどダイナミックなマイクロサービスの繋ぎ合わせはマイクロサービスで当初設計した時には想定していませんでしたが、実際にこれが行えたのはマイクロサービスである利点かと思っています。ドメインごとに疎結合にしていたおかげで、必要なシステムだけを取捨選択することができています。

実際に、外部POS連携を企画してからこの形に設計し、実装は約3ヶ月で行うことができました。修正箇所がほぼフロントエンド側だけで済んだ点が大きいです。

ここでとつぜんPRですが、フロントエンド内でいかに分岐してそれぞれのパターンと整合性をとったか、という話は来週行われるTORETA TECH UPDATE のイベントでテックリードを務めた奥野さんから詳細に語っていただきます!

toreta.connpass.com

この先の発展

今回はNECモバイルPOS連携の初回リリースを目指したので、もちろん行き届いていない課題点はいくつかあります。 特に商品マスターがトレタO/X用のメニュー管理システム内と、外部POSシステムが持っている商品マスターで二重管理となってしまっています。

トレタO/Xのコンセプトは商品の魅力を最大限に表現して注文体験を向上させることを大きな価値としているため、POSレジ側が持っている商品マスターのデータ構造だけでは足りずに多くの付加情報を持たせられる必要があります。そのため商品マスターをどちらかのみにすることは難しいため、それぞれのマスターデータをいかにスムーズに同期させるかが次の課題となっています。

また、外部POS連携第1弾と前段で書いた通り、他の外部POSとも今後積極的に繋げていく予定です。これをやるにはフロントエンド側で切り替えしていくには限界がくるため、POS連携用のシステムが必要になってきます。各POSレジが提供しているAPIやデータ構造をいかに抽象化していくか、こちらも次の大きな挑戦です。

次のプレスリリースが出せたタイミングで、また振り返って開発の内部を紹介をしようと思いますので、乞うご期待ください。

奥野さんと社員のリファクタリング部屋 -API分割とその前に

「奥野さんと社員のリファクタリング部屋」は、リファクタリングに励むトレタの社員と技術顧問の奥野さん ( @okunokentaro ) の間で実際に行われた会話を切り取った開発現場実録コンテンツです。

技術顧問: 奥野さん
三度の飯よりリファクタリングが好き
今回の質問者: 武市さん
トレタ在籍2年。沖縄在住のフロントエンジニア

PR

こちらに登場している2人が登壇するイベント・TORETA TECH UDATEが2024/08/28に開催予定! リファクタリングやリアーキテクトなど、サービスを提供させながらプロダクトの新陳代謝をいかにして行なってきたかを開発現場の実例と合わせて紹介します! toreta.connpass.com


今回の質問💬

現在BFFの実装で、各ページごとは基本的に一つのAPIにリクエストをしています。ただ、このAPI使い回されている場合もあります。

例えば、「商品取得API」というAPIを作成して、それは商品詳細を表示するために使われているページで使われているAPIです。 一方で、商品を編集するページからも「商品取得API」を呼び出しています。 これ自体は問題ないのですが、「商品取得API」というのは商品詳細を表示するページに必要な情報をBFF(API)でまとめて取得している点に違和感を私は感じています。

例えば、「商品取得API」が商品自体の情報を返すという機能としているにも関わらず、付随する情報、例えば価格の情報やタグの情報なども同じAPIで取得しています。 これはそれぞれの情報に対応する専用のAPI、例えば注文取得APIと注文履歴API、2回リクエストしたほうが再利用性が高いのではと思っています。

今後の方針としては、各APIは単一の責務を担当するというルールで定めようと検討しているのですが、奥野さんのご意見をいただきたいです。

誰のためのリファクタリング?

奥野 この話題はずっと議論されている鉄板中の鉄板の話題だと思うんですよ。 一回の通信でどの様にに返すべきか、結局は何を目指したいかによると思います。通信(ハンドシェイク)が増えれば増えるほど、当然レスポンスは遅くなっていきます。

BFFというポジションとして、直接別にデータベースとやり取りをしているわけではなく、別の外部のRESTfulなAPIやGraphQLなどから取得してくるということは、APIを分ければ分けるほどレスポンスは遅くなっていきます。 なので何を指標に置くかによると思っていて、分けることによって段階的な画面のレンダリングができるから、エンドユーザーにとって高速な表示が可能であるなのか、分けることで開発者が管理しやすいなのかによっても変わってきますね。 武市さんがこういうアーキテクチャをとろうという判断に、「誰のために」が欠けている気がします。

誰に対してプロダクトを作っているのかは、プロダクトを使ってくれるお客さんがいてこそだから、お客さんよりも自分たちの開発の利便性を優先するっていう判断は自分は怪しいなと思っています。 エンドユーザーに高速に提供するためにはこうなんだけど、トレードオフがあってやむを得ずここは分けないとツライ、みたいなジレンマはあると思います。そこを考えずに、まず開発のリファクタリングありきであったり、コードが細分化されている方がテストしやすいとか開発しやすい、というのはお客さんの方見てないな、と思ってしまいます。

武市 おっしゃるとおりで、トレタではVercelを使ってるのでキャッシュされるから、そこまでフォーマンス落ちないだろうみたいな、結構安直な考えだったところはありました。

奥野 キャッシュがあったとしても、情報ごとにキャッシュの鮮度というものがあって、頻繁に更新されないって分かってるんだったらキャッシュは有効でもいいけど、頻繁に更新される部分であったら、そもそもキャッシュは有効な手段ではない、となってくる。 例えば、画面上のレンダリングを分割して出したいとか、分割して一部の情報は一回取得すれば十分とか、ここの部分の情報は頻繁に変わるから頻繁にフェッチさせる必要がある、みたいに使いまわせる部分と、都度取得しに行かないといけない部分というのはあります。 モバイルオーダーアプリで言うと、タブとかあまり頻繁に変わらない部分もあるので一度取得したら十分だけど、在庫の情報は頻繁に変わるから都度取得しないと、売り切れなのに画面では販売中になっていたりしたら問題になてしまう。

どういった切り口でもいいですが、その開発はエンドユーザーのためにやってるのか、開発者のために行っているのか、運用コスト改善のためにやっているのか、とにかく指標を定めないまま、考えているような印象を受けたので、そこをしっかりと考えてから話を持ち出したほうがいいと思います。

武市 なるほど。ちょっと自己中的な考えだったことに気がつきました。開発者の目線しか見れていなかったです。

リファクタリングの目的を意識する

奥野 この時間はどうリファクタリングしていくかっという部分で議論してはいるけど、リファクタリングで開発生産性が上がることを目的にしてはいけないんですよ。

開発生産性が上がることが目的になるのではなくて、開発生産性が上がることによってエンドユーザーに届けることができるバグ修正だったり、機能追加が頻繁になることが目的だから、開発生産性を上げようではなくて、開発生産性を上げたことでエンドユーザーに価値を届けようなんですよ。

だからそこをリファクタリングを目的にするとかなり間違っているので、今回はリファクタリングを目的にする以前に、今回はAPIの分割を目的にしてしまっているから、しっかりとゴールを見据えないとなっという危うさは感じました。

武市 よくありがちな、手段が目的になってしまっているやつですね。

奥野 何のためにリファクタリングをしているのか、何のためにAPIをまとめる、あるいは分割するをやろうとしているのか、というその先にあるお客さんに与える価値っていうのを考えるというと思います。 そこを考えた上で分割すべきAPIを検討するだったり、統合して一個のAPIでまとめて返した方がいいのであればそれを検討するといいと思います。

武市 はい、ありがとうございます。視点がずれていたところを気づきをいただき、ありがとうございました!

コラム: REST API・GraphQL・BFFの変遷

今回は質問の答えには辿り着けませんでしたが、あらためてBFFやREST API・GraphQLなどが生まれてきた背景や目的を振り返ってみましょう。

2000年初期のWeb開発ではPHPやRubyOnRailsなど、サーバーからHTMLを返してレンダリングする方法が主流でした。RubyOnRailsではMVCモデルという開発パターンがとられており、ControllerがModelを基にHTMLであるViewを返す仕組みとなっていました。

そこからスマートフォンが流行りだし、アプリ開発が盛んになりました。アプリ開発の場合はアプリとAPIが分離されるため、アプリエンジニアはクライアント開発を行い、サーバーサイドエンジニアはAPI開発を行う分業化がされるようになりました。Webとアプリの両対応するケースも増えたので、Web側もアプリと同様の構成でAPIを呼び出すJavascript製フレームワークが登場しました。APIはマルチクライアントに対応したり外部に公開するケースも増えていったため、特定の画面に依存したAPIではなく汎用的に使えるRESTとしてリソースに準拠したAPI形式になっていきました。

APIがRESTになってくると、今度はクライアント側で必要な項目の足し引きが問題になっていきます。一覧画面では最低限の項目で十分ですが、詳細画面ではすべての項目および関連するリソースも必要となってきます。一つのAPIでそれらを包括して関連するリソースとそのネストしたリソースまで返却するようになると、データ量が多くレスポンス速度も下がってしまいます。一方で各画面に必要な項目だけを返すAPIを画面ごとに作っていくと開発量は増大していきます。そこで現れたのがGraphQLです。呼び出し側がクエリで必要な項目を指定することで最適なレスポンスをサーバー側の改修を必要とせず取得することができるようになりました。

一方、サーバー側はモノリシックな構成からマイクロサービスとしてデータベースやサーバーをドメインごとに分割する流れがでてきました。サーバーが分割されると必要なデータを取得するために複数サーバーへリクエストを必要とする場面がでてきます。そこで、データ取得や画面用にデータ加工をするサーバーとしてBFF(BackendForFrontoend)パターンが登場しました。クライアント側からはビジネスロジックを分離して表示処理に専念できるようになるため、フロントエンドの進歩により複雑化したこともあいまって必要性が高まりました。 BFFを入れることで通信は一回増えますが、HeadlessCMSなど外部APIも豊富になってきたため、クレデンシャル情報を持った処理を行うために中間サーバーを設けることも一般的になりました。

さらに現在の2024年においては、さらにBFFの在り方は変わりReact/Next.jsではSSR(Server-seide Rendering)やRSC(React Server Rendering)などサーバー側でレンダリングを行う流れが活発になっています。これは初期のPHPなどサーバーでHTMLを返していた頃と体系は似ており、技術トレンドが一巡したのかもしれません。

奥野さんと社員のリファクタリング部屋 -ディレクトリの名付け方つづき

「奥野さんと社員のリファクタリング部屋」は、リファクタリングに励むトレタの社員と技術顧問の奥野さん ( @okunokentaro ) の間で実際に行われた会話を切り取った開発現場実録コンテンツです。

技術顧問: 奥野さん
三度の飯よりリファクタリングが好き
今回の質問者: 武市さん
トレタ在籍2年。沖縄在住のフロントエンジニア

PR

こちらに登場している2人が登壇するイベント・TORETA TECH UDATEが2024/08/28に開催予定! リファクタリングやリアーキテクトなど、サービスを提供させながらプロダクトの新陳代謝をいかにして行なってきたかを開発現場の実例と合わせて紹介します! toreta.connpass.com


今回の質問💬

前回に引き続き、Webアプリケーション (Next.js) のプロダクトのリファクタリングを進めている武市さんから、ディレクトリ名についての質問です。

repositories」というディレクトリ名が、特定の意味やイメージが強く付いているため避けるべき、という前回の指摘をうけて別のディレクトリ名を考えてきました。 「repositories」という言葉の代わりに「データアクセスを抽象化し、データベースや外部システムとのやり取りを行う処理」として以下の3つのディレクトリ名を考えましたが、いかがでしょうか?

  • connectors
  • effects
  • interfaces

誤解を生む名前は避ける

奥野 正直なところ、ディレクトリ名をそこまでこだわらなくていいと思っていて、それは前回の「repositories」って名前を避けたらっていうところからも続いている話題ですが、ディレクトリ名は他の人がそれを見てわかればいい、伝わればいいと思っています。 危険な橋を渡って「repositories」って名前をつけることも別に誰も止めないし、みんなが納得してみんなが分かる名前だったら別に何でもいい。

それを踏まえて、一つちょっと注目しておくと、「interfaces」はちょっと危ないかもしれない。 なぜかというと、TypeScriptで開発している前提があるので、TypeScriptのinterface宣言を想起する部分が大きい。どうしても「types」とか「interfaces」みたいな言葉を使うと、Typescriptのinterface宣言をまとめたファイルを配置するディレクトリの印象になってしまう。

例えばReactの場合だったら「components」や「hooks」とか、Reduxだったら「redeuers」とか、ディレクトリ名の中に特定のもの・同種のものを詰めていくというのは、ディレクトリの戦略としてよく見かけるので、「interfaces」という名前をパッと見たらTypeScriptのinterfaceばっかりが入ってるように見えてしまって、データアクセスを行うものが入っているようにはちょっと見えないですね。

なので、どんな名前つけてもいいよっとは言ったものの、誤解を与える名前を避けたほうがよくて、自分なら選ばないですね。 前回の「repositories」と理由は似てますけど、やはりみんなが連想するものが偏りがちな言葉は、それを違う用途として使うのはあまり向いてないと思うし、今回の「interfaces」も多くの人が想起する内容とは違うから誤解されてしまうと思います。

武市 そうですね。おっしゃる通りです。

語彙の引き出しを広げる

奥野 「effect」と「connectors」は、自分がとやかくつっこむほどではないですけど、自分は「connectors」という言葉をここで使う例は、実はあんま見たことないのですが、どこかからの引用ですか?

武市 いえ、このディレクトリに置くファイルは「何をやっているのか」が一つの単語で分かる言葉というのを色々と考えて、「database」や「repositories」みたいな言葉ではなく、ちゃんと意味の伝わる単語を色々探してみました。

奥野 英単語として適切な言葉、ということですね?

武市 そうです。何かとコネクトするものの集合体として、「connectors」を選択しました。それも皆を納得させやすい言葉のほうがいいなと思って、「ここは別のものとコネクトしているものなんです」という自分の中で先に納得させる理由を探してからみたいなところがあったかもしれないです。

奥野 あまり深く考えすぎると使える語彙が減っていくというのがあるから、リポジトリっていう言葉は避けようみたいにして、あれも避けよう、これも避けようってすると、どんどん自分の納得できる言葉が減っていくから、「connectors」でしっくり来るならいいんじゃないとは思います。

一方でJavaでは「Connectuor Architecture」って名前のアクテクチャーがあって、オラクルとかでもそういう「Connectuor Architecture」ってのが紹介されてはいます。それが前例としてあるなら「connectors」って言葉も避けようみたいにしていくと、どんどんと使える言葉が減っていってしまい、最終的に「functions」とか「domeins」とか「processes」とか、あまりにも抽象にもほどがあるだろうっていうぐらいの言葉になり果ててしまいます。

どこかで線は引いてもいいと思うし、「repositories」で線を引くのか、「connectors」で線を引くのかは自由だけど、最終的には誤解を生まなかったらいいと思う。だから、どんな言葉を使っても極端な話をすれば、「effect」って言葉だったり、「functions」だったり、「processes」と呼んでも、誤解を抱く人間は抱くから、うちではこれのことをこう読んでるんですよっていう説明すればいい。

こういう時に本を読んでるっていうのが大きく役立ちます。本だけではなく他社の勉強会のスライドで、うちではこういうふうにやってますとか、アトミックデザインを採用してますとか、クリーンアーキテクチャーをやってますとか、ドメイン駆動を設計に取り組んでますとか、いろんな会社のいろんなエンジニアが、いろんなLT、カンファレンスで発表をしてますよね。だから、トレタで奥野さんがどうしてるとかだけじゃなくて、あの会社ではあのようにしてるって発表してたなとか、あっちの会社はトレタとやっているビジネスや作ってるアプリケーションも似てるけどこうしているんだな、ということは探すことができます。

例えば、ここの会社ではJavaを使っているからそういう言葉を使うんだなみたいな知識や経験を積めば積むほど、どういう文化圏から来た言葉だなというのもなんとなくわかってきます。なんかJavaっぽい、Goっぽい、TypeScriptっぽいなとかあるわけですよ。

知識量っていうのはこういうところの取りまとめにすごく役立つので、語彙はたくさん持ったほうがいいし、語彙を集める方法は本だけじゃなくて、SpeakerDeckとかにアップされてるLTでもいいから、とにかく他人のLT、他人の技術ブログ、他人の他社の開発者ブログを読みまくる、というのがおすすめです。

トレタでもこのリファクタリング部屋というコンテンツでブログ公開してるわけですから、読者はトレタさんではこうやってんだって伝わります。全世界にブログとして公開することで、また新たなコミュニケーションとか新たな方法の行き来が生まれていくわけです。だからここで「connectors」を使うっていうことは全くおかしいなことではないと思います。

武市 はい、ありがとうございます!自分なりには、まず名前は何にせよ、シンプルでルールを伝えやすくできたなと思ってます。これをどう保守していくかはまた宿題として考えていこうと思います。

一旦これで進めてみて、またトライアンドエラーで、ダメだった時はまた修正しながらプロジェクトを進めていきたいと思います。

奥野 はい。いいと思います。この言葉ちょっと分かりにくいなとか、いう場面が出るようであれば見直したらいいし、特に誰も気にしてないようであればそのまま使い続けていけばいいので。

武市 はい!ありがとうございます!

© Toreta, Inc.

Powered by Hatena Blog