(2019/06/15 執筆途中です。図とか追加したい)
(2019/06/19 アプリケーション構成図を追加)
「Sotetsu Lab.」というWebサービスを2014年ごろから公開しています。
ある鉄道会社の一つの編成の動きを、ユーザーが投稿する情報をもとに予測する、というのが主な機能で、そのほかに時刻表が見れたり、「何時何分に何駅を出る列車には何系が充てられる」みたいな情報がわかる、鉄道オタク丸出しなサービスです。
このWebアプリ、もはやいつ作ったのかも忘れるほど前に作りまして、ほとんど技術的な更新をしないまま(おそらく)5年以上も経ってしまったため、レガシーな技術セットとなってしまっています。
言語はPHP(CodeIgniter)、さくらのVPSにPHPサーバーとMySQLサーバーを建て、SSLはLet's Encryptで自動取得、CI/CDなんてものはもちろんなく、ソースコードをダイレクトにサーバーに上げる手法をとっていました。
今回、そんなレガシーなWebアプリをインフラからフロントエンドまでまるっとリニューアルしたので、その顛末をまとめておきます。
量が多いので、後々やったことについて詳細に記した記事を別途書こうと思っています。
リニューアル後のサイトはこちら。
https://v3.sotetsu-lab.com/
調査・設計
1. GTFSというフォーマットの存在を知る
2年ほど前に、Googleが提唱している公共交通のオープンデータフォーマット「General Transit Feed Specification」というものがあるのを知りました。
路線・停留所や駅・時刻といった情報をジャンル別にCSV形式でまとめ、zipに梱包したもので、現在では国土交通省が定める「標準的なバス情報フォーマット」にこのGTFSを拡張したものが用いられているなど、着実に日本でも広まっています。
しかしながら、日本の交通事情はGTFSが生まれたアメリカとは当たり前ですが大きく異なり、先述のようにバスであればまだシンプルなのでほぼそのまま適用できますが、こと鉄道となると、相互直通運転や種別変更といった情報が表せず、そのままでの適用は難しいな、という結論に至ります。
また、もともとがCSVファイルをzipで固めたものなので、Web APIとして使うには不便すぎるということで、「GTFSをもとにして全国の公共交通機関のオープンデータをWeb APIに出来ないか?」と考えるようになりました。
2. GTFSを拡張し、SQLで扱えるようにしたい
そこでまず、GTFSをSQLで扱えるようにパースしてみることにしました。
幸いにも、時刻表データは旧版のサイトに蓄積されたものがあるので、これをテストデータとして利用しつつ、日本の鉄道事情に適合するように拡張し、また他の時刻表・ダイヤグラム表示アプリ開発者と協議の上、「Japan Public Transport Information」フォーマットという名前で策定を進めていきました。
その成果が以下のスプレッドシートになります。(一部古いところがあります)
https://docs.google.com/spreadsheets/d/1XSroQhguG03YM2QGQ0fXlMt4RhGypVx5d2N4S_s_O_o/edit?usp=sharing
今回のリニューアルでは、基本的にこのフォーマットを下地にして実装を行っています。
3. APIを公開したい
将来的に、蓄積した公共交通機関のデータをオープンデータとして公開できるような設計にするため、アプリとAPIの間に別途認証用サーバーを作り、Open ID ConnectのClient Credential Grantで認可されたらデータソースにアクセスできる、という形を取ることにしました。
今回はひとつの鉄道会社に関する情報にとどめて開発を行いましたが、将来的には全国の公共交通機関の情報を集約できたらいいな、という願いを込めています。
インフラ
1. 大まかな構成
Sotetsu Lab.の構成は、大きく分けて、
- フロントエンド - Angular
- バックエンド - Node.js express
- API - Node.js Nest.js
- 認証サーバー
- データベース - PostgreSQL
に分かれます。
2. AWSを採用する
今回リニューアルにあたっては、AWSを採用することにしました。
フロントエンド側はSPAということもあり、S3バケットにアップロードしてCloud Front経由で配信するシンプルな形です。
バックエンド側は、フロントエンドからAPIに向けた通信をインターセプトし、認可時に使用するクライアントIDとクライアントシークレットを付与して認証サーバーへ認可処理をリクエストするだけのものなので、Node.jsで実装したロジックをServerless Frameworkを用いてLambdaにアップロードし、API Gatewayで公開しています。
今後最も負荷がかかると思われるAPIでは、DockerコンテナをECR経由でデプロイし、AWS ECSでオートスケーリングするようにしています。本当はFargateを使いたかったのですが、お金の事情でEC2です。
また、フロントエンド/APIのデプロイにあたってはCode Pipelineを使用し、Githubのmasterブランチにプッシュしたら、
- フロントエンド側:Angular CLIでビルド→S3にアップロード→Cloud FrontでInvalidate
- API側:Dockerコンテナのビルド→ECRへの登録→デプロイ
を自動でやってくれるようにしています。
おまけにECSはBlue/Greenデプロイなので、ミスった時のロールバックもボタン一つでお手の物です。
認証サーバーに関しては、AWS Cognitoをそのまま利用しています。ほんとクラウドって便利。
フロントエンド
1. Angularは重い
フロントエンドにはAngularを使用しました。
開発当時はAngular 7でしたが、現在はAngular 9が登場したので順次アップデートしています。
しかし、フルスタックなフロントエンドフレームワークであるところのAngularは、ReactやVueといったフレームワークに比べるとどうしても重くなってしまうというのが難点です。
また、Angularは内部でChangeDetectionという変更検知機構が存在し、通常状態では値が変更されたかどうかにかかわらずこれが走っているので、特にIE11のようなレガシーブラウザや。スマホのようなモバイル端末では重くなりがちです。
根本的対策としては、
- ページごとにモジュール化し、lazyloadingする
- ChangeDetectionStrategy.OnPushの積極的な使用
あたりを行いました。この結果、Lighthouseスコアもおおむねいい感じになっています。
2. Material Designの採用
旧版Sotetsu Lab.では、オレオレデザインを採用していました。
元来私はデザインがへたくそなので、一貫性のない微妙なデザインになりがちでしたが、コンポーネントライブラリとして@angular/material
を採用することで、一貫性があってなおかつ見栄えするデザインに統一することができました。
3. WebSocketでリアルタイム性を追求
Sotetsu Lab.で一番見られている機能として、「リアルタイム運用」というものがあるのですが、旧版ではリアルタイムと言いながらページを更新しないとデータも更新されない仕様でした。
これを、WebSocketを利用して「真にリアルタイム」にしました。
やっていることは、フロントエンドから「情報を投稿」フラグをサーバーに送信したら、送信者以外に「情報が投稿された」フラグを返し、それを受け取ったらAPIからデータを読み直すだけの単純なものですが、UXは向上したのではないかと思います。
バックエンド
フロントエンド側で認証情報をセキュリティ上保持できない関係上、APIとの通信に必ずバックエンドをかまし、バックエンド側で認証情報を付与してAPIに再送信する処理を行っています。BFFのようなものでしょうか。
凝ったことはやっていないので割愛します。
API
基盤となるフォーマットをもとに、各テーブルに対してCRUD処理を実装するシンプルなものです。ORMにTypeORMを使用しています。
リレーションがとても複雑で、単純に時刻表を表示するだけでもかなりのJOINが必要なので、パフォーマンス問題が今後の課題となりそうです。
ORMを無理に使わず、SQL直書きも視野に入れていきます。
まとめ
- GTFSは頑張ればWeb APIにできる
- ただしそのままでは使えないので、拡張は必要
- Angular、やっぱ重い
- しかし軽量化の余地はある
- AWSが便利すぎる
今後の展望
- 時刻表オープンデータ化の波を、バス業界だけでなく鉄道業界にも広げていきたい
- 今回作成したAPIをもとに、ディープで全国規模のオープンデータソースを作りたい
- ダイヤグラム描画(オタクしか使わないけど)
あとがき
5/31、公共交通オープンデータ協議会により、「公共交通オープンデータセンター」の運用開始が発表されました。
GTFSではなく、独自策定フォーマットのJSON-RD形式によるWeb APIですが、現時点では唯一の、信頼できる筋が提供する日本の公共交通オープンデータベースとなります。
これにより、今後公共交通とITのかかわりがさらに広がっていくことが予想されます。
鉄道とプログラミングの両方を趣味とするものとしては、この流れを歓迎しつつ、今後の行く末を注意深く見守っていき、あわよくば乗っかっていきたいと思っています。
広がれ、公共交通オープンデータ。