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.
The general Spring Boot directory structure is as follows.
General directory structure
├── gradle
│ └── wrapper
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ ├── controller
│ │ ├── persistence
│ │ ├── repository
│ │ └── service
--Simple configuration --Good outlook
--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.
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()
}
--Can define dependencies between projects --Independence of the project increases
--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.
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
--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.
--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.
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