こんにちはnekoです。
さっそくですが、皆さんはモバイルアプリ開発の中で、以下のような状況が発生した時、どのようにモックデータを作っているでしょうか。
- アプリ側の画面要件はだいたい決まっている
- しかし、APIの開発を待つ必要があり、アプリの開発を進めるためにはモックデータを使わないといけない
今回はこうした状況に対して STORES レジ で試した、Dockerを利用してモックデータを返すAPIサーバーを作った話を紹介させていただきます。
従来の対応
冒頭にあげた状況で私がよく行っていた方法は以下のようなものでした。
- 想定されるAPIのレスポンスを受け取る構造体と、その構造体を利用してモックデータを返却する関数を作る
- アプリの画面側ではその関数を呼び出して利用し、データの表示やデザインなどを実装する
しかし、この方法を行った場合、実際にAPI側の開発が完了した後で、APIの繋ぎ込みのためにそこそこの工数が発生します。
設計によって多少異なりますが、例えば、APIリクエスト、レスポンスのパース、構造体への変換処理などです。
せっかく先行して開発を進められている状況ならば、上記のような実装も可能な限り本番に近い状態で実装を進めておき、APIの開発が完了した後は疎通確認を行うだけで済むようにしておきたいものです。
実際には想定されるレスポンスを前提として、そうした実装も先行して行うことはできますが、通信を伴った確認はできません。
また、別の方法として、レスポンスをjsonファイルとしてプロジェクト内に配置し、そのjsonファイルを利用してパース部分からの実装と動作確認を行う方法もあります。
しかし今度はjsonファイルを手動で作るのは非常に面倒という問題が出てきます。
データ数が多かったり、複数のエンドポイントに対するAPIを作らなければならない状況下ではなおさらです。
そこで、こうした問題を解決するために、ローカル環境にDockerを利用したAPIサーバーを作り、そこからモックデータを受け取れる環境を作りました。
Hasuraの利用
このアプローチを考えたとき、最初に利用したのはHasuraでした。
HasuraはデータベースエンジンのスキーマからGraphQLを構築してくれるミドルウェアです。
HasuraはDockerイメージが配布されており、公式ドキュメントの手順に沿って進めれば、すぐにGraphQLを利用できます。
実際に自分のローカル環境にHasuraを使ってGraphQLサーバーを構築してみましたが、ダッシュボードが直感的に扱えるようになっていて好印象でした。
しかし、いざアプリから利用しようとしたところ、Schemaファイルのダウンロードがエラーになってしまいうまくいきませんでした。
解決方法を調べてもなかなか解決せず、どうしようかと悩んでいたところ、もう1つ大きな問題があることに気づきました。
実際にプロダクトで扱うGraphQLは、レスポンスの一部をカスタマイズしており、同じような変更をHasura側に加えるのが難しそうということです。
Hasuraのソースコードを直接変更することで対応できそうではありましたが、そこまでするなら自前でGraphQLサーバーを作ってしまった方が早いだろうという結論に至りました。
Hasuraは簡易にGraphQLを試すには優れたサービスですし、活用できる場面も多そうです。
ただ、今回の目的にはマッチせずに見送りとなりました。
自前でのGraphQL実装
さて、Hasuraの利用が難しそうということで、あらためてゼロからDockerにGraphQLサーバーを構築することとしました。
今回は目的からして実際のプロダクトに近い環境の方が良いだろうと考え、言語としてはRuby on Railsを採用しました。
前節で触れたように、プロダクトで扱うGraphQLはレスポンスの一部をカスタマイズしている箇所がありました。
そうした箇所をそのまま再現するためにも、同じ言語・フレームワークのほうが手っ取り早いだろうという判断です。
GraphQLの基本的な実装自体については他に情報が山ほど出ていますので、今回の対応で特に工夫した点や苦労した箇所についてのみ触れていきます。
クライアントの実装
iOSアプリ側ではGraphQL APIのリクエストにApolloを利用しています。
従来からある実装としては、ApolloClientを抽象化したClientを作り、それを各リクエストで利用する形にしています。
private lazy var client: ApolloClient = { let client = ApolloClient( networkTransport: RequestChainNetworkTransport( interceptorProvider: AuthInterceptorProvider( ... ... ), endpointURL: configuration.endpoint ), store: store ) return client }()
ここにlocalhostを向いているClient実装を追加し、モックサーバーを利用したいリクエストに関してはこのClientを利用しました。
private lazy var localClient: ApolloClient = { let client = ApolloClient( networkTransport: RequestChainNetworkTransport( interceptorProvider: AuthInterceptorProvider( ... ... ), endpointURL: URL(string: "<http://localhost:3000/graphql>")! ), store: store ) return client }()
途中の実装を省略しているため分かりづらいですが、例えばRepositoryからの利用は下記のようになっています。
向き先をlocalからproductに変更したい場合は、try await local
部分をプロダクト実装のインタフェースに変更するだけで良いようにしました。
public func data(_ request: SampleAPI.Request) async throws -> SampleAPI.Response { let data = try await local( for: SampleAPI.SampleQuery( ... ... ), cachePolicy: .fetchIgnoringCacheData ) let pageInfo = PageInfo( ... ) return ... }
プロジェクト内にモックデータを作っていた場合、繋ぎ込みの際には不要になった実装を削除したりする手間も発生しますが、この方法であればそうした手間もなくなります。
※ 文中のソースコードはブログ掲載用に簡易化しており、実際のプロダクトコードとは異なります
残っている課題と今後
すべてがうまくいったわけではなく、いくつかの課題も残っており、schemaファイルのマージが自動化できていないことが一番大きな障害になっています。
アプリ側で利用しているApolloはschema.graphqlファイルを元にFragmentなど必要なSwiftファイルを自動で生成する機能を持っています。
通常はサーバーから最新のschema.graphqlを取得し、Swiftファイルを生成する部分はコマンドで自動的に行っています。
しかし、今回の方法ではDockerのモックサーバーには既存からあるすべてのAPIが存在するわけではありません。
そのため、モックサーバーのschema.graphqlを元に、アプリ側のSwiftファイルを機械的に生成できません。
現状はまだこの部分をうまく自動化できておらず、モックサーバーのschema.graphqlから必要な部分のみを手動で差し込んでいます。
この部分を手動で行うことは地味にストレスになっているため、今後スクリプトを自作することを検討しています。
また、今後はモバイルアプリエンジニアに馴染みのあるSwiftやKotlinなど、Ruby on Rails以外での実装を試すことも考えています。
私自身はRuby on Railsでの開発経験があるため支障ありませんでしたが、チーム全体で捉えるとそうとは限らないため、上記のようなことをチーム内でワイワイと試しても楽しいかなと考えています。
まとめ
今回はDockerを利用してモックデータを返すAPIサーバーを作り、モバイルアプリ開発に利用した話を紹介させていただきました。
この運用もまだ始めたばかりなので、今後も色々と模索しながら試していきたいと考えています。