본문 바로가기

정리/유용 기능

Multi Module

  • 레고를 조립하듯 필요한 Module을 조립할 수 있다.
  • N개의 Module조립되어 있는 프로젝트를 Multi Module 프로젝트라고 부른다.
  • Multi Module 프로젝트 구조를 사용하는 이유
    • A 서버와 B 서버에서 동일한 DB Entity가 필요할 경우 중복된 Entity를 Module화 시켜 사용하기 위해 Multi Module 프로젝트를 사용한다.
      * 만일 각 서버마다 Entity를 생성해서 관리한다면, 변경 사항 발생 시 각 서버마다 해당 Entity를 수정해야 하기 때문에 리스크가 늘어난다.

    • Multi Module 구조에서 원하는 Module만 골라서 빌드&배포가 가능하다.

실습

1. Root Project를 생성한다.

  • Root Project를 생성하고 필요 없는 폴더와 파일을 삭제한다.
  • src 폴더와 help.md 파일을 삭제하였다.

2. API Module을 생성한다.

  • Root Project 우클릭 -> New -> Module을 선택한다.
  • Module 정보를 입력하고 Module을 생성한다.

  • api-modulemulti-module-study 안에 생성된 것을 확인할 수 있다.

* common-module도 api-module과 마찬가지로 생성한 후, Server 용 Module이 아니므로 Application.java 파일을 삭제한다

 

3. Root Project - settings.gradle 수정

  • Root Project에 생성한 Module의 정보를 include 한다.

4. api-module과 common-module 의존성 설정

  • api-module이 common-module을 참조하기 때문에 api-module의 build.gradle에 implementation project('common-module')을 추가한다.
  • 이때, implementation project에 기입할 project 명은 Root Project의 settings.gradle에 작성한 Module이름과 같아야 한다.
  • Root Project의 settings.gradle에서 Module을 관리하기 때문에 api-module의 settings.gradle을 제거한다.
  • api-module, common-module build.gradle에 tasks.register(prepareKotlinBuildScripteModel) {} 코드를 추가한다.

5. common-module에 Java 파일 생성 후 확인

@Getter
@AllArgsConstructor
public enum ErrorCode {
    SUCCESS("200", "SUCCESS"),
    UNKOWN_ERROR("-9999", "UNKOWN_ERROR");

    private String code;
    private String message;
}
  • common-module에 공통적으로 사용할 ErrorCode Enum을 생성한다.
@Service
public class TestService {

    public String testSuccess() {
        return ErrorCode.SUCCESS.toString();
    }
}

@RestController
@RequiredArgsConstructor
public class TestController {

    private final TestService testService;

    @GetMapping("/test")
    public String testSuccess() {
        return testService.testSuccess();
    }
}
  • apu-module에 TestController와 TestService를 생성한다.
  • Server를 실행하고 curl localhost:8080/test를 입력하면 common-module에서 생성한 ErrorCode.SUCCESS 값이 반환되는 것을 확인할 수 있다.
  • Module을 참조하면 해당 Module의 Java 파일을 사용할 수 있다.

6. common-module에 Bean 생성 후 확인

@Service
public class CommonService {

    public String commonSuccessResponse() {
        return ErrorCode.SUCCESS.toString();
    }
}
  • common-module에 CommonService Bean을 생성한다.
@RestController
@RequiredArgsConstructor
public class TestController {

    private final TestService testService;
    private final CommonService commonService;

    @GetMapping("/test")
    public String testSuccess() {
        return testService.testSuccess();
    }

    @GetMapping("/common/test")
    public String testCommonSuccessResponse() {
        return commonService.commonSuccessResponse();
    }
}
  • api-module의 TestController에 testCommonSuccessResponse 함수를 생성한다.
  • Server 실행 시, Parameter 1 of constructor in backstudy.apimodule.controller.TestController required a bean of type 'backstudy.commonmodule.service.CommonService' that could not be found. 다음과 같은 에러가 발생하는 것을 확인할 수 있다.

* Component Scan은 @SpringBootApplication이 존재하는 Class의 패키지 하위 내에서 이루어지기 때문에 해당 오류가 발생한다.

** 해결 방법1: ApiModuleApplication Class를 apimodule 패키지 상위로 이동시킨다.

ApiModuleApplication의 패키지 경로: backstudy.apimodule

common-module 내의 파일 경로: backstudy.~~

다음 상황에서 ApiModuleApplication을 상위 패키지로 옮기게 되면 backstudy에 위치하게 되어 common-module 내 파일을 Scan 할 수 있게 된다.

** 해결 방법2: @SpringBootApplication의 Component Scan 범위를 지정한다.

@SpringBootApplication(
        scanBasePackages = {"backstudy.apimodule", "backstudy.commonmodule"}
)


7. 공통 Dependency 관리

plugins {
    id 'java-library'
}

api 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
  • common-module의 build.gradle에 추가한다.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    public Member(String name) {
        this.name = name;
    }
}

public interface MemberRepository extends JpaRepository<Member, Long> {
}
  • common-module에 Member Entity, MemberRepository를 생성한다.
@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberRepository memberRepository;

    @PostMapping("/save-member")
    public Member saveMember() {
        Member member = new Member("test1");
        return memberRepository.save(member);

    }
}
  • api-module에 MebmerController를 생성한다.
  • Server를 실행하면 Entity와 Repository를 Bean 주입받지 못해 발생하는 에러가 발생한다.
@EntityScan({"backstudy.commonmodule.entity"})
@EnableJpaRepositories(basePackages = {"backstudy.commonmodule.entity"})
public class ApiModuleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiModuleApplication.class, args);
    }

}
  • ApiModuleApplication에 위와 같이 Annotation을 붙여주면, 해당 경로에 존재하는 Entity와 Repostiory를 주입받아 Server가 정상적으로 실행된다.
  • localhost:8080/save-member를 호출하면 정상적으로 생성된 member를 응답받을 수 있다.

Project Build

tasks.bootJar { enabled = false }
tasks.jar { enabled = true }
  • common-module의 build.gradle에 추가한다.
  • bootJar를 true 설정하면 . jar 파일을 생성하는데 common-module의 경우 . jar 파일을 생성할 필요가 없어 false로 설정한다.
  • bootJar가 true로 설정된 경우, Main 메서드를 찾아가는데 common-module의 경우 main 메서드를 제거했기 때문에 오류가 발생한다.
  • jar = true 로 설정하면 xxx-plain.jar 파일을 생성하는데, 생성된 파일의 경우 Dependency를 가지지 않고 Class와 Resource만 포함하고 있기 때문에 Server를 실행시킬 수 없다.

 

* ./gradlew clean :api-module:buildNeeded --stacktrace --info --refresh-dependencies -x test 명령어로 프로젝트를 build 할 수 있다.

 

* jar 파일 실행 시, profile 설정은 -Dspring.profiles.active=[profile name] 명령어로 가능하다.