LoginSignup
3

More than 1 year has passed since last update.

posted at

updated at

Gradleのマルチプロジェクトを使ってモジュラモノリスを実現する

エキサイト株式会社メディアの開発担当の佐々木です。

弊社では、現在CMSの再開発をSpringBoot使って構築しています。現時点では人数とかも少数な為、モノリスな構成になっていますが、将来的にはマイクロサービスにはする予定で、最近少し聞くようになってきたモジュラモノリスの構成を意識して、プロジェクトを作成しています。Gradleのマルチプロジェクトを使用してモジュールごとにプロジェクトを分けて開発を行っています。下記にて概要を解説します。

一般的なSpringBootの構成

一般的なSpringBootのディレクトリ構成は下記のようになります。

一般的なディレクトリ構成
├── gradle
│   └── wrapper
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── controller
    │   │               ├── persistence
    │   │               ├── repository
    │   │               └── service

メリット

  • 構成がシンプル
  • 見通しがいい

デメリット

  • パッケージごとにアクセスしてほしいものを管理できない(controllerからpersitenceとかが参照できてしまう)
  • 機能分割がかなり大きい単位になってしまう

controller,repository,service などが、1つのプロジェクトの配下に存在し、とてもシンプルな構成です。とても小さいアプリケーションであれば、何も問題がありませんが、肥大化していくと、機能ごとにわけるのは難しくなってきます。

Gradleのマルチプロジェクトを使ったパッケージごとの構成変更

Gradleは標準でマルチプロジェクト構成をカバーします。上記のSpringBootアプリケーションをパッケージごとにマルチプロジェクト構成するとこのようになります。

├── HELP.md
├── build.gradle
├── persistence
├── repository
├── service
└── web

persistence,repository,service,webがそれぞれ別プロジェクトになりました。ある程度独立して管理できるようになります。依存関係もGradleを定義すると下記のようになります。

settings.gradle
rootProject.name = 'demo'

include 'web'
include 'core'
include 'service'
include 'repository'
include 'persistence'

build.gralde

plugins {
    id 'org.springframework.boot' version '2.3.7.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

allprojects {
    repositories {
        mavenCentral()
    }

    group = 'com.example'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '11'
}

subprojects {

    apply plugin: 'java'
    apply plugin: 'java-library'
    apply plugin: 'idea'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'

    bootJar { // 基本的にはSpringBootアプリケーションとしては起動しないようにする
        enabled = false
    }

    jar {
        enabled = true
    }

    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }

    repositories {
        mavenCentral()
    }

    dependencies {
      // 共通の依存関係を定義する
    }
}

project(":persistence") {

}

project(":repository") {
    dependencies {
        implementation project(":persistence")
    }

}

project(":service") {
    dependencies {
        implementation project(":persistence")
        implementation project(":repository")
    }
}

project(":web") {

    bootRun {
        sourceResources sourceSets.main
    }

    bootJar { // SpringBootアプリケーションとして起動できるかを定義
        enabled = true
    }

    dependencies {
        implementation project(":service")
        implementation project(":repository")
    }
}

test {
    useJUnitPlatform()
}


メリット

  • プロジェクト同士の依存関係を定義できる
  • プロジェクトの独立性が高まる

デメリット

  • フラットなときより見通しは悪い
  • 考慮するべきことが少し多くなる

Javaの世界では、ただのパッケージでしたが1つのプロジェクトにすることにより独立性が高まります。上記のプロジェクトに外部用APIを提供したいとなったときに、下記のように書き直すとservicerepositoryはそのままにAPIのエンドポイントを増やせます。

settings.gradle

include 'api'  // 追加

build.gralde

// 
// 省略
//

project(":api") {

    bootRun {
        sourceResources sourceSets.main
    }

    bootJar { // SpringBootアプリケーションとして起動できるかを定義
        enabled = true
    }

    dependencies {
        implementation project(":service")
        implementation project(":repository")
    }
}

// 
// 省略
//

これを追加することで、外部用APIのプロジェクトが作成できます。service層repository層は、いままで使用していたものをそのまま使えます。model等を共有できるようになります。

Gradleのマルチプロジェクトを使ったモジュラモノリス構成

上記の構成だと、1つのアプリケーションの中でモジュールの共通化を依存関係を定義することによって、実装上は注意しなくてもある程度疎結合なアプリケーションを構築できるようになります。しかし、マイクロサービスとして分離できるかというと、serviceの中が癒着していたり、切り出すときに悩む部分がまだ多いと思います。
Gradleは、マルチプロジェクトを階層化できますので、それを使ってより、こまかくプロジェクトを切っていきます。

├── core
├── mediaA
│   ├── persistence
│   ├── repository
│   ├── service
│   └── web
├── mediaB
│   ├── persistence
│   ├── repository
│   ├── service
│   └── web

settings.gradle

rootProject.name = 'demo'

include 'core'  // 主要なビジネスロジックが集まっているところ

include 'mediaA:service'
include 'mediaA:repository'
include 'mediaA:persistence'

include 'mediaB:service'
include 'mediaB:repository'
include 'mediaB:persistence'

build.gradle

// 略

project(":mediaA:persistence") {

}

project(":mediaA:repository") {
    dependencies {
        implementation project(":mediaA:persistence")
    }
}

project(":mediaA:service") {
    dependencies {
        implementation project(":account:service")
        implementation project(":mediaA:persistence")
        implementation project(":mediaA:repository")
    }
}

project(":mediaA:web") {

    bootRun {
        sourceResources sourceSets.main
    }

    bootJar {
        enabled = true
    }

    dependencies {
        implementation project(":mediaA:service")
        implementation project(":mediaA:repository")
    }
}

project(":mediaB:repository") {
    dependencies {
        implementation project(":mediaB:persistence")
    }
}

project(":mediaB:service") {
    dependencies {
        implementation project(":mediaB:persistence")
        implementation project(":mediaB:repository")
    }
}

project(":mediaB:web") {

    bootRun {
        sourceResources sourceSets.main
    }

    bootJar {
        enabled = true
    }

    dependencies {
        implementation project(":mediaB:service")
        implementation project(":mediaB:repository")
    }
}

// 略

メリット

  • 複数のサービスがモノレポで存在してもサービスレベルで依存しない設定が可能
  • 汎用的なモジュールのみを依存定義ができるので、開発効率はあがる

デメリット

  • 1つのリポジトリが肥大化する
  • 見通しは少しずつ悪くはなっていく
  • 大きくなっていくとビルドが少しずつ遅くなる(フルビルドで20秒くらい)

こんな形になります。mediaAもmediaBも同じような構成で似たようなサービスになります。しかし、DB等は分けたいし、でもcoreプロジェクトに主要なビジネスロジックは集まっているので、同期したいとなったとき等にこの構成が使えます。
mediaAmediaBのお互いのプロジェクトを干渉しあわずに、しかしビジネスロジックは共有できるというのは、小さいプロジェクトが多いと割と嬉しいことが多いです。これが成長していったときには、mediaAmediaBとは干渉し合ってないので、coreの部分だけ気にすれば、1つのサービスとして切り出せます。それであればマイクロサービスにすればいいじゃんという意見もありますが、アクティブなプロジェクトを少人数で運営している場合、リポジトリが分かれていたり、環境が少しずつ異なるのはかなりのストレスになります。

最後に

簡単ですが、Gradleのマルチプロジェクトを使用してモジュラモノリスの実現方法の紹介をさせていただきました。マルチプロジェクトをデフォルトでサポートしてるのはMavenやGradle以外にあまり見当たらなかったです。マイクロサービス人気がかなり加熱していますが、開発の体力的にも結構大変かと思いますので、一旦モノリスで作り、成熟したときに切り出しやすいようにしておくのがいいと判断しています。しかし、単なるモノリスだと規約だけでは、紐解くのが大変になるので、プロジェクトごとわけられるSpringBoot + Gradleはいい選択肢だとおもっています。

エキサイト株式会社では、自社サービス開発ができるまたはやりたいエンジニアを広く募集しております。
連絡は下記からお願いいたします!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3