Achieve modular monoliths with Gradle's multi-project

I'm Sasaki, who is in charge of media development at Excite Japan Co., Ltd.

We are currently building a CMS redevelopment using Spring Boot. At the moment, the number of people is small, so it has a monolithic structure, but in the future we plan to make it a microservice, and we created a project with an awareness of the modular monolithic structure that we have been hearing a little recently. doing. We use Gradle's multi-project to develop separate projects for each module. The outline is explained below.

General Spring Boot configuration

The general Spring Boot directory structure is as follows.

General directory structure


├── gradle
│   └── wrapper
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── controller
    │   │               ├── persistence
    │   │               ├── repository
    │   │               └── service

merit

--Simple configuration --Good outlook

Demerit

--I can't manage what I want to access for each package (persitence can be referenced from the controller) --Functional division becomes a fairly large unit

controller, repository, service, etc. exist under one project and have a very simple structure. If it's a very small application, there's nothing wrong with it, but as it grows larger, it becomes more difficult to separate by function.

Configuration change for each package using Gradle's multi-project

Gradle covers multi-project configurations by default. This is what happens when the above Spring Boot application is configured as a multi-project for each package.

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

persistence, repository, service, web are now separate projects. You will be able to manage it independently to some extent. Dependencies are also as follows when Gradle is defined.

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 { //Basically, do not start as a Spring Boot application
        enabled = false
    }

    jar {
        enabled = true
    }

    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }

    repositories {
        mavenCentral()
    }

    dependencies {
      //Define common 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 { //Defines whether it can be booted as a Spring Boot application
        enabled = true
    }

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

test {
    useJUnitPlatform()
}


merit

--Can define dependencies between projects --Independence of the project increases

Demerit

--The outlook is worse than when it is flat --A little more to consider

In the Java world, it was just a package, but making it a single project increases independence. When you want to provide external API to the above project, you can increase the number of API endpoints while keeping service and repository by rewriting as follows.

settings.gradle



include 'api'  //add to

build.gralde



// 
//abridgement
//

project(":api") {

    bootRun {
        sourceResources sourceSets.main
    }

    bootJar { //Defines whether it can be booted as a Spring Boot application
        enabled = true
    }

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

// 
//abridgement
//

By adding this, you can create a project with external API. The service layer and repository layer can be used as they are. You will be able to share models etc.

Modular monolith configuration using Gradle's multi-project

With the above configuration, by defining the commonality of modules and the dependencies in one application, it is possible to build an application that is loosely coupled to some extent without paying attention to the implementation. However, when it comes to separating as a microservice, I think that there are still many parts that are worried when cutting out or the inside of service is adhered. Gradle can hierarchize multi-projects, so you can use it to cut projects in more detail.

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

settings.gradle



rootProject.name = 'demo'

include 'core'  //Where the main business logic is gathered

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

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

build.gradle



//Abbreviation

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")
    }
}

//Abbreviation

merit

--Even if multiple services exist in the mono repo, it is possible to set them independently at the service level. --Development efficiency is improved because only general-purpose modules can be defined as dependencies.

Demerit

--One repository grows ――The outlook is getting worse little by little --The build slows down little by little as it gets bigger (about 20 seconds for a full build)

It looks like this. Both mediaA and mediaB have similar configurations and similar services. However, I want to separate DB etc., but since the main business logic is gathered in the core project, this configuration can be used when I want to synchronize. The fact that you can share business logic without interfering with each other's projects of mediaA and mediaB is often quite nice when there are many small projects. When this grows, mediaA and mediaB do not interfere with each other, so if you only care about the core part, you can cut it out as one service. There is an opinion that microservices should be used in that case, but if you are running an active project with a small number of people, it can be quite stressful to have separate repositories or slightly different environments.

Finally

It's easy, but I introduced how to realize a modular monolith using Gradle's multi-project. I haven't found much support for multi-projects other than Maven and Gradle by default. The popularity of microservices is heating up considerably, but I think that it is quite difficult to develop physically, so I think it is better to make it with a monolith once and make it easy to cut out when it matures. However, if it is just a monolith, it will be difficult to unravel it with just the rules, so I think Spring Boot + Gradle, which can be divided by project, is a good option.

Excite Japan Co., Ltd. is looking for engineers who can or want to develop their own services. Please contact us from the following!

https://www.wantedly.com/companies/excite/projects

Recommended Posts

Achieve modular monoliths with Gradle's multi-project
Multi-project with Keycloak
Java multi-project creation with Gradle