Gradle๋ก ๋ฉํฐ๋ชจ๋ ํ๋ก์ ํธ ๊ตฌ์ฑํ๊ธฐ
์๋ก
ํ์ฌ ์ ๊ฐ ์๋ ๋ถ์์์๋ ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ณต์ ํ๋ ๋ค์์ ํ๋ก์ ํธ๊ฐ ์กด์ฌํ๋๋ฐ, ํ๋์ ๋๋ฉ์ธ์ ๊ณต์ ํ๊ณ ๊น์ ๋ฐ๋ก ๊ด๋ฆฌํ๊ธฐ ์ํด ๋๋ฉ์ธ ํด๋์ค๋ค์ ๋ฉํฐ ํ๋ก์ ํธ์ Nexus Maven ์ ์ฅ์๋ฅผ ์ด์ฉํด์ ๋๋ฉ์ธ ํด๋์ค๋ฅผ ๋ถ๋ฆฌํ๋ ํํ๋ก ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ ๋ ์ด๋ฏธ ์ธํ ์ด ๋ ํ๊ฒฝ์์ ๊ฐ๋ฐ์ ํ๊ณ ์๊ธฐ์ ๋ฐ๋ก ์ค์ ์ ๊ดํด์๋ ๊ฑด๋๋ฆด ๋ถ๋ถ์ด ์๊ธฐ๋ ํ๊ณ , ์ง์ ๊ตฌ์ฑํด ๋ณธ ์ ๋ ์์ด์ ์ด๋ฒ๊ธฐํ์ ์ ๋ฆฌ์ ํจ๊ป ์ง์ ํ๊ฒฝ ๊ตฌ์ฑ์ ํด๋ณด๊ณ ์ ํฌ์คํ ํ๊ฒ ๋์์ต๋๋ค.
์ด๋ฒ ๊ฒ์๊ธ์์๋ ๋ฉํฐ๋ชจ๋์ ์ ์์ Gradle์ ์ด์ฉํ ๋ฉํฐ๋ชจ๋์ ๊ตฌ์ฑ์ ๋ํด ์ ๋ฆฌํด๋ณด๊ฒ ์ต๋๋ค.
๋ชฉ์ฐจ
- ๋ฉํฐ๋ชจ๋์ด๋?
- Gradle๋ก ๋ฉํฐ๋ชจ๋ ๊ตฌ์ฑํ๊ธฐ
- ํ ์คํธ
ํ ์คํธ ํ๊ฒฝ
- Apple Silicon (M1), IntelliJ
- Java 11, Spring Boot, JPA, Gradle
- Docker MySQL
์์ค ํ์ธ
https://github.com/dkswnkk/multi-module-test/tree/main/multi-module-project
๋ฉํฐ๋ชจ๋์ด๋?
๋ฉํฐ ๋ชจ๋์ ํ๋์ ํ๋ก์ ํธ๋ฅผ ์ฌ๋ฌ ๊ฐ์ ์์ ๋ชจ๋๋ก ๋๋์ด์ ๊ฐ๋ฐํ๊ณ ๊ด๋ฆฌํ๋ ๋ฐฉ์์ ์๋ฏธํฉ๋๋ค. ๊ฐ๊ฐ์ ๋ชจ๋์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐ ๋ฐ ๋น๋๋ ์ ์๊ณ , ํ์ํ ๊ฒฝ์ฐ ๋ค๋ฅธ ๋ชจ๋๊ณผ ์์กด์ฑ์ ๊ฐ์ง ์ ์์ต๋๋ค. ํ์ฌ ๋ง์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ํ๋ ์์ํฌ์์ ๋ฉํฐ๋ชจ๋์ ์ง์ํ๋ฉฐ, Java์์๋ Maven, Gradle๊ณผ ๊ฐ์ ๋น๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉํฐ๋ชจ๋์ ๊ตฌ์ฑํ ์ ์๊ณ , Python์์๋ setuptools, Node.js์์๋ npm ๋ฑ์ด ์์ต๋๋ค.
์ฆ, ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ Util, Domain, Repository ๋ฑ์ ๊ณตํต ๋ชจ๋๋ก ๋ถ๋ฆฌํด ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ข ๋ ์ง๊ด์ ์ผ๋ก ์๋ฅผ ๋ค์ด์ ์์๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ ์ ์ด๋ฏธ์ง๋ ๋จ์ผ ํ๋ก์ ํธ์ ๋๋ค. API ์๋ฒ, Batch ์๋ฒ, Admin ์๋ฒ๋ ๊ฐ๊ฐ์ด ๋ ๋ฆฝ๋ ํ๋ก์ ํธ์ด๋ฉฐ, ๊ฐ๊ฐ์ ์๋ฒ๋ค์ Spring Boot + JPA๋ฅผ ์ฌ์ฉํ๊ณ ์๊ธฐ์ User ์ํฐํฐ๋ฅผ ํ์๋ก ํ๊ณ ์์ต๋๋ค. ๋ฐ๋ผ์ ๊ฐ ์๋ฒ๋ง๋ค, ๊ณตํต๋ User๋ผ๋ ๋๋ฉ์ธ์ ๊ฐ์ง๊ณ ์์ด์ผ ์๋น์ค ๋ก์ง์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
๋ฐ๋๋ก ์ ์ด๋ฏธ์ง๋ ๋ฉํฐ ๋ชจ๋ ํ๋ก์ ํธ์ ๋๋ค. API ์๋ฒ, Batch ์๋ฒ, Admin ์๋ฒ๋ ๊ฐ๊ฐ์ ์๋ฒ๋ง๋ค ๊ฐ์ง User ์ํฐํฐ๊ฐ ์๋๋ผ ๋์ผํ ํ๋์ User ์ํฐํฐ์ ์์กดํ๊ณ ์์ต๋๋ค.
์ ์์๋ฅผ ํตํด ๋จ์ผ ํ๋ก์ ํธ์ ๊ฒฝ์ฐ์ ํฌ๊ฒ ๋ค์ ๋ ๊ฐ์ง์ ๋ฌธ์ ์ ์ด ์๊ธด๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
- ๊ณตํต์ ์ธ ์ฝ๋ ์ฒ๋ฆฌ ํ์: ๋์ผํ ํ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ์ง๋ง domain์ ๊ฐ๊ฐ์ ํ๋ก์ ํธ์ ์ ์ธํ๊ธฐ ๋๋ฌธ์, ๋์ผ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด API ์๋ฒ์์ ๋๋ฉ์ธ์ด ๋ณ๊ฒฝ๋๋ฉด ๋์ผํ ์์ ์ Batch ์๋ฒ์๋ ์งํํด์ผ ํ๋ค.
- ํด๋จผ ์๋ฌ: ํ๋ก์ ํธ๊ฐ ์ปค์ง์๋ก ๊ณตํต๋ ์ฝ๋๋ค์ ์ฌ๋์ ์ค์๋ก ์ผ์น์ํค์ง ๋ชปํด ์๋ฌ๊ฐ ๋ฐ์ํ ํ๋ฅ ์ด ๋์์ง๋๋ค.
์ฆ, ํ ํ๋ก์ ํธ์์ ๋๋ฉ์ธ(Entity) ํด๋์ค์ ๋ณ์๋ฅผ ์ถ๊ฐํ ๊ฒฝ์ฐ์ ๋ค๋ฅธ ๋ชจ๋ ํ๋ก์ ํธ์์๋ ๋์ผํ๊ฒ ์ถ๊ฐํด์ฃผ์ง ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ ์ ๋ฑ ์๋ฌ๊ฐ ๋ฐ์ํ ์๋ฐ์ ์๋๋ฐ ์ด๋ฐ copy & paste ๋ฐ๋ณต ์์ ์ ๊ฐ๋ฐ์๊ฐ ์์ ํ๋ค ๋ณด๋ฉด ํด๋จผ์๋ฌ๊ฐ ๋ฐ์ํ ์๋ฐ์ ์์์ ๋ฌผ๋ก ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ Entity ์์๋ ๋ถ๊ตฌํ๊ณ ์ฝ๋๊ฐ ๋ชจ๋ ๋ค๋ฅธ ์ด์ํ ์ํฉ์ด ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
Gradle๋ก ๋ฉํฐ๋ชจ๋ ๊ตฌ์ฑํ๊ธฐ
์ด์ ๋ฉํฐ๋ชจ๋ ํ๋ก์ ํธ๋ฅผ ํ๋ฒ ๊ตฌ์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ ์ ๋ multi-module-project๋ผ๋ Gradle ํ๋ก์ ํธ๋ฅผ ์์ฑํ์ต๋๋ค. ์ฌ๊ธฐ์ ์ด ํ๋ก์ ํธ๊ฐ ๋ฃจํธ ํ๋ก์ ํธ๊ฐ ๋๋ฉฐ ๋ค๋ฅธ ๋ชจ๋ ํ์ ๋ชจ๋๋ค์ ๊ด๋ฆฌํ๋ ์ญํ ์ ํ๊ฒ ๋ฉ๋๋ค.
๋ฃจํธ ํ๋ก์ ํธ๋ ํ์ ๋ชจ๋์ ๊ด๋ฆฌํ๋ ์ญํ ๋ง ํ๊ธฐ ๋๋ฌธ์ ๋ถํ์ํ srcํด๋์ ๊ฐ์ ๊ตฌ์ฑ๋ค์ ์ ๊ฑฐํด ์ค๋๋ค.
๋ค์์ผ๋ก Root ๋ชจ๋ ํด๋ฆญ > New > Module.. ์ ํตํด ๋ค๋ฅธ ํ๋ก์ ํธ ๋ํ ์์ฑํฉ๋๋ค.
์ ๋ ์์ ๊ฐ์ด ๊ฐ๋จํ๊ฒ multi-module-api, multi-module-batch, multi-module-database์ Spring Project ๋ชจ๋๋ก ๋ง๋ค์์ต๋๋ค.
- multi-module-api: API ๊ด๋ จ ๋ชจ๋
- multi-module-batch: Batch ๊ด๋ จ ๋ชจ๋
- multi-module-database: multi-module-api์ multi-module-batch์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ๋ชจ๋(์ฝ๊ฒ ๋งํ๋ฉด ํ์ฌ ์์์์๋ ์ํฐํฐ)
๊ทธ ํ, mult-module-project์ setting.gradles์์ ๋ชจ๋๋ค์ include ํด์ค๋๋ค.
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.9'
}
repositories {
mavenCentral()
}
bootJar.enabled = false
subprojects {
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
apply plugin: 'java'
apply plugin: 'java-library'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
// ๊ด๋ฆฌํ๋ ๋ชจ๋์ ๊ณตํต dependencies
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
test {
useJUnitPlatform()
}
}
project(':multi-module-api') { // ์ปดํ์ผ ์ multi-module-database ๋ก๋ฉ
dependencies {
compileOnly project(':multi-module-database')
}
}
project(':multi-module-batch') { // ์ปดํ์ผ ์ multi-module-database ๋ก๋ฉ
dependencies {
compileOnly project(':multi-module-database')
}
}
project(':multi-module-database') { // multi-module-databse๋ bootJar๋ก ํจํค์ง ํ ํ์ ์์
bootJar { enabled = false }
jar { enabled = true }
}
multi-module-project, ์ฆ root ํ๋ก์ ํธ์ build.gradle๋ ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ์ค์ ํฉ๋๋ค.
multi-module-database๋ฅผ bootJar๋ก ํจํค์ง ํ ํ์๊ฐ ์๋ ์ด์ ๋ database์ ๊ฐ์ด Entity Class๋ฅผ ์ฃผ๋ก ๋ชจ์ ๋์ ํด๋์ค์ธ ๊ฒฝ์ฐ main() ๋ฉ์๋๊ฐ ํ์ํ์ง ์์ต๋๋ค. bootJar ์์ ์ ์คํ ๊ฐ๋ฅํ jar์ ์์ฑํ๋ ค๊ณ ์๋ํ๊ธฐ ์ํด main() ๋ฉ์๋๊ฐ ํ์ํ์ง๋ง, multi-module-database๋ main() ์์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ญํ ์ ํ๋ ๋ชจ๋์ด๊ธฐ ๋๋ฌธ์ false๋ก ๋นํ์ฑํํ๋๋ก ํฉ๋๋ค.
๋ง์ฝ "Could not find method compile() for arguments" ์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์์ธ์ compile, testCompile, runtime, testRuntime์ Gradle 7.0 ๋ฒ์ ์์ ๋ ์ด์ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ฐ๋ผ์ Gradle 7.0 ๋ฒ์ ์ด์์ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด implementation, testImplementation, runtimeOnly, testRuntimeOnly๋ก ๋ณ๊ฒฝํ์ฌ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation project(':multi-module-database')
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
testImplementation 'org.springframework.batch:spring-batch-test'
implementation project(':multi-module-database')
}
multi-module-api ํ๋ก์ ํธ์ multi-module-baatch์ build.gradle ์ค์ ์ ๊ฐ๊ฐ ์์๋๋ก ์์ ๊ฐ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋น๋๋ฅผ ์ํํ๋ฉด ์์ ๊ฐ์ด multi-module-database๊ฐ multi-module-api์ multi-module-batch์ ์ ์์ ์ผ๋ก ์์กด์ฑ์ ๊ฐ์ง๊ณ ์๋ ๊ฑธ ํ์ธํ ์ ์์ต๋๋ค.
ํ ์คํธ ์งํ
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String password;
public User(String email, String password) {
this.email = email;
this.password = password;
}
}
public interface UserRepository extends JpaRepository<User, Long> {
}
๊ทธ๋ฆฌ๊ณ ๊ฐ๋จํ๊ฒ ํ ์คํธ๋ฅผ ์งํํ๊ธฐ ์ํด multi-module-database์ ์ํฐํฐ์ Repository๋ฅผ ์์ ๊ฐ์ด ์ถ๊ฐํ์ต๋๋ค.
@Controller
@RequiredArgsConstructor
public class ApiController {
private static final String EMAIL = "email@gmail.com";
private static final String PASSWORD = "password";
private final UserRepository userRepository;
@PostMapping("/sign-up")
public void signUp() {
userRepository.save(new User(EMAIL, PASSWORD));
}
}
๋ง์ฐฌ๊ฐ์ง๋ก multi-module-api์๋ ์์ ๊ฐ์ด Controller๋ฅผ ๊ตฌ์ฑํ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ multi-module-api ํ๋ก์ ํธ ์คํ ํ API๋ฅผ ํธ์ถํ๋ฉด ์ ์์ ์ผ๋ก ๊ฐ์ด DB์ ์ ์ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
"Parameter 0 of constructor in com.example.multimoduleapi.ApiController required a bean of type 'com.example.multimoduledatabase.UserRepository' that could not be found."
๋ง์ฝ ์์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค๋ฉด multi-module-api์์ multi-module-databse์ Entity์ Repository๋ฅผ Scan ํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
@SpringBootApplication
@EntityScan(basePackages = {"com.example.multimoduledatabase"})
@EnableJpaRepositories(basePackages = {"com.example.multimoduledatabase"})
public class MultiModuleApiApplication {
public static void main(String[] args) {
SpringApplication.run(MultiModuleApiApplication.class, args);
}
}
ํด๊ฒฐ๋ฒ์ ์์ ๊ฐ์ด @EntityScan๊ณผ @EnableJpaRepositories๋ฅผ ์ด์ฉํด basePackages ๊ฐ์ ์์ฑํด ์ฃผ์๋ฉด ๋ฉ๋๋ค.