gqlgenを使ったファイルアップロード機能を実装して感じたこと

キャンプ場を検索・予約できるサービス(以下hinata スポット)などの開発を担当している名嘉眞です。

hinata スポットなどvivitのいくつかのプロダクトでは、フロントエンドとバックエンドの間にBFFが存在しており、BFFではGraphQLを採用しております。 またBFFはGoで書かれていて、gqlgenというライブラリを使用しています。

今回は画像ファイルのアップロード機能をgqlgenを使って実装した話をします。

実装内容としては、gqlgenのドキュメントに記載されているファイルアップロードの例とほぼ同じです。 gqlgen File Upload このドキュメントを見てもらえれば実装はできるのですが、実装に際して感じたことを書いていこうと思います。

gqlgenのドキュメントを読む前

今回ブログとして書くきっかけになったタスクに着手する以前から、他のマイクロサービスで既に画像アップロード機能はありました。 実装内容としては、画像アップロードのmutationのパラメータに画像ファイルをbase64エンコードして含めるという方法でした。 この方法だと以下のような点で使いにくさがありました。

base64エンコードでアップロードする方法の使いにくい点

  • ファイル名はファイルとは別で渡す必要がある

  • string型で受け取るので扱いにくい(例:GCSなどにアップロードする際にio.Readerに変換する必要がある)

  • テストコードを書く際もファイルをbase64エンコードした文字列が必要

gqlgenのFile Uploadのドキュメントを読む

gqlgenのドキュメントに以下のように記載されています。

Graphql server has an already built-in Upload scalar to upload files using a multipart request. It implements the following spec https://github.com/jaydenseric/graphql-multipart-request-spec, that defines an interoperable multipart form field structure for GraphQL requests, used by various file upload client implementations. To use it you need to add the Upload scalar in your schema, and it will automatically add the marshalling behaviour to Go types.

GraphQLの様々なクライアントで運用可能なマルチパートフォームの仕様に準じて実装していて、使用するためには Upload スカラーの追加が必要とのことです。

仕様として公開されている方法でファイルアップロードの機能を実装できることはコードの保守性などを考えても良いことだと感じます。

以下のように Upload スカラーの追加することで、Upload型が定義できるようになります。

scalar Upload

type Mutation {
  uploadImage(input: UploadImageInput!): UploadImageOutput!
}

input UploadPlanImageInput {
  """
  画像データ
  """
  file: Upload!
}

生成されたGoのコードについて

gqlgenを利用してGraphQLスキーマからGoのコードを生成した結果が以下のようになります。

type MutationResolver interface {
    UploadImage(ctx context.Context, input model.UploadImageInput) (*model.UploadImageOutput, error)
}

type UploadImageInput struct {
    // 画像データ
    File graphql.Upload `json:"file"`
}

type UploadImageInput のFileフィールドは、graphql.Upload となっています。 この graphql.Upload は以下のような構造体です。

type Upload struct {
    File        io.Reader
    Filename    string
    Size        int64
    ContentType string
}

ファイル自体はio.Raader型で定義されていて、ファイル名やファイルサイズ、ファイルの種類も定義されていて使いやすそうです。

ここでこの記事の冒頭に記載したbase64エンコードでアップロードする方法の使いにくい点を振り返ってみます。

base64エンコードでアップロードする方法の使いにくい点

  • ファイル名はファイルとは別で渡す必要がある

  • string型で受け取るので扱いにくい(例:GCSなどにアップロードする際にio.Readerに変換する必要がある)

  • テストコードを書く際もファイルをbase64エンコードした文字列が必要

上記の使いにくい点がUpload型で改善できます。

  • ファイル名が構造体のフィールドとして定義されている

  • ファイルがio.Reader型なので扱いやすい。

  • io.Readerはinterfaceなのでテストコードも書きやすい(io.Readerを実装した別のオブジェクトに差し替えることができる)

使いにくい点が全て解決できました。もちろん全てユースケースにあてはまるわけではないと思いますが、基本的にはgqlgenを使う場合のファイルアップロードはUpload型を使っていきたいと思いました。

さいごに

vivit では、エンジニアを積極採用中です!

ポジションも様々用意しておりますので、GoやGraphQLを使用したプロダクト開発に興味を持って頂いた方は、是非カジュアル面談にお越しください。

新卒採用

www.wantedly.com

キャリア採用

www.wantedly.com

www.wantedly.com

www.wantedly.com

皆様とお話できるのを楽しみにしております!!