모놀리식 프로젝트를 MSA로 분할하기 위해서 예광탄 코드
를 작성한다.
(예광탄은 실제 목표물을 조준하기 위해서 시험적으로 쏘는 탄알인데 빛의 궤적을 그려준다. - 실용주의 프로그래머 12장 토픽)
Save diary
에 대해 MSA로 분할해본다.Post Man
으로 테스트한다.일지와 관련된 엔티티인 food, diet, diary는 Diary DB에, 작성자 엔티티는 Writer DB에 넣어 본다. Diary Service는 각 엔티티들의 복합키(Writer의 외래키로 사용됨)를 이용해 Writer 엔티티를 알아낼 수 있다.
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으로 위 코드 테스트를 수행했다.
https://github.com/dasd412/DiabetesDiary-MSA/commit/d6766ff855e15ed2775af2ba5fdb749954cd4de6
# 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
방식은 레이어 별로 나눠서 빌드를 진행한다. 그리고 변경이 없는 부분은 캐시
를 활용하기 때문에 다시 빌드할 필요가 없다.
<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
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
image
에서는 로컬 도커 저장소에서 시작할 대상 이미지를 찾는다. 없다면 도커 허브에서 확인한다. (개선할 사항이 있다면, 하드 코딩된 부분이다. 버전 업할 때마다 0.0.1-SNAPSHOT
이라는 부분을 일일이 바꿔야 한다면 자동화가 필요할 것 같다. )ports
를 해석해보면 호스트 8081 포트로 오는 트래픽은 컨테이너 내 8081 포트로 매핑된다는 것이다. (호스트 포트 : 컨테이너 포트 형식)networks
를 사용하면 동일한 네트워크 내 컨테이너 연결을 관리할 수 있다. 다수의 마이크로 서비스를 동일한 컨테이너 내에서 연결 및 실행하려면 필요한 부분이다. 기본적으로 도커 컨테이너는 격리된 환경
이기 때문에 다른 컨테이너와 통신할 수 없기 때문이다.나중에 쿠버네티스를 학습하면 대체될 부분이다.
https://www.daleseo.com/docker-networks/
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
@EnableConfigServer
를 부착하면, 해당 서비스가 스프링 클라우드 컨피그 서비스로 활성화된다.
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>
스프링 클라우드 컨피그 서버와 통신하는 데 필요한 의존성들을 다이어리 서비스에 추가해준다.
spring:
application:
name: diary-service
profiles:
active: dev
cloud:
config:
uri: http://configserver:8071
spring.application.name
에선 컨피그 서버가 다이어리 서비스의 구성 정보를 찾을 수 있게 이름을 지정해준다.
이 spring.application.name
에 지정된 이름과 컨피그 서버 내에 있는 다이어리 서비스 구성 정보 파일 이름은 동일해야 한다.
그리고 다이어리 서비스 역시 컨피그 서버의 엔드포인트를 찾을 수 있도록 uri를 지정해준다.
@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
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 컨테이너 실행 시 서비스간 순서대로 서비스 실행을 시작한다. 이 경우엔 컨피그 서버를 실행하고 나서 다이어리 서비스가 실행된다.
docker-compose -p ... up
명령어로 프로젝트 이름을 설정하면 된다. docker images 에서 동일한 이름의 image가 있을 경우 발생한다.https://stackoverflow.com/questions/50947938/docker-compose-orphan-containers-warning
depends_on
을 누락했기 때문이었다. 컨피그 서버가 먼저 시작하고 나서 다이어리 서비스가 시작할 수 있도록 순서를 지정했더니 정상 작동하였다.