STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

Go と GraphQLで作る組織管理基盤

こんにちは。プロダクト基盤グループの inari111 です。STORES の各プロダクトへ導入する共通基盤を開発しています。
私の部署内で2つ目の基盤プロダクトとなる組織管理基盤を作ったのでご紹介します。
社内では「maja(マヤ)」と呼ばれています。

この記事では maja の Go アプリケーション部分について説明します。

maja とは

事業者、店舗、従業員、従業員の権限を管理する基盤プロダクトです。
各プロダクトがそれぞれ実装していた事業者や従業員といったデータを集約していくために maja を作ることになりました。

maja を作ることになった詳しい経緯はこちらの記事をご覧ください。

note.st.inc

maja フロントエンドはこちらで紹介されています。

product.st.inc

バックエンドは Go で実装していて、 GraphQL の API を提供しています。

アーキテクチャ

シンプルなレイヤードアーキテクチャで実装しています。

以前実装した ID 基盤とほぼ同じアーキテクチャで各レイヤーの責務は同じです。

product.st.inc

DI

google/wire を使って DI を行っています。

Handler

ルーターに go-chi/chi を使っています。
GraphQL を採用しているので 99designs/gqlgen を使ってスキーマから resolver やモデルを生成し使っています。

Application

ユースケースと言われるような、アプリケーションのビジネスルールを表現するレイヤーです。
具体的には、Domain 層の呼び出し、 Repository を使ったデータの永続化や取得、外部 API 出しなどを行います。
また、DB のトランザクション処理はここで行っています。

Domain

ドメインモデル、ドメインロジックの一部、Repository 等の interface を置いています。

Infra

Infra 層では DB モデル、ドメイン層で定義した Repository の実装、その他 interface の実装を置いています。
DB まわりは jmoiron/sqlx を使って素朴に実装しています。

テスト

テストは主に2種類あります。

Application 層(ユースケース)のユニットテスト

下記のような方針で書いています。

  • 外部へのリクエストはモック
  • DB はモックしない
  • API の E2E テストでカバーできている場合はなくても OK

API の E2E テスト

方針は Application 層とほぼ同じですが、権限のチェックなど Application 層に来る前に行っている処理のテストケースも書いています。

  • 外部へのリクエストはモック
  • DB はモックしない
  • 権限がないときにエラーになることのテストケースも書く
  • Yamashou/gqlgenc を使って GraphQL スキーマと Query から Client 用のコードを生成する

E2E テストは下記のような流れです。

バリデーション

GraphQL のスキーマにバリデーション用のディレクティブを定義し、 input のフィールドに指定しています。

constraint directiveの定義はこちら。

directive @constraint(
  maxLength: Int
  format: String
  pattern: String
  startsWith: String
  endsWith: String
  required: Boolean
) on INPUT_FIELD_DEFINITION

input のフィールドに下記のようにバリデーションルールを記載します。

# 例
input AddEmployeeInput {
  """
  従業員の名前
  """
  name: String! @constraint(required: true, maxLength: 100)

  """
  招待を送るメールアドレス
  """
  email: Email @constraint(format: "email")
}  

これらのバリデーションルールからバリデーション用のコードを生成しています。
バックエンドは gqlgen を使ったコード生成をするときに go-playground/validator 用のタグをstruct のフィールドに付与しています。

// GraphQL スキーマから生成した Go の struct
type AddEmployeeInput struct {
    // 従業員の名前
    Name string `json:"name" validate:"required,max=40"`
    // 招待を送るメールアドレス
    Email *string `json:"email,omitempty" validate:"omitempty,email"`
}

各レイヤーの依存関係のチェック

fe3dback/go-arch-lint を使いCIでチェックしています。
各レイヤーの依存関係は人間の目でチェックするのは限界があるので、こういったツールでチェックするのがおすすめです。

おわりに

Go アプリケーションまわりについて書きました。
今回説明できなかったところや詳しい解説は別記事にて書いていけたらと思います。