📘

GraphQLとgRPCにおける日付型の取り扱い

2024/07/23に公開

こんにちは。エンジニアのYです。

データ連携(バッチ取り込み)機能のGA版ではバッチの設定を管理するWeb APIを開発しました。Web APIはgRPCサーバーをバックエンドとするBFFサーバーにより構成されています。BFFサーバーはTypeScriptとApolloにより実装されたGraphQLサーバーです。
このWeb APIは、機能の1つとして日次で実行されるバッチの実行結果の取得を提供しています。実行日を指定したバッチ実行結果を取得するには、クエリパラメータとして日付を指定します。しかし、GraphQLとgRPCともに標準で日付型はサポートされていないため、一手間加える必要がありました。
本記事では、GraphQLとgRPCそれぞれにおける日付型の実現方法について説明します。

GraphQLでの日付の取り扱い

TypeScriptで書かれたBFFサーバーの実装では、先にGraphQLスキーマを定義し、それを元にコードを生成するスキーマファーストのアプローチを採用しています。TypeScriptコードはgraphql-code-generatorを用いてGraphQLスキーマから自動生成されます。
次の手順で、日付型をカスタムスカラーとして定義し、自動生成されたコード上においてはJavaScriptのDateオブジェクトとして使用できます。

特定の日付を指定してバッチの実行結果を取得するQueryを次のように定義します。Date型はカスタムスカラーとして定義します。

scalar Date

type Query {
    fetchBatchResult(
        date: Date!
    ): FetchBatchResultResponse!
}

type FetchBatchResultResponse {
    succeeded: Boolean!
}

このDate型にJavaScriptのDateオブジェクトを割り当てるにはgraphql-code-generatorの設定ファイルであるcodegen.ymlに下記の設定を追加する必要があります。

scalars:
  Date: Date

以上の設定の下でgraphql-code-generatorを実行すると、生成したTypeScriptコードにおけるDate型のフィールドには、JavaScriptのDateオブジェクトが対応します。型定義を元に実装したリゾルバのコードにおいては、requiredFields.dataが期待通りDate型になっていることがわかります。

const query: QueryResolvers<TContext> = {
    fetchBatchResult: async (_, requiredFields, context) => {
    const date = requiredFields.date;  // Date型のオブジェクトとして参照することができます。

    const succeeded = ...

    return { succeeded };
  },
};

次に、カスタムスカラーのDate型に対するリゾルバーを実装し、Apollo Serverへ渡すschemaに組み込みます。自前でリゾルバーの実装も可能ですが、今回はgraphql-scalarsが提供している、Apollo Serverに対応したDate型のリゾルバーを使用しました。graphql-scalarsをプロジェクトへインストールし、ドキュメントに従い、graphql-scalarsのリゾルバーと実装したQueryリゾルバーを共にApollo Serverへ登録します。

import { resolvers } from 'graphql-scalars';

const server = new ApolloServer({
  schema: makeExecutableSchema({
    typeDefs,
    resolvers: {
      Query: query,
      ...resolvers,
    },
  }),
});

以上のような設定をすることで、GraphQLにおいて日付を取り扱うことができます。これにより、日付フィールドをString型として定義した場合などに比べて、パースやバリデーションの実装を自前で行わずに日付操作が可能になります。

gRPCでの日付の取り扱い

標準での日付型のサポートはありませんが、google.protobuf.timestamp.protoにて日時型を扱うTimetampが公開されています。API開発の初期段階ではこのTimestampをインポートして使用していました。
しかし、Timestampにはバッチ実行結果を取得する要件に対して必要のない時刻の情報が含まれています。それにより、タイムゾーンの管理が発生し、バグが生まれやすいコードになっていました。
最終的にはTimestampを使用しない判断を行い、下記のようなuint32型のyear, month, dayを持つDateを新たに定義することで、タイムゾーンの概念を排除しました。

message Date {
  uint32 year = 1;
  uint32 month = 2;
  uint32 day = 3;
}

まとめ

GraphQLとgRPCのそれぞれにおいて日付型を取り扱う方法について説明しました。

Sprocketで働きませんか?

弊社ではカジュアル面談を実施しております。
ご興味を持たれましたら、こちらからご応募お待ちしております。

Sprocketテックブログ

Discussion