エキサイト株式会社佐々木です。アドベントカレンダー2021の2ページ目の1日目になります。メディア事業部では、SpringBoot x Gradleマルチプロジェクトを使用して、モジュラーモノリスのような構成で各メディアのリビルドの開発を進めています。既存のリポジトリは1メディアで30〜50リポジトリが存在し、リポジトリの行き来で見通しが悪くなっているので、リビルドではモノリポ x マルチプロジェクト形式にしています。
Gradleマルチプロジェクトとは
Gradleマルチプロジェクトは、1つのメインプロジェクト内にいくつものサブプロジェクトが存在する構成になります。
設定
設定には、build.gradleとsettings.gradleを使用します。
settings.gradle
マルチプロジェクトで運用するには、settings.gradleにプロジェクト名を追記します。
仮に、web,batch,api,usecase,service,repositoryのように各層ごとに分けるようにすると下記のようになります。
rootProject.name = 'demo' include "web" include "batch" include "api" include "service" include "repository" include "domain"
build.gradle
settings.gradleで分割したモジュールをbuild.gradleに反映していきます。
plugins {
id 'org.springframework.boot' version '2.6.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
subprojects { // ★1
apply plugins: "java" // ★2
apply plugin: "org.springframework.boot"
apply plugin: "io.spring.dependency-management"
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
dependencies {
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
}
project(":web") { // ★3
bootJar { // ★4
enabled = true
}
dependencies { // ★5
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation project(":service") // ★6
implementation project(":domain") // ★6
testImplementation 'io.projectreactor:reactor-test'
}
}
project(":api") {
bootJar {
enabled = true
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation project(":service")
testImplementation 'io.projectreactor:reactor-test'
}
}
project(":service") {
bootJar {
enabled = false
}
dependencies {
implementation project(":repository")
}
}
project(":repository") {
bootJar {
enabled = false
}
dependencies {
implementation project(":domain")
}
}
project(":domain") {
bootJar {
enabled = false
}
}
上記のような設定になります。★ごとに解説します。
★1
Gradleマルチプロジェクト構成にすると、プロジェクト直下以外は、すべてサプブロジェクトとなります。この時に、サブプロジェクト共通で読み込みたいようなライブラリや設定があるときに、 subprojects {} の中に書いておくと全てのサブプロジェクトに適用できます。ほぼすべてのプロジェクトで必要そうなものを記述しておきます。(LombokやSpringBootのAnnotationまわりの設定等)
★2
Gradleのプラグインの設定はサブプロジェクトごとにプラグインを使うか使わないかを決められます。逆に設定しないと使えないです。ここでよくハマっているのをみます。
★3
各サブプロジェクトごとの設定は、 project(":プロジェクト名") {} の内側に記述します。ここには、プロジェクト固有のものを設定します。例では、project(":web"){...} では、テンプレートエンジンのThymeleafとSpringBootのWeb系のライブラリを読み込んでいます。
★4
SpringBootはコンパイルすると実行可能なjarの生成が可能です。ですが、serviceプロジェクトやrepositoryプロジェクトでは、実行形式にする必要がありません(エンドポイントがないので)。そういうときには、bootJar{} を使用して、実行可能なjarを生成する・しないの設定を追加できます。webやapiプロジェクトは、エンドポイントがそれぞれあるので、bootJar{enabled=true}を指定して、実行可能なjarを生成するようにします。
★5
サブプロジェクトごとに依存関係を定義できます。ここで定義したものは、そのサブプロジェクト内でしか使えないことに注意してください。
★6
ここで、サブプロジェクト内で別のサブプロジェクトを依存関係に含めています。これを行うことで、サブプロジェクト内のコードを使えるようになります。例ではwebプロジェクトは、serviceサブプロジェクトとdomainサブプロジェクトのコードにアクセスができますが、repositoryサブプロジェクトのコードにはアクセスできないことになります。
Gradleマルチプロジェクトのメリット
Gradleマルチプロジェクトのメリットは、アクセス制御が可能になことです。依存関係をサブプロジェクト単位で定義できることによって、モジュラーモノリスやミニサービスの構成を簡単に実現できます。依存関係に定義していないサブプロジェクトやライブラリを使おうとしてもコンパイルエラーになりますので、管理が楽になります。
Gradleマルチプロジェクトのような機能がない状態で、厳密に依存関係を制御しようとすると、コードレビューをするまたはチェック用のコードを書く等のことをしなければならないですが、Gradleの依存関係の設定のみでそれが可能になります。JavaにもArchUnitというライブラリはありますが、テストコードを記述する必要があります。
最後に
アドベントカレンダー1日目を書かせていただきました。他社同様エキサイトHDでもアドベントカレンダーをやっていますので、ご覧いただけると幸いです。(https://qiita.com/advent-calendar/2021/excite-hd)
エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。
カジュアル面談はこちらになります! meety.net
募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com