해당 구조는 분명 scale out시에 서비스 각자의 DB를 가지므로 이 DB에 대해서 동기화라는 부분이 중요하였다. 이를 kafka connect로 해결할수있었지만, 지금 내가 구현하고싶은 서비스가 이정도로 확장성을 고려해야할지 의문이라 선택하지 않았다.
두번째
kafka를 메시지 q로 사용하여, 외부 API조회용과 값을반영하여 DB에 저장하기위해서 DBservice를 통해 DB에 중복체크 및 반영되도록 설계하였다. DBservice가 DB만을 관리하는 서비스로 하고싶었지만, DB를 조회하는 입장에서는 kafka를 통해서 할수가없었으므로 (조회후 응답받아야함.) DB랑 직접 연결하였다. 그럼 DBservice가 그렇게 큰 역할을 하고있지않았다. 또한 외부 택배 API조회가 동기 처리가되어있으므로, 카카오 채널에 대해 응답이 바로 나갈수없었고 기다려야했으므로 선택하지않았다.
세번째(선택)
(선택한 아키텍처) 카카오 채널 요청을 바로 응답해줄수있을뿐만 아니라, 외부 택배 조회 api로 나눠 해당 서비스 필요시 내부 kafka요청을 통해 동작요청이 가능하게하였고, 하나의 DB에 외부택배조회서비스와 조회기능서비스가 같은 DB를 바라보고있으므로, 각 서비스별로 DB에 대해서 동기화해줄필요도없었다. → 이는 DB가 하나이므로 장점이될수있다.
다만, 추후 서비스가 DB가 하나이므로 트랙잭션 처리가 매우 중요해질수있다…그때는 각 서비스별로 DB를 쪼개고 동기화하는 방식인 첫번째 안을 고려할것이다.
구현
멀티 모듈
공통의 관심사를 가진 것을 하나로 묶어놓는 것
common(공통적인) 코드들을 묶어서 놓을수있고, DTO등 많은 부분들을 공유가능하여, 변경사항이 생겻을때 따로 각자 변경할필요없는 장점을 가진다
plugins { id 'org.springframework.boot' version '2.7.3' id 'io.spring.dependency-management' version '1.0.13.RELEASE' id 'java' } //java version sourceCompatibility = '11'
allprojects{ group = 'com.lostcatbox' version = '0.0.1-SNAPSHOT'
// 하위 모든 프로젝트 공통 세팅 subprojects { apply plugin: 'java' apply plugin: 'idea' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management'
//목적 : ConcurrentKafkaListenerContainerFactory클래스를 생성하고 ConsumerFactory인터페이스를 내부 멤버변수에 Set하고 Bean 으로 등록하기 위한 class @EnableKafka @Configuration publicclassKafkaConsumerConfig{ private Environment env;
//kafka로 해당 외부 택배조회 요청을 consumer 역할하는 것 추가 //containerFactory 지정해주기
@KafkaListener(topics = topicName, groupId="test", containerFactory = "kafkaListenerContainerFactory") //containerFactory중요 publicvoidkafkaconsumer(RequestInfo requestInfo){ PostDto postDto = postManager.getpost(requestInfo); //해당 요청에 대해 post 정보 추출 if (!postDto.getPostNumber().isEmpty()){ //응답 문제시 빈 PostDto객체이므로 not_found 리턴
//buufer memory size 지정 props.put(ProducerConfig.BUFFER_MEMORY_CONFIG ,env.getProperty(ProducerConfig.BUFFER_MEMORY_CONFIG));
//key serialize 지정 props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG , StringSerializer.class);
//value serialize 지정 props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG ,JsonSerializer.class);
return props; }
// ProducerFactory를 return하는데 이때 위에서 만들어 놓은 producerConfig()의 설정 정보를 이용해서 ProducerFactory생성자를 호출 @Bean public ProducerFactory<String, RequestInfo> producerFactory(){ returnnew DefaultKafkaProducerFactory<>(producerConfig()); }
//최종적으로 우리가 Controller에서 사용 할 KafkaTemplate을 IOC 컨테이너에 등록하는 일을 하게되고 이때 위에서 만들어 놓은 producerFactory() 를 호출해서 KafkaTemplate생성자 Parameter로 넘겨준다. @Bean public KafkaTemplate<String, RequestInfo> kafkaTemplate(){ returnnew KafkaTemplate<>(producerFactory()); } }
externalpost: container_name: externalpost build: ./externalpost expose: - "8081" command: ["java","-jar","/app.jar"] #jar 파일 실행 depends_on: - postdb networks: - trackingpost_net # 해당 네트워크로 편입 restart: always
servicehome: container_name: servicehome build: ./servicehome ports: - "8080:8080" command: ["java","-jar","/app.jar"] #jar 파일 실행 depends_on: - postdb networks: - trackingpost_net # 해당 네트워크로 편입 restart: always
zookeeper: image: wurstmeister/zookeeper container_name: zookeeper expose: - "2181" networks: - trackingpost_net # 해당 네트워크로 편입 kafka: image: wurstmeister/kafka container_name: kafka expose: - "9092" environment: #환경변수 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_CREATE_TOPICS: "posttopic" KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 #{public ip혹은 hostname}(consumer나 producer에서 접속할 ip혹은 도메인):9092 kafka 브로커를 가리키는 사용 가능 주소로 초기연결시에 클라이언트에 전달되는 메타 데이터 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092 # 리스너 들의 목록이고, 호스트/ip 로 구성한다. 해당 옵션을 사용하지 않으면 모든 인터페이스에서 수신 할 수 있다. 기본값. 0.0.0.0 depends_on: - zookeeper networks: - trackingpost_net # 해당 네트워크로 편입
networks: #네트워크 새로 등록 trackingpost_net: driver: bridge
발생한 오류들
graldew bootJar Fail
docker를 사용하여 서버를 올릴때는 jar파일로 올리기위해 jar로 패키징할려고하니까
문제가 graldew bootJar가 자꾸 fail이 떳다
원인
멀티모듈 환경에서 build를 시도했고, 모듈들의 각 build.gradle에 rootproject가 각자써져있었고, dependencies={} 도 입력되어있었다.
해결
각 build.gradle을 지워줬더니, 이제 root에있는 build.gradle만이 참조되어 build되므로 정상적으로 동작함.
아래와 같이 각 모듈들에 대해 실행해줬고, 그래서 jar가 정상적으로 생성됨(build/libs에 jar생성확인)
./gradlew bootJar -p ./servicehome
현재 전체 환경을 한번에 보기위해 root에다가 build.gradle만 관리하고있었고, 안에 필요한 모든것을 구성해놓음(개별 build.gradle필요없다)
core utils는 서버로 돌릴일없으니 jar =true, bootjar =false로 해놓는게 필수다.
Broker may not be available
1 2 3
externalpost | 2022-09-14 11:46:28.677 WARN 1 --- [ntainer#0-0-C-1] org.apache.kafka.clients.NetworkClient : [Consumer clientId=consumer-test-1, groupId=test] Connection to node 1004 (localhost/127.0.0.1:9092) could not be established. Broker may not be available.
advertised.listeners=PLAINTEXT://0.0.0.0:9092
0.0.0.0:9092는 server의 외부IP와 kafka의 포트번호입니다. 보안을 위하여 0.0.0.0으로 표기하였습니다.