-
Spring Multi Module 구현[공부] 프로그래밍/Spring・Spring Boot (JAVA) 2024. 5. 16. 16:22
♧ 전체 코드 : https://github.com/woodisco/multi-moduleGitHub - woodisco/multi-module
Contribute to woodisco/multi-module development by creating an account on GitHub.
github.com
◇ Git Flow 전략
git을 사용하는 개발 환경에서 branch간 문제없이 배포를 안전적으로 할 수 있도록 branch를 관리하는 전략이다.
・ main(master) : 실제 운영 환경에 있는 코드 브랜치
・ dev : main 브랜치를 베이스로 생성한 브랜치, 다음 배포에 나갈 코드 브랜치
・ feature : 각 개발 브랜치, dev 브랜치에 머지
・ release : dev 브랜치를 베이스로 생성된 배포 브랜치, 모든 작업 완료 후에 main에 머지
・ hotfix : 장애발생 시 main 브랜치를 베이스로 생성한 브랜치
Spring Multi Module 개념
필요한 기능별로 module을 생성하여 조립하며 N개의 module이 조립되어 있는 프로젝트를 multi module 프로젝트라고 부른다.
exception 핸들링 :
프레임워크에서 발생한 exception은 반드시 커스텀하게 wrapping하여 처리한다.
@RestControllerAdvice 어노테이션을 사용하여 모든 예외를 해당 클래스에서 클라이언트와 사전에 정의한 값으로 재정의 한다.
프로젝트 생성 & 모듈 생성
① multimodule 프로젝트 생성
② multimodule 프로젝트에서 module-api 모듈 생성
③ multimodule 프로젝트에서 module-common 모듈 생성
④ settings.gradle 수정
=> 추가한 모듈 설정해주기
rootProject.name = 'multi-module' include 'module-api' include 'module-common'
⑤ module-api의 build.gradle 수정
=> module-api에서 module-common을 사용하기 때문에 설정 추가해주기
implementation project(':module-common')
module-api에서 module-common을 참조하는지 테스트
: Java 객체 참조 테스트① module-common에 enum 작성
② module-api에서 DemoController, DemoService를 작성
③ curl 명령어로 테스트
=> curl localhost:8080/save
▷ curl 명령어
curl은 "Client URL"의 약자로, 명령줄에서 HTTP 요청을 수행할 수 있는 오픈 소스 도구입니다. 주로 웹 서버와 상호 작용할 때 사용되며, 다양한 프로토콜을 지원합니다. curl을 사용하면 웹 페이지를 가져오거나, API에 요청을 보내는 등의 작업을 쉽게 할 수 있습니다.
module-api에서 module-common을 참조하는지 테스트
: Spring Bean 참조 테스트① module-common에서 CommonDemoService 작성
② module-api/DemController에 CommonDemoService 추가
③ CommonDemoService의 Bean이 생성되지 않는 에러 발생
♤ 해결방법 2가지
1. 실행시키는 패키지 기준으로 컨포넌트 스캔을 하게 되는데 이때 하위 모듈에도 똑같은 패스로 컴포넌트 스캔이 이루어지게 된다. module-commo과 module-api에는 dev-de까지 폴더가 동일하고 module-commo에는 moduleapi라는 폴더가 없기 때문에 Bean을 주입받지 못하게 된다. 그렇기 때문에 module-api의 ModuleApiApplication의 위치가 중요해지는데 dev-de의 폴더로 이동을 시키게 되면 CommonDemoService의 Bean을 주입받을 수 있게 된다.
2. ModuleApiApplication에 지정해주기
@SpringBootApplication( scanBasePackages = { "dev.be.moduleapi", "dev.be.modulecommon" } )
Exception 핸들링
: module-api① CommonResponse 작성
② CustomException 작성
③ DemoController, DemoService에 메소드 추가
=> CustomException을 발생시키기
public String exception() { if (true) { throw new CustomException(CodeEnum.UNKNOWN_ERROR); } return "exception"; }
④ GlobalExceptionHandler 작성
@RestControllerAdvice
전역적으로 RESTful 웹 애플리케이션에서 발생할 수 있는 예외를 처리하는 클래스에 적용됩니다. 이 애노테이션을 사용하면 예외 처리를 담당하는 클래스를 정의할 때 특정 컨트롤러나 패키지에 제한되지 않고 전역적으로 적용할 수 있습니다.
@RestControllerAdvice 애노테이션이 적용된 클래스는 다음과 같은 기능을 수행할 수 있습니다:
・ 전역 예외 처리: 모든 컨트롤러에서 발생하는 예외를 처리할 수 있습니다.
・ 특정 예외 타입에 대한 처리: 특정 예외 타입에 대한 처리 로직을 정의할 수 있습니다.
・ 특정 컨트롤러나 패키지에 대한 예외 처리: basePackages 또는 basePackageClasses 속성을 사용하여 특정 패키지나 컨트롤러에서만 발생하는 예외를 처리할 수 있습니다.
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(CustomException.class) public CommonResponse handlerCustomException(CustomException e) { return CommonResponse.builder() .resultCode(e.getReturnCode()) .resultMessage(e.getReturnMessage()) .build(); } @ExceptionHandler(Exception.class) public CommonResponse handlerException(Exception e) { return CommonResponse.builder() .resultCode(CodeEnum.UNKNOWN_ERROR.getCode()) .resultMessage(CodeEnum.UNKNOWN_ERROR.getMessage()) .build(); } }
예외가 발생한 경우, CustomException으로 한번 매핑을 해준뒤에 throw가 던져지고 그 throw는 @RestControllerAdvice에 의해 잡히게 된다. 그 다음 @ExceptionHandler에 선언 해놓은 클래스가 있을 경우에 해당 메소드가 실행된다.
DB 연동
① module-common에 docker-compose.yml 작성
# Docker Compose의 버전 정보 version: '3.8' services: mysql: # 서비스의 이름 container_name: multimodule_local image: mysql:latest volumes: - ./db/conf.d:/etc/mysql/conf.d ports: - "3306:3306" environment: # 환경 변수 - MYSQL_DATABASE=multimodule_local - MYSQL_USER= - MYSQL_PASSWORD= - MYSQL_ROOT_PASSWORD= - TZ=Asia/Tokyo
② module-common/db/conf.d 패키지를 생성한 뒤, my.cnf 작성
# MySQL8 default character set는 utf8mb4 이므로 client만 선언합니다. [client] default-character-set = utf8mb4 # MySQL8 default authentication policy는 `caching_sha2_password`입니다. # 이를 지원하지 않는 DB Client로 접속하기 위해서는 기존 정책인 `mysql_native_password`로 설정합니다. [mysqld] authentication-policy = mysql_native_password
③ module-api/resources/application.yml 작성
server: port: 8080 spring: application: name: multi-module datasource: url: jdbc:mysql://localhost:3306/{{컨테이너이름}}?zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8&serverTimezone=Asia/Tokyo username: password: driver-class-name: com.mysql.cj.jdbc.Driver
④ 도커 파일 실행하기
docker-compose up -d --build --force-recreate
⑤ DB연동 테스트를 위해 module-api/DemoService 수정
public String save() { Member newMember = memberRepository.save(Member.builder() .name(Thread.currentThread().getName()) .build()); return "save"; } public String find() { return String.valueOf(memberRepository.findAll().size()); }
⑥ MemberRepository의 Bean 주입 에러 발생
=> ModuleApiApplication에 지정해주기
@EntityScan("dev.be.modulecommon.domain") @EnableJpaRepositories(basePackages = "dev.be.modulecommon.repositories")
⑦ 현시점 실제 DB에 Member 엔티티가 존재하지 않으므로 module-api/resources/application.yml에 아래를 추가
=> ddl-auto: create : 실제 DB에 생성해주는 것을 로그에서 확인 가능
jpa: database: mysql database-platform: org.hibernate.dialect.MySQL5InnoDBDialect hibernate: ddl-auto: create
테이블이 만들어진것을 확인 가능
⑧ curl localhost:8080/save or find 를 통해 DB연동 테스트하기
빌드 및 배포
◇ gradle로 빌드를 해야하는 이유
로컬에서 개발을 하는 경우는 인텔리제이의 도움을 받아 서버를 띄울 수 있다. 하지만 실제 운영 서버의 경우에는 인텔리제이의 도움을 받을 수 없고 jar파일을 넘겨서 실행을 시켜야 한다. 그래서 gradle이나 maven과 같은 빌드 풀을 사용하여 jar파일을 만들어야 한다. 만들어진 jar파일을 운영 서버로 넘겨 실행시켜야 한다.① module-common/build.gradle에 추가
tasks.bootJar { enabled = false } tasks.jar { enabled = true }
bootJar의 기본값은 true이며 true로 설정할 경우 **.jar라는 파일이 형성되게 된다. module-common의 경우는 다른 모듈에서 참조하는 목적의 모듈이기 때문에 실행 가능한 jar파일을 만들 필요는 없다. 만약 true로 설정할 경우 메인 클래스를 찾게 되는데 module-common는 module-api처럼 메인 클래스가 없기에 에러가 발생하게 된다.
jar의 기본값도 true이며 true로 설정할 경우 **-plain.jar라는 파일이 형성되게 된다. **.jar와의 차이점으로는 dependency를 갖고 있지 않게 된다. 즉 클래스와 리소스만 포함하고 있어서 서버를 실행 시킬 수 없다.② 실행 : ./gradlew clean :module-api:buildNeeded --stacktrace --info --refresh-dependencies -x test
[Gradle 빌드 명령어 + jar 파일 실행 CLI] - Gradle 빌드 명령어 :: root project -> ./gradlew clean :module-api:buildNeeded --stacktrace --info --refresh-dependencies -x test
・ --info :
debug -> info -> warn -> error 이 단계로 info를 설정할 경우, info -> warn -> error 전부 출력을 하겠다는 의미
・ -x test :
테스트 코드는 스킵jar파일 생선 완료
③ jar파일로 실제 서버 구동・jar파일로 이동하기- profile 지정 X -> java -jar module-api-0.0.1-SNAPSHOT.jar - profile 지정 O -> java -jar -Dspring.profiles.active=local module-api-0.0.1-SNAPSHOT.jar
・실행 : java -jar module-api-0.0.1-SNAPSHOT.jar
④ ./gradlew clean 실행 시 build 폴더가 삭제
profile 설정 - 1
환경별로 설정해야하는 profile이 다르기 때문에 N개의 profile설정이 필요하다
① yml 파일 이름 변경 및 수정
- application-local.yml
- application-beta.yml
profile-name: "local"
② module-api/DemoService에서 테스트
@Value("${profile-name}") private String name;
③ 해당 에러 발생
Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
=> 스프링을 실행하면 profile이 디폴트로 설정되어 application-local.yml을 읽어 들이지 못하고 있다.
해결방법은 Active profiles를 수정하기
Add VM option을 선택 후, 아래와 같이 추가하기
-Dspring.profiles.active=local
profile 설정 - 2
실제 운영 서버에서는 jar파일을 사용해서 서버를 돌리기 때문에 java의 jvm 옵션을 통해 profile 값을 설정
① 파일을 수정했기 때문에 빌드 및 배포 다시 하기
java -jar -Dspring.profiles.active=local module-api-0.0.1-SNAPSHOT.jar java -jar -Dspring.profiles.active=beta module-api-0.0.1-SNAPSHOT.jar
출처 : 패스트캠퍼스 10개 프로젝트로 완성하는 백엔드 웹개발(Java/Spring) 초격차 패키지 Online
'[공부] 프로그래밍 > Spring・Spring Boot (JAVA)' 카테고리의 다른 글
Feign Client 구현 - 1 (0) 2024.05.21 비동기 프로그래밍 구현 (0) 2024.05.20 Spring Batch 구현 - 7 (0) 2024.05.14 Spring Batch 구현 - 6 (0) 2024.05.09 Spring Batch 구현 - 5 (0) 2024.05.08