첫 번째 예광탄 쏘기

dasd412·2022년 11월 18일
0

MSA 프로젝트

목록 보기
3/25
post-custom-banner

목표

모놀리식 프로젝트를 MSA로 분할하기 위해서 예광탄 코드를 작성한다.
(예광탄은 실제 목표물을 조준하기 위해서 시험적으로 쏘는 탄알인데 빛의 궤적을 그려준다. - 실용주의 프로그래머 12장 토픽)

세부 목표

  1. 일지 CRUD 중 Save diary에 대해 MSA로 분할해본다.
  2. 스프링 시큐리티, 유틸리티는 아직 신경쓰지 않는다.
  3. 도커로 말아본다.
  4. 분할을 마치면, Post Man으로 테스트한다.
  5. 컨피그 서버를 구성하고 연동한다.
  6. 서비스 디스커버리를 구성하고 연동한다.

대략적인 다이어그램

일지와 관련된 엔티티인 food, diet, diary는 Diary DB에, 작성자 엔티티는 Writer DB에 넣어 본다. Diary Service는 각 엔티티들의 복합키(Writer의 외래키로 사용됨)를 이용해 Writer 엔티티를 알아낼 수 있다.

기존 모놀리식 프로젝트 시퀀스 다이어그램


진행 단계

1. 일지 저장 스켈레톤 코드 만들기

food, diet, diary는 복합키로 묶여 있는 관계이다. 하지만 처음부터 그런 것까지 고려하면 머리 아프다.

최대한 단순하고 작게 만들기 위해 diary 엔티티만 저장하는 서비스 로직을 만들었다. 그리고 postman 테스트용 코드이기 때문에 id도 1L로 하드코딩되어 있다. dto나 엔티티 코드 모두 food, diet,writer와 관련된 코드는 복잡성을 줄이기 위해 다 주석 처리했다.

@Service
public class SaveDiaryService {

    private final DiaryRepository diaryRepository;

    public SaveDiaryService(DiaryRepository diaryRepository) {
        this.diaryRepository = diaryRepository;
    }
    @Transactional
    public Long postDiaryWithEntities(SecurityDiaryPostRequestDTO dto) {
        logger.info("post diary in service logic");

        DiabetesDiary diary = new DiabetesDiary(1L, dto.getFastingPlasmaGlucose(), dto.getRemark());
        diaryRepository.save(diary);

        return diary.getId();
    }
}

어플리케이션을 실행 후 postman으로 위 코드 테스트를 수행했다.

git url

https://github.com/dasd412/DiabetesDiary-MSA/commit/d6766ff855e15ed2775af2ba5fdb749954cd4de6


2. 일지 저장 스켈레톤 코드 도커로 말기

Dockerfile

# stage 1 : openJDK 이미지에서 build라고 명명된 이미지 생성 후 압축 해제
FROM openjdk:11-slim as build

LABEL maintainer="Young Jun Yang <dasd412@naver.com>"

ARG JAR_FILE

COPY ${JAR_FILE} app.jar

# jar 파일 압축 풀기
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf /app.jar)


# stage 2 : 전체 jar 파일 대신, 레이어만 포함된 또 다른 이미지 생성
FROM openjdk:11-slim

VOLUME /tmp

# stage 1에서 build라고 명명된 첫 이미지에서 여러 레이어 복사.
ARG DEPENDENCY=/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app

ENTRYPOINT ["java","-cp","app:app/lib/*","com.dasd412.api.diaryservice.DiaryServiceApplication"]

layerd jar라는 방식을 활용해 도커 파일을 구성했다.
이 방식과 반대되는 방식은 jar 파일 하나를 통째로 빌드하는 것이다.
후자의 경우는 코드 단 한줄에서라도 변경이 있으면 전부 다시 빌드해야 해서 시간 낭비가 많다.

반면, layred jar 방식은 레이어 별로 나눠서 빌드를 진행한다. 그리고 변경이 없는 부분은 캐시를 활용하기 때문에 다시 빌드할 필요가 없다.

pom.xml (dockerfile:build 용 플러그인)

			<plugin>
				<groupId>com.spotify</groupId>
				<artifactId>dockerfile-maven-plugin</artifactId>
				<version>1.4.13</version>
				<configuration>
					<repository>${docker.image.prefix}/${project.artifactId}</repository>
					<tag>${project.version}</tag>
					<buildArgs>
						<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
					</buildArgs>
				</configuration>
				<executions>
					<execution>
						<id>default</id>
						<phase>install</phase>
						<goals>
							<goal>build</goal>
							<goal>push</goal>
						</goals>
					</execution>
				</executions>
			</plugin>

참고하면 좋은 링크

https://mio-java.tistory.com/67
https://ktae23.tistory.com/219

docker-compose.yml

version: '3.7'

services:
 diaryservice:
   image: msa/diary-service:0.0.1-SNAPSHOT
   ports:
     - "8081:8081"
   environment:
     - "SPRING_PROFILES_ACTIVE=dev"
   networks:
     backend:
       aliases:
         - "diaryservice"

networks:
 backend:
   driver: bridge
  1. image에서는 로컬 도커 저장소에서 시작할 대상 이미지를 찾는다. 없다면 도커 허브에서 확인한다. (개선할 사항이 있다면, 하드 코딩된 부분이다. 버전 업할 때마다 0.0.1-SNAPSHOT이라는 부분을 일일이 바꿔야 한다면 자동화가 필요할 것 같다. )
  2. ports를 해석해보면 호스트 8081 포트로 오는 트래픽은 컨테이너 내 8081 포트로 매핑된다는 것이다. (호스트 포트 : 컨테이너 포트 형식)
  3. networks를 사용하면 동일한 네트워크 내 컨테이너 연결을 관리할 수 있다. 다수의 마이크로 서비스를 동일한 컨테이너 내에서 연결 및 실행하려면 필요한 부분이다. 기본적으로 도커 컨테이너는 격리된 환경이기 때문에 다른 컨테이너와 통신할 수 없기 때문이다.

나중에 쿠버네티스를 학습하면 대체될 부분이다.

참고하면 좋은 링크 (도커 네트워크)

https://www.daleseo.com/docker-networks/


3. 컨피그 서버 구성하기

컨피그 서버

스프링 클라우드 컨피그 부트스트랩 클래스

@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

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

}

@EnableConfigServer 를 부착하면, 해당 서비스가 스프링 클라우드 컨피그 서비스로 활성화된다.

컨피그 서비스 용 bootstrap.yml 작성하기

spring:
  application:
    name: config-server
  profiles:
    active: native #git
  cloud:
    config:
      server:
        encrypt:
          enabled: false
        native:
          search-locations: classpath:/config
        git:
          uri: https://github.com/dasd412/DiabetesDiary-config.git
          search-paths: diary-service

server:
  port: 8071

management:
  endpoints:
    web:
      exposure:
        include: "*"

spring.profiles.active를 참고해서 cloud.config.server 의 구성 데이터 위치를 알아낼 수 있다.
위 설정을 예로 들면, 프로파일이 native일 경우 컨피그 서버 어플리케이션 resources 하위 디렉토리에 존재하게 된다.
반면 git으로 할 경우 외부 git repository에 구성 정보들을 얻게 해뒀다.

다이어리 서비스

의존성 추가하기

  <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
  </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

스프링 클라우드 컨피그 서버와 통신하는 데 필요한 의존성들을 다이어리 서비스에 추가해준다.

다이어리 서비스용 bootstrap.yml 작성하기

spring:
  application:
    name: diary-service
  profiles:
    active: dev
  cloud:
    config:
      uri: http://configserver:8071

spring.application.name에선 컨피그 서버가 다이어리 서비스의 구성 정보를 찾을 수 있게 이름을 지정해준다.
spring.application.name에 지정된 이름과 컨피그 서버 내에 있는 다이어리 서비스 구성 정보 파일 이름은 동일해야 한다.

그리고 다이어리 서비스 역시 컨피그 서버의 엔드포인트를 찾을 수 있도록 uri를 지정해준다.

다이어리 서비스에 @RefreshScope 부착하기

@RefreshScope
@EnableJpaAuditing
@SpringBootApplication
public class DiaryServiceApplication {

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

}

컨피그 서버는 프로퍼티가 변경되면 최신 상태로 바로 업데이트한다. 하지만, 다른 마이크로 서비스는 시작할 때만 프로퍼티를 읽기 때문에 컨피그 서버에서 프로퍼티가 변경되었다고 할지라도 자동 적용되지 않는다.

따라서 마이크로 서비스를 중단하지 않고도 프로퍼티를 갱신할 방법이 필요하다. 이 경우 Spring Actuator가 제공하는 @RefreshScope를 부착한 후, /actuator/refresh에 접근해보자. 그러면 서비스를 재시작하지 않고도 프로퍼티가 갱신된다.

참고 링크

https://programmer93.tistory.com/56

docker-compose.yml

version: '3.7'

services:
  configserver:
    image: msa/config-server:0.0.1-SNAPSHOT
    ports:
      - "8071:8071"
    networks:
      backend:
        aliases:
          - "configserver"

  diaryservice:
    image: msa/diary-service:0.0.1-SNAPSHOT
    ports:
      - "8081:8081"
    environment:
      PROFILE: "dev"
      CONFIG_SERVER_URI: "http://configserver:8071"
      CONFIGSERVER_PORT: "8071"
    depends_on:
      - configserver
    networks:
      - backend

networks:
  backend:
    driver: bridge

depends_on을 사용하면 , docker 컨테이너 실행 시 서비스간 순서대로 서비스 실행을 시작한다. 이 경우엔 컨피그 서버를 실행하고 나서 다이어리 서비스가 실행된다.

마주쳤던 오류

  1. Found orphan containers ~~
    docker-compose -p ... up 명령어로 프로젝트 이름을 설정하면 된다. docker images 에서 동일한 이름의 image가 있을 경우 발생한다.

참고 링크

https://stackoverflow.com/questions/50947938/docker-compose-orphan-containers-warning

  1. 다이어리 서비스 매핑 안되는 오류
    분명히 8081 포트로 docker-compose에 있는데도 불구하고 8080 기본 포트로 매핑됬었다. 그 이유는 depends_on을 누락했기 때문이었다. 컨피그 서버가 먼저 시작하고 나서 다이어리 서비스가 시작할 수 있도록 순서를 지정했더니 정상 작동하였다.

profile
SW 마에스트로 14기 (스타트업 재직중)
post-custom-banner

0개의 댓글