Gradle을 이용한 멀티 모듈 프로젝트 관리 방법을 소개한다.
사용하는 이유는 프로젝트가 커질 수록 공통으로 사용하는 서버가 많아지는데 (혹은 클래스) 그 클래스들을
전부 복사&붙여넣기를 할 수 없다.
수정이 필요한 경우 전부 수정을 해야하고 실수의 여지가 많다.
하나의 공통 프로젝트를 둔 다음 해당 프로젝트에서 가져다 쓸 수 있도록 할 때 사용한다.
프로젝트 구조
multi-module-root 를 두고 하위에 domain과 sub-web을 두었다.
최상위 multi-module-root를 만든 후 new -> module -> gradle -> java 로 만들면 된다.
settings.gradle
rootProject.name = 'multi-module-root'
include 'sub-web', 'domain'
settings.gradle 파일에 include 된다.
없으면 넣어줘야 한다.
Domain 서브모듈
domain에는 User 엔티티와 repository를 작성해서 넣어준다.
User.java
import lombok.Getter;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter
public class User {
@Id
@GeneratedValue
private Long id;
@Column
private String name;
@Column
private String email;
public User() {
}
public User(String name, String email) {
this.name = name;
this.email = email;
}
}
UserRepository.java
@Repository
public interface UserRepository extends JpaRepository <User, Long> {
void findByName(String name);
}
build.gradle
bootJar { enabled = false}
jar {enabled = true}
dependencies {
}
sub-web 모듈
sub-web모듈에는 domain의 repository를 가져다 쓰는 서비스를 뒀다.
UserService.java
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository repository) {
this.userRepository = repository;
}
public User setUser(User user) {
return userRepository.save(user);
}
public User getUser(Long id) {
return userRepository.getById(id);
}
public List<User> findAll() {
return userRepository.findAll();
}
}
이렇게 작성하면 에러가 난다.
UserRepository를 찾을 수 없기 때문이다.
build.gradle
bootJar { enabled = false}
jar {enabled = true}
dependencies {
implementation project(':domain')
}
dependency로 domain프로젝트를 추가해줘야 한다.
TestCase 작성
UserRepository Test
@ExtendWith(SpringExtension.class)
@DataJpaTest
@EnableJpaRepositories(basePackages = "com.heypli.repo.repository")
@EntityScan(basePackages = "com.heypli.repo.domain")
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void addUser() {
userRepository.save(new User("test", "at@naver.com"));
Optional<User> saved = userRepository.findById(1L);
if(saved.isPresent()) {
assertThat(saved.get().getName(), is("test"));
} else {
System.out.println("test fail...");
}
}
}
Respository테스트를 위해 @DataJpaTest를 사용했다.
서브모듈 안에 있는 클래스이기 때문에 @EnableJpaRepositories로 패키지를 설정했다.
@EntityScan도 필요하다.
테스트를 성공한다.
UserServiceTest
@ExtendWith(MockitoExtension.class)
@SpringBootTest
public class UserServiceTest {
@MockBean
private UserRepository repository;
@InjectMocks
private UserService userService = new UserService(this.repository);
@Test
public void findAllUser() {
User user = new User("Test", "22@naver.com");
// repository에서 리턴할 값 지정
given(repository.findAll()).willReturn(List.of(user));
// when
List<User> list = userService.findAll();
// then
Assertions.assertEquals(1, list.size());
Assertions.assertEquals(user.getName(), list.get(0).getName());
}
@Test
public void addUser() {
User user = new User("Test", "aa@naver.com");
given(repository.save(user)).willReturn(user);
User savedUser = userService.setUser(user);
Assertions.assertNotNull(savedUser);
assertThat(savedUser.getName(), is("Test"));
}
}
UserService는 Repository에 의존하고 있기 때문에 UserRepository를 MockBean으로 생성한다.
UserRepository에서 리턴할 값을 given으로 설정하고 서비스 레이어를 테스트한다.
multi-module-root
build.gradle
plugins {
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.heypli'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation project(':sub-web')
implementation project(':domain')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'junit:junit:4.13.1'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
subprojects {
group 'com.heypli'
version '1.0'
repositories {
mavenCentral()
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
sourceCompatibility = 11
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
implementation 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
}
멀티모듈(root)의 build.gradle이다.
루트에서 서브 모듈에 클래스를 사용하기 위해서는 implementation project로 추가해줘야 한다.
subprojects로 서브 모듈의 디펜던시를 추가해준다.
UserController.java
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/user")
public ResponseEntity<User> createUSer(@RequestBody User user) throws Exception{
User savedUser = userService.setUser(user);
User saved = userService.getUser(savedUser.getId());
return new ResponseEntity<>(saved, HttpStatus.OK);
}
}
서브모듈 호출 테스트
MultiModuleRootApplication.java
@SpringBootApplication
@ComponentScan(basePackages = {"com.heypli"})
@EnableJpaRepositories({"com.heypli.repo.repository"})
@EntityScan({"com.heypli.repo.domain"})
public class MultiModuleRootApplication {
public static void main(String[] args) {
SpringApplication.run(MultiModuleRootApplication.class, args);
}
}
서브 모듈 테스트를 위해 ComponentScan과 EnableJpaRepositories, EntityScan으로 해당 경로의 빈을 스캔한다.
https://github.com/tweety2411/multi-module-root
'Spring' 카테고리의 다른 글
Spring Boot @EnableAutoConfiguration (0) | 2022.02.16 |
---|---|
Spring Boot 외부 설정 @Value, @ConfigurationProperties (0) | 2022.02.16 |
Spring Boot2 Swagger 사용 (0) | 2022.02.08 |
Spring restTemplate Connection pool 사용 (0) | 2022.02.07 |
Spring cloud circuit breaker fallbackUri 사용 (0) | 2022.02.04 |