この記事はカバー株式会社 Advent Calendar 2023 15日目の記事になります。
カバー株式会社エンジニアのSです。よろしくお願いいたします。
前回の記事は @ys-cover による「カバー株式会社におけるAWS Control Towerの導入」でした。AWS Control Tower の導入を検討されている方にとって、とても興味深い内容となっておりますので、こちらの記事もぜひご覧ください。
はじめに
先日弊チームメンバーが作成した記事が公開されました。記事に対する反応で多かったのが API クライアントの自動生成の箇所でした。
API クライアントの自動生成には openapi-generator の dart モジュールを利用しています。早期に API クライアント自動生成の仕組みを整えることで、クライアントチームは本質的な機能実装に注力可能になりました。
また API クライアントは Dart 標準の http ライブラリを用いて作成されるため、メンテナンスコストを低く抑えられ、拡張性も高く様々な要件に柔軟に対応可能でした。
本記事では openapi-generator の導入方法の詳細は省きつつ、生成された API クライアントの拡張方法や便利な使い方について具体例を交えて紹介します。
便利な使い方
openapi-generator 経由で自動生成された API クライアントは下記で初期化することで利用可能です。
import 'package:openapi/api.dart' as openapi;
final baseUrl = "https://api.example.com";
final apiClient = openapi.ApiClient(
basePath: baseUrl,
);
言及がない限り、後続のコードブロック内に登場する apiClient
は上記のものを指します。
共通リクエストヘッダを定義したい
下記の addDefaultHeader
関数で共通リクエストヘッダの定義が可能です。
// 共通リクエストヘッダの定義
apiClient
..addDefaultHeader('App-Version', '1.0.0')
..addDefaultHeader('Build-Number', '45')
..addDefaultHeader('Accept-Language', 'en-US,en;q=0.5');
動的に Authorization リクエストヘッダを定義したい
API クライアント初期化時、下記の authentication
引数の指定で可能です。
import 'package:openapi/api.dart' as openapi;
final baseUrl = "https://api.example.com";
final apiClient = openapi.ApiClient(
basePath: baseUrl,
authentication: ApiClientAuth(
() async => authUser?.getIdToken(),
),
);
authentication
引数に指定する ApiClientAuth
の内容は下記です。指定する値は、自動生成された抽象クラス Authentication
を継承している必要があります。
abstract class Authentication {
Future<void> applyToParams(
List<QueryParam> queryParams,
Map<String, String> headerParams,
);
}
import 'package:openapi/api.dart';
class ApiClientAuth implements Authentication {
ApiClientAuth(this.tokenCaller);
final Future<String?> Function() tokenCaller;
@override
Future<void> applyToParams(
List<QueryParam> queryParams,
Map<String, String> headerParams,
) async {
final accessToken = await tokenCaller();
if (accessToken != null) {
headerParams['Authorization'] = 'Bearer $accessToken';
}
}
}
利用する API の Client を差し替えたい
例えば、リトライポリシーや通信時に割り込み処理を追加したい場合、標準 http ライブラリだけでは対応できません。対応の一つとして http ライブラリの Client を差し替える方法があります。
自動生成された API クライアント内部では、標準 http ライブラリの Client を利用しています。そのため、BaseClient を継承したクラスであれば差し替え可能です。
http_interceptor のような標準 http ライブラリとの連携が意識されているライブラリであれば Client を差し替え可能です。flutter pub add http_interceptor
実行後に下記で差し替え時の挙動確認が可能です。
import 'package:openapi/api.dart' as openapi;
final apiClient = openapi.ApiClient(
basePath: baseUrl,
)..client = InterceptedClient.build(
interceptors: [LoggingInterceptor()],
retryPolicy: ApiClientRetryPolicy(),
);
interceptors
引数に指定する ApiClientInterceptor
の内容は下記です。指定する値は InterceptorContract
を継承している必要があります。
import 'package:http_interceptor/http/interceptor_contract.dart';
import 'package:http_interceptor/models/request_data.dart';
import 'package:http_interceptor/models/response_data.dart';
class LoggingInterceptor implements InterceptorContract {
@override
Future<RequestData> interceptRequest({required RequestData data}) async {
print(data.toString());
return data;
}
@override
Future<ResponseData> interceptResponse({required ResponseData data}) async {
print(data.toString());
return data;
}
}
retryPolicy
引数に指定する ApiClientRetryPolicy
の内容は下記です。指定する値は RetryPolicy
を継承している必要があります。
import 'package:http_interceptor/models/retry_policy.dart';
class ApiClientRetryPolicy extends RetryPolicy {
@override
int get maxRetryAttempts => 3;
@override
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
if (500 <= response.statusCode) {
return true;
}
return false;
}
}
おわりに
弊チームでは本記事内で紹介した内容で API クライアントを利用しております。OpenAPI 定義ファイルから Flutter で利用可能な API クライアントの自動生成を検討されている方の参考になれば幸いです。
また、本記事では紹介しませんでしたが openapi-generator の dart モジュールの導入を検討したくなった方のために、参考記事を後述の参考リンクに載せておきます。
次回は @mk-cover による「「笑い声」を分析・検証してみる」です。こちらも是非ご覧ください。