バックエンドでマスターデータを扱うときのTips

ATOM開発チームの上野です。 普段はGo言語を使ってAPIサーバやバッチ処理機構の実装などを担当しています。

今回はATOMのAPIサーバのプロジェクトにおける マスターデータの運用手法をご紹介します。

マスターデータとは

マスターデータはサービスを動かす上で必要な基本的な情報であり、 サービスを提供する側が定義するデータになります。

ATOMでは、設定画面で選択できる項目やそのカテゴリ情報など さまざまなデータをマスターデータとして扱っています。

マスターデータは各ユーザが登録した情報と比較して データ量が少なく、更新頻度が少なく、参照される頻度が多いという特徴があります。

マスターデータの運用

ATOMではバックエンドのサービスでマスターデータを利用するために リレーショナルデータベースにインポートする形式にしています。

マスターデータの更新作業以下のフローで行っています。

  1. Googleスプレッドシート 上でデータの更新作業をする
  2. 更新したデータをCSVにエクスポートしてGitのリポジトリに取り込む
  3. サービスが正しく動作することをテストする
  4. リリース作業の一環でデータベースにCSVのマスターデータをインポートする

インポートされたマスターデータはAPIサーバで加工されてフロントエンドのアプリケーションに渡されたり、 APIサーバ内でのロジックの実装で直接利用されたりします。

キャッシュ機構

マスターデータは参照される頻度が多く、データ量は比較的少ないため、 キャッシュすることでバックエンドのパフォーマンスを向上させることが可能です。

ATOMでは go-cache という簡易的なオンメモリキャッシュ機構を提供するパッケージを利用しています。 go-cacheはGoの標準パッケージのみで実装されており、非常にシンプルに用件を満たしてくれるので採用しました。

例えば以下のように、データベースからデータを取得する箇所に差し込むことで簡単に導入することができます。

package table

import (
    "time"

    "github.com/patrickmn/go-cache"
)

var (
    CacheStore                *cache.Cache
    TableCacheExpiration      = 3 * time.Minute
    TableCacheCleanupInterval = 10 * time.Minute
)

// Category はマスターデータ
type Category struct {
    //
}

func init() {
    // ストアを初期化
    CacheStore = cache.New(TableCacheExpiration, TableCacheCleanupInterval)
}

// GetAllCategory はマスターデータのカテゴリ情報を返す
func GetAllCategory() []*Category {
    // キャッシュがあればそれを返す (DBへのアクセスが発生しない)
    if res, found := CacheStore.Get("cache-key-category"); found {
        if rows, ok := res.([]*Category); ok {
            return rows
        }
    }

    // なければデータベースから取得する
    categories := MyDatabaseClient.FindAllCategory()

    // キャッシュに保存して返す (以降、TableCacheExpiration の間はDBへのアクセスが発生しない)
    CacheStore.Set("cache-key-category", categories, 0)
    return categories, nil
}

マスターデータを元に定数を自動生成

サービスを実装する上で、例えば以下のようにロジックにマスターデータが組み込まれる時があります。

if category.Name == "special" {
    if user.IsGuest() {
        return errors.New("ゲストはスペシャルなカテゴリを閲覧することはできません!")
    }
}

しかし、このようにマスターデータに依存するロジックを実装した場合、 マスターデータの値の変更によって不具合が発生する可能性が生じます。 上記の場合は special という文字列が別の単語になってしまうと挙動が変わります。

そのため、マスターデータの中でもロジックで使用されるものは定数部分のソースコードを自動生成することによって、 こういったミスが発生しないようにしています。

package masterdata

// 以下はマスターデータのCSVファイルの情報を元に自動生成されています
const (
    CategoryA       = "a"
    CategoryB       = "b"
    CategorySpecial = "special"
)
if category.Name == masterdata.CategorySpecial {
    if user.IsGuest() {
        return errors.New("ゲストはスペシャルなカテゴリを閲覧することはできません!")
    }
}

リポジトリに入っているマスターデータの更新作業と同時に定数のソースコードも更新することで、 意図せずマスターデータの値が変更された場合もコンパイルエラーにより検知することができます。 こういった地道な工夫が不具合を発生しにくくするポイントだと考えて取り組んでいます。

まとめ

今回はATOMプロジェクトにおけるマスターデータの運用手法をご紹介しました。 マスターデータの運用はどのプロジェクトでも悩むポイントだと思いますので、ぜひ参考にしていただければと思います!