Books/스프링으로 하는 마이크로서비스 구축

3. 공조 마이크로서비스 집합 생성

tjddneva 2024. 5. 12. 13:07

기술 요구사항

이 책은 맥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  수동테스트 자동테스트

는 각자 하는 것으로 하자.