ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Multi Module
    정리/유용 기능 2022. 11. 13. 20:11
    • 레고를 조립하듯 필요한 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] 명령어로 가능하다.

    댓글

Designed by Tistory.