본문 바로가기

Spring

Spring Boot 멀티 모듈 프로젝트 관리

728x90
반응형

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

 

GitHub - tweety2411/multi-module-root

Contribute to tweety2411/multi-module-root development by creating an account on GitHub.

github.com

 

728x90
반응형