トレタ開発者ブログ

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

堅牢なフロントエンド開発を支えるAPI型チェックと防衛的プログラミング

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

フロントエンドアプリケーションで社内外のAPIを利用する際には、セキュリティやデータ整合性の問題、スキーマの不整合、予期しない変更への対応といった数々の課題がつきものです。 こうした課題に対応し、信頼性の高いフロントエンドを構築するために、弊社では防衛的プログラミングと厳密な型チェックを活用しています。これにより、APIとの連携におけるリスクを最小限に抑え、開発の効率と品質を両立させています。

本記事では、防衛的プログラミングの考え方、型チェックを厳密に行う理由、実際の開発現場の例を交えてご紹介します。


API利用におけるセキュリティの重要性

まず、JavaScriptにおける潜在的なセキュリティリスクとして、プロトタイプ汚染が挙げられます。この攻撃手法では、悪意のあるデータを用いてオブジェクトのプロトタイプが不正に変更され、アプリケーション全体に予期しない影響を与える可能性があります。

JavaScriptのオブジェクトはプロトタイプを通じてプロパティやメソッドを継承します。この仕組みを利用して、攻撃者はAPIレスポンスのJSONオブジェクトに特殊なキー(例: __proto__)を含め、プロトタイプを改変します。その結果、アプリケーション全体で動作が予期せぬ形に変更される可能性があります。

具体的なリスクとしては、以下が挙げられます:

  • リモートコード実行:悪意のあるコードがシステム内で実行される。
  • データ改ざん:重要な設定やデータが不正に変更される。
  • サービス停止:予期しないプロパティ変更によるエラーでアプリケーションが動作停止する。

このようなリスクを回避する方法の一つとして、APIレスポンスの厳密な型チェックを行う方法があります。詳細については以下の記事をご参照ください:


厳密な型チェックとスキーマ駆動開発

弊社では、フロントエンドとバックエンドの連携において、スキーマ駆動開発を採用しています。この手法では、開発初期にAPIスキーマを定義し、両チームがそのスキーマに基づいて並行して作業を進めます。

しかし、スキーマ通りに実装したつもりでも、繋ぎ込み時に不具合が発生することがあります。例えば、スキーマ変更の共有漏れや実装ミスによる不整合が挙げられます。このような場合、スキーマに「過不足なく」準拠しているかどうかを検証する仕組みが必要です。

そこで、クライアント側では防衛的プログラミングにより厳密な型チェックを行い、不具合発生時の原因特定とスムーズな修正を実現しています。

新しい機能追加の場合

新機能を既存のシステムに追加する際、非破壊的変更であっても注意が必要です。例えば、新しいフィールドやオプションが追加される場合、クライアント側でそれに対応するロジックが正しく実装されていないと、全体の整合性が崩れる可能性があります。

例えば、あるAPIにオプショナルなフィールドを追加したとします。このフィールドは必須ではないため、既存の仕様を大きく損なうことはありません。しかし、特定の機能やユースケースにおいて、このフィールドが適切に設定されない場合、システム全体の整合性が崩れるリスクが発生します。

この場合は互換性の有無に関わらず、サーバーとクライアント間での正確なコミュニケーションと早期の検知体制が重要です。フロントエンド側で厳密な型チェックを行っていると、必須フィールドでなくてもフィールドが追加されたことでエラーが発生します。 これにより、仕様変更や新機能追加時の対応漏れを防ぎ、システム全体の安定性を保つことができます。

外部APIの場合

一方で、外部APIに対しては、内部APIとは異なる柔軟なアプローチが必要です。外部APIは、提供元によって予告なく変更される可能性があり、必ずしも事前に提供されたスキーマ通りのデータが返ってこない場合もあります。

例えば、ある外部APIを利用する際、仕様と異なるデータが返されることが判明した場合、厳密な型チェックを維持することでかえってエラーが頻発し、開発スピードやシステムの安定性に悪影響を及ぼすことがあります。このような場合、柔軟性を優先したロジックを導入することで、APIの変化に適応しやすくしています。

具体的には、レスポンスデータのバリデーションやエラーハンドリングを強化し、予期しない変更にも耐えられる仕組みを構築しています。


型チェックのデメリットと今後の方針

厳密な型チェックは、多くの場面で開発を効率化し、信頼性を向上させる一方で、デメリットも存在します。特に、リソースが限られているプロダクトでは、型チェックの厳密さが負担となる場合があります。

例えば、開発リソースが縮小されたプロダクトでは、型チェックの更新が滞り、APIの後方互換性を保つことが難しくなる場合があります。一方で、活発に開発が行われているプロダクトでは、厳密な型チェックが問題の早期発見や修正を可能にし、信頼性向上に大きく寄与しています。

そのため、弊社ではプロダクトのフェーズや状況に応じて型チェックの適用範囲を調整し、効率と柔軟性のバランスを図っています。


さいごに

弊社の防衛的プログラミングと型チェックのアプローチをご紹介しました。記載した通りプロジェクトの規模やフェーズにおいて合う合わないがありますが、堅牢で安定したサービス提供に向けた開発の参考にしていただければと思います。

監視業務(オンコール)に必要なこと

こんにちは、トレタVPoEの北川です。 今回はトレタの開発組織で行っている監視業務(オンコール対応)について紹介しようと思います。

監視業務(オンコール対応)

トレタでは飲食店向けにサービスを提供しているため、飲食店の営業時間である深夜や土日祝にもシステムが利用されています。そのためトレタの営業時間外でも安定してサービスを提供できるように監視体制をつくり運用しています。

オンコールとは、システム障害が発生した際にオンコールの担当者が対応できるように待機する業務です。当番の担当者は障害発生時に障害状況の確認や各所への連絡を行います。

トレタではPagerDutyというサービスを使い、事前に定めたSLOの値を超えた場合に待機中の担当者に電話が鳴る仕組みとなっています。オンコール対応は当番制で365日11:00-24:00の時間に対応しています。担当者のルールとしては以下が定められています。

  • 飲酒禁止
  • 通信環境確保
  • 機器携行
  • 覚醒状態であること

簡潔に言うと、呼び出されたときに対応ができる状態であること、となります。それ以外に制約はないので、上記を満たしていれば家で動画を見ててもいいし、外に出掛けても問題はありません。

オンコール対応は業務の一部であるため当番にあたった担当者は会社から手当がつきます。 ただし、開発メンバー全員のオンコール対応への参加は必須にはしていません。プライベートな事情もあるので任意参加としています。

開発組織の方針としては、オンコール対応への参加は推奨としています。開発者は自分たちで作ったサービスの運用に責任を持つべきであり、どういったSLOで運用されるかを理解した上でシステム作りをすべきだからです。

逆に言うと、アラートが鳴らないようにシステム側が万全に対応されていれば、オンコール対応は待機するのみで緊急対応することなく手当を貰うことができます。エンジニアにとって「怠惰」は美徳という考えのもと、深夜や休日を心穏やかに過ごすためにシステムを整えておくモチベーションとして、オンコール対応の仕組みが働くのがこの制度のもう一つの目的です。

監視体制の仕組み化

上記の監視業務を行うには、システム側での監視の仕組み化が必要です。

  1. 障害が発生していることをシステムが検知できること
  2. オンコール担当者が状況を手早く確認できること
  3. オンコール担当者が対応を行える、または適切にエスカレーションできること

これらを構築するために必要なのがSLO / ダッシュボード / 対応マニュアルです。このセットは各アプリケーションや各マイクロサービスごとに整備します。

1. SLOを設定する

まずはシステムに異常がないかを検知するために、メトリクスと閾値を定義します。その閾値となるのが「SLO(Service Level Objective)」です。

監視するメトリクス

クライアントアプリケーションかサーバーかによって異なりますが、以下のメトリクスを設定することが多いです。

  • 稼働率:システムが利用可能な状態になっているか
  • 成功率:システムが正常に稼働しているか
  • 応答速度:システムが期待通りの速さでレスポンスできているか

トレタの場合ではこれらのメトリクスをDatadogのSLOの機能を使って設定を行なっています。そしてDatadogのIntegrationを使い、メトリクスの閾値を超えるとPagerDutyへと通知が行われます。

稼働率

稼働率は主に外形監視を使った死活監視(ヘルスチェック)で計測しています。トレタの場合はMackerelというサービスを使い、事前に設定しているヘルスチェックのエンドポイントに対して定期的にリクエストを行い、エラーとなった場合には通知します。

ヘルスチェックには主に2種類のエンドポイントを設けています

  • システム単体として応答するエンドポイント(/_status_check
  • システム単体および依存するシステムの応答までを確認する、いわゆるDeepHealthCheckを行うエンドポイント(/_health)

Mackerelからは/_health のエンドポイントをリクエストし、エンドポイント内では依存する内部システムのエンドポイント(/_status_check)または外部システムのヘルスチェックエンドポイントへとリクエストを行います。

稼働率の閾値としては、基本的にサービス要求に基づいて設定を行いますが、理論値としては依存するシステムの稼働率が限界値となります。 例えば、CloudRunとCloudSQLを利用しているサーバーの場合、CloudRunの稼働率は99.95%以上であり、CloudSQLの稼働率も99.95%以上です。そのためそのサーバーの稼働率の限界は99.95×99.95の99.90%以上となります。さらにそのサーバーが別サービスを利用している場合にはその稼働率をさらに掛け合わせることになります。 これらは理論値なので、加えてシステム上のダウンタイムなどを加味するとさらに下げた値を現実的な閾値として設定します。

※ 2024年11月時点

成功率

サーバーとしてAPIを提供しているのであれば、成功率は以下で定義できます。

成功率 = 正常に応答したリクエスト数 / 全体のリクエスト数

「正常に応答した」とは、レスポンスのHTTPステータスコード2xx系/3xx系/4xx系のどれかであることとします。

成功率を監視することで、アプリケーションのリリースによりバグが埋めこまれたことや、依存するサービスに障害が起きていることを検知しやすくなります。 成功率の閾値をどうするかは、こちらもアプリケーションやサーバーの特性によります。より事業クリティカルな責務のシステムであれば閾値は高く定義する必要があります。

応答速度

サーバーの応答速度であればAPI Gatewayからレスポンスタイムのメトリクスを取得できます。 クライアントアプリケーションであればページ表示時間になります。 ページ表示時間はトレタの場合はWebページをVercelでホスティングすることが多いので、VercelのSpeed Insightsを使い、FirstContentfulPaint(FCP)LargestContentfulPaint(LCP)を取得することができます。

vercel.com

https://vercel.com/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fv1689795055%2Fdocs-assets%2Fstatic%2Fdocs%2Fconcepts%2Fspeed-insights%2Fv2%2Fres-chart-dark.png&w=3840&q=75&dpl=dpl_D3Vd4AcRwGJ3VGKSrKSZDYdQQ5jj

2. Dashboardを作る

ダッシュボードは上記のSLOのメトリクスや、他にもサーバーなどの各種メトリクスを一覧化し、障害が発生した際にどこに異常があるかを見つけやすくするために役立ちます。

ダッシュボードに載せるべきメトリクスというのは一律で定めてはいませんが、サーバーで設定する一般的なメトリクスは主に下記になります。

APIサーバー

  • CPU使用量
  • メモリ使用量

DBサーバー

  • CPU使用量
  • メモリ使用量
  • コネクション数

API Gateway

  • リクエスト数
  • 5xxレスポンス数
  • レイテンシー

3. 対応マニュアル(Runbook)を用意する

「Runbook」と呼ぶことが多いですが、障害発生時にオンコール担当者がどのようにアクションをすればよいかをまとめたドキュメントです。

オンコール担当者がそのアプリケーションやシステムの直接の開発者でない場合でも適切な復旧作業が行えるように、具体的な指示を記載します。

  • 各種アラートの説明
  • 各種アラートがが鳴った時に確認すべきダッシュボードの項目
  • メトリクスに異常がある場合の対応方法

対応方法としてはサーバー台数を増やしたりスペックの変更など、基本的にはGCPやAWSのコンソール上から操作できる内容を手順とあわせて記載します。

それらを行なっても回復しない場合は、最終的には担当開発者へのエスカレーションを行います。エスカレーションの際の連絡先(Slackでのチャンネル、メンバー)もRunbbokに記載が必要です。

さいごに

弊社ではマイクロサービスの構成にした際に、チームが分散されるのと各メンバーの守備範囲が分かれてしまうため、このように体系的な整備を行いました。 チームが拡大した時など、監視体制の検討をはじめた際にぜひ参考にしていただければ幸いです。

フロントエンド開発環境スタートセット2024秋

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

今回は弊社でフロントエンドアプリケーションを新しく構築する際の開発環境として、何のライブラリを入れるかという開発環境初期セットを紹介しようと思います。

Web Framework / CSS Framework / Tesing Framework / Linter / Formatter、それぞれ定番で使うデファクトが大体ありましたが、近年では新しいライブラリも登場したので、2024年現在・最新版を、今回は直近で作られた実際のリポジトリを例にご紹介します。 今回紹介するリポジトリのアプリケーションはtoB向けの管理画面のアプリケーションで、特質した部分も特にない一般的なWebアプリケーションです。

それでは早速、package.jsonの内容はを見ていきましょう。

"dependencies": {
    "next": "14.2.13",
    "react": "18.3.1",
    "react-dom": "18.3.1"
},
"devDependencies": {
    "@biomejs/biome": "1.9.2",
    "@types/node": "20.16.9",
    "@types/react": "18.3.9",
    "@types/react-dom": "18.3.0",
    "@vitejs/plugin-react": "4.3.1",
    "@vitest/coverage-v8": "2.1.1",
    "eslint": "9.11.1",
    "eslint-plugin-import-access": "2.2.2",
    "jsdom": "25.0.1",
    "knip": "5.30.5",
    "postcss": "8.4.47",
    "tailwindcss": "3.4.13",
    "typescript": "5.6.2",
    "typescript-eslint": "8.7.0",
    "vitest": "2.1.1"
},

Web Framework

以前に弊社で利用している技術スタックについて紹介しましたが、フロントエンドのフレームワークにはNext.js / Reactを利用しています。 それぞれのバージョンは最新で、Next.jsはAppRouterの使用を前提としています。

nextjs.org

弊社内の既存のNext.jsを利用しているアプリケーションでも、PagesRouterからAppRouterへの移行は少しずつ進めており、AppRouter化の進め方の詳細は先日の弊社イベントで紹介された技術顧問の奥野さんのスライドにも細かく説明されているので、ぜひご参考にしてください。

speakerdeck.com

CSS Framework

CSSフレームワークについては、以前はEmotionを使うことが多かったのですが、Next.jsのAppRouter化に伴いEmotionではRSC(ReactServerComponent)に対応していないため、TailwindCSS を利用しています。

RSCに対応したCSSフレームワークの選定については、決定打があまりなかったため軽量さやシェア、あとは別で進めているデザインシステムとの親和性を考慮してTailwindCSSを選んでいます。

tailwindcss.com

Lint

LinterやFormatterについては以前はEslintPrettierを主に使っていましたが、最近ではBiome に移行する流れがでてきました。

Biomeの良さは実行速度の速さです。実際にCIで実行しているEslintのリントに比べて実行時間は約1/3になりました。

BiomieはPrettierとほぼ互換があるのと、Lintルールも一通り対応しています。Eslintのプラグインは対応していないため、一部のプラグインを使うためにEslintも残して併用していますが、Eslint単独ですべてのルールをチェックするよりもCIの待ち時間が大幅に短くなるメリットがあります。

biomejs.dev

Test

テストツールも以前はJest を主に使っていましたが、現在ではVitest に移行しています。こちらもBiomeと同様に既存資産からの移行がしやすく、実行速度が格段に早くなっています。

vitest.dev

Utility

こちらは必須ではないですが便利ライブラリとしてよく使用しているのがKnip です。KnipはExportsなどを解析して使われていない不要なファイルを抽出して削除してくれるライブラリです。使われているかわからないようなファイルを見つけ出し安全に削除できるため、リファクタリングに重宝します。他のプロダクトではでデータベースの乗り換えなど大規模なリアーキテクトがあったので、その際の旧実装のデッドコードを全て除去するのにも活躍しました。

knip.dev

さいごに

いかがだったでしょうか。少し前まではPrettier などはどのリポジトリにも入れていましたが、それも新しいライブラリに置き換わりより快適な開発環境に移り変わっています。新規プロダクトを作る以外にも、既存のアプリケーションの見直しにぜひご活用ください。

技術顧問からのひとこと

今回の環境構築において、Prettierがレギュラーから外れたことには驚きがありました。Prettierは、その完成度の高さから、永続的に利用され続けるだろうと思われていたフォーマッタですが、時代の流れを感じます。このように、数年前には当たり前だったライブラリ構成も、年月とともにトレンドが移り変わることがよくあります。

弊社では、ただ流行を追うのではなく、安定性やCIでの処理時間なども考慮して環境構築を行っています。特に、Node.jsで実装されていた処理がRustやGoで開発されたライブラリに移行していく流れには、時代の変化を強く感じます。今後もJavaScript以外のエコシステムがさらに発展していくことに興趣が尽きません。

© Toreta, Inc.

Powered by Hatena Blog