3. 공조 마이크로서비스 집합 생성
기술 요구사항
이 책은 맥OS 에서 진행하는데 나는 Windows11 이라 좀 안타깝다.
도구 설치
- Git
- Java
- Curl
- jq
- 스프링 부트 CLI
현재 스프링 부트 CLI 3.2.5v 가 최신인데 이 버전은 Java 17을 필요로 한다. Java 버전이랑 스프링 부트 CLI 버전이랑 맞추어야 하고, jq 는 Chocolatey 패키지 매니저를 통해 다운받자.(환경변수 설정 잘 해주자)
소스 코드
https://github.com/PacktPublishing/Hands-On-Microservices-with-Spring-Boot-and-Spring-Cloud
에서 다운받자.
마이크로서비스 환경 소개
챕터3 에서 만들 마이크로서비스 구조는 아래와 같다.
Product 서비스 (port: 7001)
Product 서비스는 제품 정보를 관리한다.
- 제품 ID (Product ID)
- 이름 (Name)
- 무게 (Weight)
Review 서비스 (port: 7002)
Review 서비스는 리뷰 정보를 관리한다.
- 제품 ID (Product ID)
- 이름 (Name)
- 무게 (Weight)
Recommendation 서비스 (port: 7003)
Recommendation 서비스는 추천 정보를 관리한다.
- 제품 ID (Product ID)
- 이름 (Name)
- 무게 (Weight)
Product 서비스 (port: 7001)
Product 서비스는 제품 정보를 관리
- 제품 ID (Product ID)
- 이름 (Name)
- 무게 (Weight)
골격 마이크로서비스 생성
스프링 이니셜라이저로 골격 코드 생성
위의 github 링크를 통해 코드를 가져와서, 참고하면서 스프링부트 프로젝트를 만들어보자.
책에보면 다음과 같게 스프링부트 프로젝트를 만든다.
spring init \
--boot-version=2.1.0.RC1 \
--build=gradle \
--java-version=1.8 \
--packaging=jar \
--name=product-service \
--package-name=se.magnus.microservices.core.product \
--groupId=se.magnus.microservices.core.product \
--dependencies=actuator,webflux
--version=1.0.0-SNAPSHOT \
product-service
이걸 보고 아까 위에서 spring boot cli 를 설치했으니 그대로 명령어를 쳐서 만들어도 되고, 아니면 https://start.spring.io/ 에서 만들어도 되는데 링크 Spring Initializr 는 이제 java 8이 없다. 이니셜라이저를 통해 만들거면 17로 만든 다음 나중에 버전을 다운해야한다.
product 서비스, review 서비스, recommendation 서비스, product composite 서비스 총 4개 만들어보자.
만들고 난 후 폴더 구조를
some-temp-folder(나는 'practice' 폴더를 만들었다.)
ㅏmicroservices
ㅏproduct-composite-service
ㅏproduct-service
ㅏrecommendation-service
ㅏreview-service
이렇게 만들어준다.(폴더 구조보면 artifact가 없는데 폴더이름은 이렇게...)
+ 각 프로젝트 내 settings.gradle 을 보면 rootProject.name = 'demo' 이렇게 되어있을텐데 이를 각 프로젝트 최상단 폴더명으로 해준다. 예를들어 product-service 는 'product-service'
Gradle에 멀티 프로젝트 빌드 설정
- practice 폴더 밑에 settings.gradle 파일 생성
- microservice 밑에 아무 폴더에서 gradle 폴더, .gitignore 파일, gradlew 파일, gradlew.bat 파일 복사
- 최상단 폴더에서 한꺼번에 빌드할것이므로 microservice 밑에 위 4개는 삭제
- practice 폴더 밑에서 ./gradlew build 로 빌드 수행, -> 성공
Restful API 추가
api 프로젝트와 util 프로젝트 추가
- API 정의를 배치할 별도의 gradle 프로젝트를 생성한다. 자바 인터페이스를 사용해 RESTful API를 설명하고, 모델 클래스로 API 요청 및 응답에 사용할 데이터를 정의한다.
- 전체 마이크로서비스가 공유하는 헬퍼 클래스를 배치할 util 프로젝트를 만든다.
※ 참고로 이 책에서는 빌드배포를 단순히 유지하고자 api와 util 프로젝트를 멀티 프로젝트 빌드에 포함
(고로, 프로젝트 최상단 폴더인 Practice 폴더의 settings.gradle 에 api, util 추가)
저자의 GITLAB 코드를 참고해 api, uitl 폴더 및 내부 파일들을 만들어보자.
api 프로젝트
api 폴더 내에 build.gradle 파일이다.
아래는 api 폴더 구조이다.
(composite 밑에 있는 파일들은 나중에 만든다..)
util 프로젝트
util 프로젝트에는
- 예외 클래스인 InvalidInputException과 NotFoundException
- 유틸리티 클래스인 ServiceUtil, GlobalControllerExceptionHandler, HttpErrorInfo 가 있다.
- build.gradle은 api 의 것과 동일하다.
API 구현
1. microservices 폴더 밑에 4가지 서비스 build.gradle 파일에 의존성 요소에 api 및 util 프로젝트를 추가
dependencies{
implementation project(':api')
implementation project(':util')
}
2. api 및 util 프로젝트의 스프링 빈을 감지하도록 Application 클래스에 @ComponentScan 어노테이션 추가
@SpringBootApplication
@ComponentScan("se.magnus")
public class ProductServiceApplication{
...
}
2번까지는 공통인듯 싶어 서비스4개에 다 했다.
3. api 폴더의 ProductService 인터페이스를 구현하는 ProductServiceImpl.java 를생성해야하는데 만드는 위치를 잘 확인. 현재는 데이터베이스가 없으므로 하드코딩한 응답을 반환
4. product-service 내 resource 폴더안에 applcation.properties 를 지우고 yml 파일로 만들고 안에 포트 번호 입력
(+서비스 4개에 다 추가해주자: test contextLoads()에러)
server.port: 7001
server.error.include-message: always
logging:
level:
root: INFO
se.magnus.microservices: DEBUG
5. 최상단 practice 폴더에서 빌드 실행
./gradlew build
6. 빌드되면 product-service 의 jar 실행
java -jar microservices/product-service/build/libs/product-service-0.0.1-SNAPSHOT.jar
또는 백그라운드에서 실행
java -jar microservices/product-service/build/libs/product-service-0.0.1-SNAPSHOT.jar &
- 스프링부트 2.5.0 부터 build/libs 폴더에 SNAPSHOT.jar, SNAPSHOT-plain.jar 두개가 생성되므로 책처럼 *.jar 해서 실행하면 안된다)
- SNAPSHOT-plain.jar 안생기게 하려면 build.gradle 에 아래 추가
jar {
enable = false
}
7. 실행하고 나서 웹페이지에서 localhost:7001/product/123 들어가거나
터미널에서 curl http://localhost:7001/product/123
입력하면 결과가 잘 반환되는것을 볼 수 있다.
복합 마이크로서비스 추가
product-composite-service 를 추가하는 것이다.
복합 서비스의 구현은
핵심 서비스로의 발신 요청을 처리하는 1. 통합 컴포넌트와
2. 복합 서비스 자체 구현의 두 부분으로 나뉜다.
이렇게 나누어야 나중에 단위 테스트와 통합 테스트를 간편하게 자동화하고 통합 컴포넌트를 모의 객체로 대체해 서비스 구현을 개별적으로 테스트할 수 있다!
API 클래스
위 사진과 같은 API 클래스가 있다. 참고해보자.
속성
위에서 microservices/product-composite-service 밑에 resource 밑에 application.yaml 만든거를 수정
나중에 서비스 검색 메커니즘으로 대체된다고 책에 그러더라.
1. 통합 컴포넌트
microservices/product-composite-service/java/se/magnus/microservices/composite/product
밑에 services 패키지(폴더)를 만들어주고 그 안에 통합 컴포넌트인
ProductCompositeIntegration.java 를 만들자!
이 클래스는 @Component annotation을 사용해 스프링 빈으로 선언돼 있으며, 세 가지 핵심 서비스의 API 인터페이스를 구현하고 있다.
통합 컴포넌트는 스프링 프레임워크에서 제공하는 헬퍼 클래스인 RestTemplate.java 를 사용해 핵심 마이크로서비스에 HTTP 요청을 보낸다. 먼저 RestTemplate 을 구성한 후에 통합 컴포넌트에 주입해야 한다.
기본 Application Class인 ProductCompositeServiceApplication.java에 다음 코드를 추가하자.
이제 이걸로 통합 컴포넌트인 ProductCompositeIntegration.java 의 생성자를 만들어보면,
이렇게 된다!
이제 아까 틀만 만들어놓은 getProduct, getRecommendations, getReviews 함수를 제대로 만들어주면 된다. 나중에 로그 남기기 시작하면 또 바뀐다ㅠ
2. 복합 API 구현
통합컴포넌트와 같은 위치에 ProductCompositeServiceImpl.java를 만들자.
이거는 그냥 코드를 참고하자.
package se.magnus.microservices.composite.product.services;
import org.springframework.beans.factory.annotation.Autowired;
import se.magnus.api.composite.product.*;
import se.magnus.api.core.product.Product;
import se.magnus.api.core.recommendation.Recommendation;
import se.magnus.api.core.review.Review;
import se.magnus.util.http.ServiceUtil;
import java.util.List;
import java.util.stream.Collectors;
public class ProductCompositeServiceImpl implements ProductCompositeService {
private final ServiceUtil serviceUtil;
private ProductCompositeIntegration integration;
@Autowired
public ProductCompositeServiceImpl(ServiceUtil serviceUtil, ProductCompositeIntegration integration) {
this.serviceUtil = serviceUtil;
this.integration = integration;
}
@Override
public ProductAggregate getProduct(int productId) {
Product product = integration.getProduct(productId);
List<Recommendation> recommendations = integration.getRecommendations(productId);
List<Review> reviews = integration.getReviews(productId);
return createProductAggregate(product, recommendations, reviews, serviceUtil.getServiceAddress());
}
private ProductAggregate createProductAggregate(Product product, List<Recommendation> recommendations, List<Review> reviews, String serviceAddress) {
// 1. Setup product info
int productId = product.getProductId();
String name = product.getName();
int weight = product.getWeight();
// 2. Copy summary recommendation info, if available
List<RecommendationSummary> recommendationSummaries = (recommendations == null) ? null :
recommendations.stream()
.map(r -> new RecommendationSummary(r.getRecommendationId(), r.getAuthor(), r.getRate()))
.collect(Collectors.toList());
// 3. Copy summary review info, if available
List<ReviewSummary> reviewSummaries = (reviews == null) ? null :
reviews.stream()
.map(r -> new ReviewSummary(r.getReviewId(), r.getAuthor(), r.getSubject()))
.collect(Collectors.toList());
// 4. Create info regarding the involved microservices addresses
String productAddress = product.getServiceAddress();
String reviewAddress = (reviews != null && reviews.size() > 0) ? reviews.get(0).getServiceAddress() : "";
String recommendationAddress = (recommendations != null && recommendations.size() > 0) ? recommendations.get(0).getServiceAddress() : "";
ServiceAddresses serviceAddresses = new ServiceAddresses(serviceAddress, productAddress, reviewAddress, recommendationAddress);
return new ProductAggregate(productId, name, weight, recommendationSummaries, reviewSummaries, serviceAddresses);
}
}
예외처리 추가
예외처리는 util 프로젝트에 exceptions, http 패키지에 들어있다.
비즈니스 로직과 예외처리방식을 분리하는 것이 나중에 테스트를 위해 중요한데 이 책은 비즈니스 로직 계층이 생략되어 있고 @RestController 컴포넌트에 직접 구현했다.(뭔가 내가 알던 폴더구조랑 많이 다르더라)
예외처리는 GlobalControllerExceptionHandler.java 를 참고한다.
API 구현의 예외처리
API 구현에서는 util 프로젝트의 예외를 사용해 오류 발생을 알린다.
Product-service 프로젝트의 ProductServiceImpl.java 에 예외처리 사항을 추가하자.
API 클라이언트의 예외처리
API 클라이언트인 통합 컴포넌트(ProductCompositeIntegration.java)에도 예외 처리 로직을 추가하여 수정해주자..
API 수동테스트 자동테스트
는 각자 하는 것으로 하자.