every Tech Blog

株式会社エブリーのTech Blogです。

新規プロダクトのリポジトリ構成にモノレポを採用してみた

新規プロダクトのリポジトリ構成にモノレポを採用してみた

お久しぶりです,DELISH KITCHEN開発部でSoftware Engineer(SE)をしている鈴木です.
every Tech Blog Advent Calendar 2024(夏) の15日目を担当する事になりましたので,鈴木が開発に携わっている新規プロダクトで採用しているリポジトリ構成についてお話させていただきます.

はじめに

私事ですが,夏の兆しを感じ始めたタイミングでトモニテ開発部からDELISH KITCHEN開発部に異動しました.
異動後も新規プロダクトの開発に携わっており,有り難いことに大部分を任せていただいているので,トモニテ相談室の立ち上げ時に感じていた課題をユーザーへの影響を気にせずに払拭できる絶好の機会を楽しんでいます.
課題はいくつもあり,どれも払拭に向けて奔走中なのですが,大きなところとしてポリレポ構成をやめ,モノレポ構成を採用している点があります.
今回はポリレポ構成時に感じていた課題感を共有し,モノレポ構成だとどのように解決されるのかをお伝えしていこうと思います.

ポリレポやモノレポとは?

ポリレポとは,Git等でプロダクトに必要になるソースコードを管理する際,webやserverなどの各モジュールを別リポジトリとして管理する方式のことです.
一方でモノレポはポリレポと対を成す言葉であり,プロダクトに必要になる各モジュールを単一のリポジトリで管理する方式のことです.
従って,ポリレポではプロダクトに必要なモジュールの数だけリポジトリが作成されるのに対し,モノレポでは常に1つのリポジトリが作成されることになります.

ポリレポとモノレポのイメージ

ポリレポ運用時に感じていた課題

トモニテ相談室はポリレポ方式でモジュールを管理しています.
立ち上げ時の私の経験値や社内での知見の豊富さから当時は最適解であったのですが,開発を続けるにつれ以下のような点を課題に感じるようになりました.

  • OpenAPIファイルがserverモジュールに配置されており,server以外のモジュールがスキーマ駆動開発を実践できない(しづらい)
  • 共通の定数を各モジュールがそれぞれ定義する必要があり不毛

順に詳細をお伝えしていこうと思います.

server以外のモジュールがスキーマ駆動開発を実践できない

トモニテ相談室ではOpenAPIファイルをserverモジュールに配置しています.
OpenAPIはAPIの仕様を表現するためのものであるため,ポリレポ運用をしているプロダクトでserverモジュールに配置したくなるのは自然な流れかと思います.
しかし,このような配置にすることでOpenAPIファイルを参照可能なモジュールがserverに限定されてしまうため,他のモジュールが単純にはスキーマ駆動開発を実践できなくなってしまいます.

共通の定数を各モジュールがそれぞれ定義する必要があり不毛

ポリレポ構成のプロダクトではユーザーのステータス等の共通で利用する定数は各モジュール毎に定義する必要があります.
各モジュールで言語を変えて同じ定数を定義していくのはいささか不毛であり,実装者もコードレビュワーも値に誤りがないかに神経をとがらせる必要があります.
また,その値を変更しなければならなくなった際,定義されている全てのモジュールを調べ上げ,再び値に誤りが無いように変更しコードレビューをしていく必要があります.

モノレポ構成による課題解決

現在私が開発している新規プロダクトは以下のようなモノレポ構成をとっています.

.
├── web
├── server
├── constant
└── openapi

定数やOpenAPIなどの共通で使われる部分をそれぞれconstantおよびopenapiという独立したモジュールに集約させており,以下のイメージのようにwebやserverなどが必要に応じてこれらに依存するようにしています.
このような構成を取ることにより,ポリレポ時に感じていた課題を解決出来ています.

依存関係のイメージ

どのように解決しているのかを一緒に見ていこうと思いますが,定数管理に対して感じていた課題もOpenAPIに対して感じていた課題も,本質的には各モジュールでシェアして利用すべきものをそのように出来ていなかったという点で共通しており,解決方法は酷似しています.
従って,本記事ではファイルの管理方法に特徴があるconstantモジュールをピックアップし,どのように解決したのかを見ていこうと思います.

constantモジュールによる課題解決

constantモジュールは言語に依存しない形で定数を集約し,webやserver等のモジュールがタイプセーフに定数にアクセスすることを可能にする必要があります.
以下でどのように言語に依存しない形で定数を集約し,どのようにwebやserverからタイプセーフに定数にアクセス可能にしているかを紹介していきます.

言語に依存しない定数の集約方法

constantモジュールでは以下のように言語に依存しないで定数を集約するようにしています.

constant/
└── model/
    ├── status.json
    └── status.schema.json

model/配下にはモデルに関連する定数を配置しており,(filename).jsonには実際の定数を,(filename).schema.jsonには定数のJSON Schemaを記述しています.
status.jsonstatus.schema.jsonの例はそれぞれ以下のようになります.

{
  "deactive": 0,
  "active": 1
}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "ステータス",
  "description": "ステータスを表現する定数を管理するオブジェクト",
  "type": "object",
  "additionalProperties": false,
  "required": ["deactive", "active"],
  "properties": {
    "deactive": {
      "type": "integer",
      "example": 0
    },
    "active": {
      "type": "integer",
      "example": 1
    }
  }
}

webやserverから定数にアクセスする方法

quicktypeというCLIツールを介してアクセス可能にしています.
quicktypeはJSONやJSON Schema等から様々な言語のコードを自動生成するツールです.
以下のようにquicktypeを実行する(※)と,

$ quicktype status.schema.json -s schema -o status.ts --prefer-types --readonly --no-runtime-typecheck

以下のようなコードが自動生成されます.
なお短縮のために一部コメントを削除しているのでご留意下さい.

export type Status = {
    readonly active:   number;
    readonly deactive: number;
}

// Converts JSON strings to/from your types
export class Convert {
    public static toStatus(json: string): Status {
        return JSON.parse(json);
    }

    public static statusToJson(value: Status): string {
        return JSON.stringify(value);
    }
}

このようにquicktypeを用いて(filename).schema.jsonをベースにコードを自動生成し,生成されたコードを利用し(filename).jsonを参照する事でwebやserverからアクセス可能にしています.
openapiモジュールにおいても同様であり,OpenAPIファイルからコードを自動生成し,serverやweb等のモジュールでそれらを参照しています.


短縮のために--no-runtime-typecheckフラグを利用していますが,このフラグを外すと(filename).jsonに記述されている値に誤りが有る際にバリデーションエラーが発生するようになり,ローカル開発時にミスを発見しやすくなるため,外すことをおすすめします.

モノレポを採用してみて感じたメリット・デメリット

本記事執筆時点ではモノレポ歴は1ヶ月程度なのでまだまだ掴みきれていないと思いますが,上述の課題を解決できた以外にも以下のようなモノレポのメリットを感じています.

  • issueが1つのリポジトリに集約されて分かりやすい
  • プロダクトに必要なコードが1つのリポジトリに集約されていて楽.いつも同じリポジトリにアクセスすれば良い
  • ローカル開発時に各モジュールの起動が楽.Docker Composeを用いて必要なモジュールをすぐに起動できる

一方で以下のようなデメリットも有ると思います.

  • CI/CDが複雑になる
  • 共通化に対して常に意識しなければならない
  • 共通利用するものをどのように集約しどのようにアクセス可能にするかに一定のコストがかかる

まとめ

本記事では私が新規プロダクトのリポジトリ構成にモノレポを採用した背景とその詳細,及びモノレポに対する所感を紹介させていただきました.
ポリレポはその特徴故にプロダクトに関わる各モジュールで共通して利用すべきものをシェアすることが難しくなっています.
その一方でモノレポは工夫こそ必要なものの,共通して利用すべきものを集約し,各モジュールでシェアすることが可能になっており,モノレポを採用することにより私は課題を解決することが出来ました.
この記事が私と同じようにポリレポならではの課題を感じており,モノレポに対して興味を持っていらっしゃる開発者の方々のお役に立てたら大変嬉しいです.
ここまでお読みいただきありがとうございました!