航空券の予約をおこなうWebアプリケーションのバックエンド開発
概要
- 人数: 6人 (マネージャー1人、品質保証(テスト)およびサブマネージャー1人、フロントエンド1人、デザイナー1人、バックエンド1人(自分)、引継ぎ1人)
航空券の検索や予約は GDS(Global Distribution System)と呼ばれるシステムによって行われます。このGDSに対する操作を SOAP(Simple Object Access Protocol) API で提供しているサービス(以下SOAP API)があります。この案件では、クライアントがJSON形式で航空券情報を取得できるように、SOAP APIで取得したデータを変換して返すWebサーバーの開発を行いました。
主な業務内容
- 航空券販売の仕組みの把握
- SOAP API を利用するためのクライアントライブラリの設計・開発
- 取得したデータをデータベースに登録する処理の作成
どのように業務を進めたか
まずは航空券の販売の仕組みを理解する必要があった。SOAP APIのドキュメントには航空券のデータ構造が分かるような図が記載されていない(API仕様くらいしか書いていない)ので、SOAP APIで返ってくるオブジェクトの関係図を作ってそのデータが何を表すデータなのかを推定した。
どうやら、航空券販売では予約と発券が分かれているようだ。特に予約は Booking という言い方をする。座席を販売するというのが航空券販売の考え方のようだ。座席には Booking Code というコードが振られていて、このコードに運賃ルールが紐づいている。席数が少なくなるほど、安い座席が売り切れるから高くなるといった仕組みなのだろう。
さて、作成したオブジェクトの関係図を見ると、どうやら「旅程」、「航空便」、「運賃」、「チケットに書き込まれる情報」といったものを表すデータから構成されているようだ。
「旅程」には「航空便」と「チケットに書き込まれる情報」が入っている。「チケットに書き込まれる情報」には「運賃」や、その後の予約に必要な情報、手荷物などのルールに関する情報が入っている。
SOAP APIには、「検索」、「詳細情報」、「予約」、「発券」などのAPIが用意されている。「検索」APIは経由地や出発時刻などを受け取り、「旅程」の一覧を返すようだ。「詳細情報」APIは「航空便」を受け取り、単一の「旅程」を返すようだ。
さて、作成するWebサーバーの役割は、このSOAP APIが返すデータを今時のAPIのような形式でクライアントに返すことだ。SOAP APIの大まかな仕様を把握したら、次はWebサーバーの作成に取り掛かった。
言語は TypeScript で、 fastify.js というフレームワークを使用することにした。別にTypeScriptである必要はなかったが、フロントエンドの方は Vue.js を使っているようなのでそちらに言語を合わせた。 JavaScript のフレームワークとしては Express.js が有名なようだが、僕は新しいフレームワークの方が好きなので fastify.js を使ってみた。
フロントエンド開発者が別の人になる以上、サーバーのインターフェースは柔軟に変えられるほうがよいだろう。そこで、SOAP APIへアクセスするクライアントモジュール(以下SOAPクライアント)を作り、それをサーバーに組み込むような設計にした。
こうすれば、別のサーバーフレームワークを使いたいときもSOAPクライアントをそのまま流用できるし、APIインターフェースだけSOAPクライアントとは別の形に書き換えることも可能になる。
SOAPクライアントが行う処理は以下のようになる。
- オブジェクトをSOAPで送るXMLの文字列に変換する。
- XMLの文字列をSOAPの仕様に則って送信する。
- 受け取ったXML文字列をオブジェクトに変換する。
SOAPではwsdlというファイルで型を定義する。TypeScriptで書く以上、SOAPクライアントにも型定義が欲しかったので、wsdlファイルの型定義からTypeScriptの型定義を手動で作った。原理的には自動で作れるのだが、丁度よいライブラリがなく、また自分で作るよりは必要な分だけ手動で書いた方が早そうだったのでそうした。
SOAP APIが受け取れる、または返せるすべてのデータに対する変換処理を書くと時間がかかりすぎてしまうため、SOAPクライアントは必要な処理を少しづつ追加していく形で開発を進めた。
たとえば、「運賃」には通貨単位変換前の金額や、関税込みの金額などいくつか含まれているが、表示上必要なのは最終的な金額のみだからそれを取得する分だけの処理を書くといった具合だ。
画面のデザインはあらかじめデザイナーが作成したものが用意されていた。画面上に表示するデータがSOAP APIのどれに相当するのかを推定しながら、必要なデータの取得処理を追加していった。
どのデータを使うべきかはマネージャーに多めに相談をした。僕は航空券販売の仕組みに精通しているわけではないので、データのマッピングに関する責任は別の人に負ってもらいたかったためだ。
APIのインターフェースも画面のデザインに基づいて考えた。本当はクライアント側の担当者が考えた方がいいと思うのだが、僕が考える形になった。
最初はREST形式のAPIの書き方しか知らなかったのでそのように実装していた。ただ、SOAPはRPC形式のAPIなので、REST形式で表現すると不都合な部分があった。たとえば、「運賃」の取得はGETメソッドを使いたいが、SOAP API内ではquery stringとして使えない文字が含まれるデータがあったりした。
なので、RPC形式のAPIも元のAPIとは別に実装しておいた。SOAPクライアントはサーバーの実装から切り離しているため、インターフェースだけ変えたAPIを実装するのはすぐにできた。
もっとも、クライアント側の実装はRPC形式のAPIの切り替えにすぐに対応できなさそうだったので、古いAPIもそのまま残す形にしたが。
さて、Webサーバーの実装では上記のSOAPクライアントを組み込むことになる。fastify.jsの場合はプラグインとしてSOAPクライアントのインスタンスをサーバーにくっつければよいようだ。こうすれば、各URLの処理で共通のSOAPクライアントインスタンスを使用できる。
ちなみに、認可機能もプラグインとして実装した。このWebサーバーにおける認可は、別のWebアプリケーションが作成した認証情報を使う。クッキーにトークンが入っているので、そのトークンがRedisサーバー上に存在するかどうかで認可した。
さて、クライアント側の担当者は別なので、作成したサーバーのAPIの仕様を説明する必要があった。また、クライアントアプリの実装とは独立してサーバーのAPIをテストできるようにする必要があった。なので、一連のAPIを実行するサンプルスクリプトを用意した。
航空券の予約は、「検索」、「詳細表示」、「予約」の順にAPIを実行することで行われる。「検索」で旅程を表示し、その旅程に含まれる航空便について「詳細表示」APIで予約に必要な情報を取得し、「予約」APIにその情報とユーザーの名前などの追加情報を渡すことで一連の予約処理が完了する。
サンプルスクリプトはこれら一連のAPI呼び出しを行う。
後に、予約を作成するときに自社のデータベースにもその予約情報を登録する機能を追加した。こちらは予約情報を管理するWebアプリケーション内で使っているデータベースクライアントをプラグインとしてこのサーバーに組み込む形で実装した。
原理的にはSOAPクライアントが返してきた情報すべてをデータベースのデータの形式に変換することも可能なのだが、とりあえずはそこまでは必要ないのでSOAPクライアントが返すJSONをそのままデータベースに保存する形にした。