product 밈ㅊ recommendation -> MongoDB
review -> JPA 사용해 MySQL 데이터베이스에 접속
여전히 이 책은 mac 나는 윈도우~
6장에서는 새 도구 설치는 X
데이터베이스에 수동으로 접근하고자 데이터베이스 실행에 사용한 도커 이미지에 포함된
CLI 도구를 사용한다. ( port : mysql- 3306 , mongoDB - 27017)
(책의 그림이 살짝 잘못된거같음 내 생각엔 product, recommendation, review 밑에 통합계층이 아니라 영속성 계층이 있어야할거같은 느낌 )
MongoDB에 저장된 데이터를 확인
docker-compose exec mongodb mongo product-db --quiet --eval "db.products.find()"
스프링 데이터 외에 자바 빈 매핑 도구 MapStruct
도 사용
MapStruct == 스프링 데이터 엔티티 객체와 API 모델 클래스를 쉽게 상호 변환
각 핵심 마이크로서비스 빌드파일에 의존성을 추가해주고 (mongoDB, mysql, h2 등)
id 필드와 version 필드는 엔티티 클래스에만 추가, api 모델 클래스에는 추가X
version 필드는 낙관적 잠금(optimistic locking)을 구현하고자 사용.
낙관적 잠금 : 스프링 데이터가 동시 업데이트에 의한 겹쳐 쓰기를 확인하고엔티티를 업데이트하고자 사용한다.
==스테일 데이터인지 확인** (업데이트 요청version값 < version필드 값 이면 다른 사용자에 의해 업데이트된 데이터)
ProductEntity 클래스 (MongoDB)
RecommendationEntity 클래스 (MongoDB)
ReviewEntity 클래스 (jpq-mysql)
여기는 몇번 써봐서 패스
스프링 데이터는 리포지토리를 정의하기 위한 기본 클래스 집합을 제공하는데, 여기서 기본 클래스인 CrudRepository와 PagingAndSortingRepository를 사용.
CrudRepository : db에 데이터 생성(c) 저장된 데이터 읽고(r) 업데이트하고(u) 삭제하기(d)위한 표준 메서드 제공
PagingAndSortingRepository :Repository는 CrudRepository 클래스에 페이징 및 정렬 기능을 추가한 클래스.
update 테스트
setupDb 메서드에서 생성 업데이트 -> findById()로 db 에서 엔티티 가져와서 확인
version체크
delete 테스트
setupDb 메서드에서 생성-> 삭제 -> db에 남아있는지 existsById로 체크
read 테스트
setupDb 메서드가 생성한 엔티티 -> findByProductId()로 존재여부 확인 -> assertEqualsProduct() (=로컬 헬퍼 메서드) 이용해 반환된 엔티티가 setupDb 생성 에티티와 같은지 확인
오류 상황의 대체 흐름 확인 테스트 2개
😋6. optimisticlockError
낙관적 잠금 메커니즘이 잘 작동하는지 확인하고자 stale 데이터를 업데이트하는 경우에 오류 처리가 제대로 되는지 확인
동일한 엔티티 2개 가져와서 -> 다른 변수에 저장 -> 그중 하나를 업데이트 (version도 자동으로 증가된다) -> 다른변수 업데이트 시도 OptimisticLockingFailureException 예외가 발생할 것이라고 예상
@Test
public void optimisticLockError() {
// Store the saved entity in two separate entity objects
ReviewEntity entity1 = repository.findById(savedEntity.getId()).get();
ReviewEntity entity2 = repository.findById(savedEntity.getId()).get();
// Update the entity using the first entity object
entity1.setAuthor("a1");
repository.save(entity1);
// Update the entity using the second entity object.
// This should fail since the second entity now holds a old version number, i.e. a Optimistic Lock Error
try {
entity2.setAuthor("a2");
repository.save(entity2);
fail("Expected an OptimisticLockingFailureException");
} catch (OptimisticLockingFailureException e) {}
// Get the updated entity from the database and verify its new sate
ReviewEntity updatedEntity = repository.findById(savedEntity.getId()).get();
assertEquals(1, (int)updatedEntity.getVersion());
assertEquals("a1", updatedEntity.getAuthor());
}
@Test
public void paging() {
repository.deleteAll();
List<ProductEntity> newProducts = rangeClosed(1001, 1010)
.mapToObj(i -> new ProductEntity(i, "name " + i, i))
.collect(Collectors.toList());
repository.saveAll(newProducts);
Pageable nextPage = PageRequest.of(0, 4, ASC, "productId");
nextPage = testNextPage(nextPage, "[1001, 1002, 1003, 1004]", true);
nextPage = testNextPage(nextPage, "[1005, 1006, 1007, 1008]", true);
nextPage = testNextPage(nextPage, "[1009, 1010]", false);
}
테스트 관련 에러가 너무 떠서 원인을 분석하려고 뒤져보는데
각각 프로젝트를 하나씩열어서 하나씩 빌드하는데
이런식으로 root project를 못알아먹어서
https://projobs.tistory.com/111 처럼 setting.gradle에서
include ':api'
include ':util'
를 준 후 해봤지만
더 난리났다.
3장에서도 비슷한 에러를 겪었어서
마찬가지로 root project 인 VECTOR 폴더의 settings.gradle에
include를 이용해 api와 util을 인식시켜줘봤다.
그리고 설정에서 invalidate chache를 통해서 다시 빌드시켜줬다.
여전히 안된다..
(이거는 각각 microservice 기능이 아니라 root project인 vector에서 빌드만 잘 되면 되는거라 일단 넘어감)
이전 장까지 잘 되던 vector 프로젝트 빌드도 안된다 따흐흐흑
에러를 하나씩 수정해가고있다
진짜 많다 에러 ... 🤯😩
도대체 어디서 부터 잘못된건지 모르겠다..^^
일단 시간이 너무 소요돼서 내용확인부터 먼저 했다.
결국
저자의 github 기반의 코드로 깃허브 레포(VECTOR1)를 따로 파서 복사하면서 해봤다.
이 역시도 에러가 많이 났지만 구글링 하면서 몇개몇개 고쳐서 진행
와 드디어 build 성공이 떴다 진짜 너무 감격이다
내용을 이어서 진행한다.. 😅
각각 마이크로서비스하나에 db 연결된걸 확장하면 각 ms가 실제 사용하는게 어떤 db인지 헷갈린다.
해결 : 마이크로서비 스가 시작된 직후에 접속한 데이터베이스의 URL을 기록하는 로그 문을 추가.
아래처럼 상위 서비스 클래스에서 main 메소드에 추가해서 log 출력
String mongodDbHost = ctx.getEnvironment().getProperty("spring.data.mongodb.host");
String mongodDbPort = ctx.getEnvironment().getProperty("spring.data.mongodb.port");
LOG.info("Connected to MongoDb: " + mongodDbHost + ":" + mongodDbPort);
영속성 계층 통해서 db 정보 생성/ 삭제하려면 핵심 서비스 api에 api operation 추가해야함
아래는 create 와 delete 삭제 오퍼레이션의 예시
삭제 오퍼레이션의 구현의 멱등성 유지
즉, 삭제를 여러 번 호출하더라도 같은 결과를 반환해야한다는 것
product 마이크로서비스의 서비스계층인 ProductServiceImpl에 영속성 계층의 리포지토리 클래스와 자바 bean mapper 클래스를 생성자에 주입한다.
그리고 createProduct()구현에서는
save 통해 새 엔티티저장, 예외처리는 테스트에서 만든 DuplicateKeyException만 하고 나머지는 InvalidInputException예외로 변환
( 매퍼 메서드apiToEntity()와 entityToApi()를 사용해 API 모델 클래스와 엔티티 클래스를 상호 변환 한다)
MapStruct로 매퍼 클래스를 선언
product에서 서비스의 매퍼 클래스 ProductMapper
1 :entityToApi() 메서드는 엔티티 객체를 -> API 모델 객체에 매핑
2 : 엔티티 클래스에서는 serviceAddress 필드가 없으므로 ignore
3 : apiToEntity() 메서드는 API 모델 객체를 -> 엔티티 객체에 매핑
4 : API 모델 클래스에 없는 id, version 필드가 필요없으므로 ignore
MapStruct는 비단 이름이 같은 필드의 매핑만 지원하는 것 아니라 이름이 다른 필드의 매핑도 지원
ex. 추천 서비스의 rating, rate 필드 매핑
생성 및 삭제 API 오퍼레이션에 대한 테스트 추가
setupDb()를 @Before 로 테스트에 우선되게 시행 시키고 (deleteAll)
duplicateError() 확인해 예상 오류가 맞는지 확인
delete도 멱등성 확인
헬퍼 메서드들 만들어서 api 전송하고 응답 상태 확인하고자 함
composite 엔티티의 생성 및 삭제 오퍼레이션을 위해 복합 API를 확장하는 방법
기존 엔티티 생성, 삭제, 집계는 비슷하나 스웨거 기반 문서 어노테이션 추가해서 진행한다.
product-composite 엔티티 생성 api operation 추가하고
삭제하는거 추가하고
API 문서를 위한 설명문 application.yml 파일에 yaml 추가한다.
swagger 확인해보면 추가된것 확인 가능
복합 서비스의 생성 및 삭제 API를 구현하기전에-> 핵심 ms api 생성/ 삭제 가능하도록 통합 계층 확장 필요
@Override 이용해서 createProduct() 메소드를 구현해 product ms 호출하는 식으로 코드 구현한다.
HTTP 요청을 보내는 책임은 RestTemplate 객체에 위임하고, 오류 처리는 헬퍼 메서드인 handleHttpClientException에 위임
그리고 나서 복합 서비스 생성 및 삭제를 구현하는데
생성의 경우
ProductAggregate 객체를 상품, 추천, 리뷰 객체로 각각 나눈 다음 유형별로 통합계층의 create 호출한다.
삭제의 경우
각각 유형별 호출이 아니라 통합 계층의 delete 메소드 3개 호출해 삭제한다.
도커를 이용해서 하려면 db 설정을 줘야함
도커 컴포즈가 제어하는 시스템 환경에 MongoDB와 MySQL을 추가하고 도커 컨테이너로 실행될 때도 데이터베이스를 찾을 수 있도록 마이크로서비스에 구성을 추가
데이터베이스 연결가능하도록 product, recommendation, review 의 yml에도 db관련 설정코드를 넣어준다.
도커 컴포즈의 exec 커맨드로 데이터베이스의 CLI 도구를 실행 가능
docker-compose exec mongodb mongo --quiet
exit은 종료
docker-compose exec mysql mysql -uuser -preview-db
으로 mysql cli 들어갈수있고
마찬가지로 exit으로 종료
하면 200 OK가 떠야하는데..? 500이 뜬다 😭😢😢
그래 .. 한방에 될리가 없지 ....
아 docker를 띄울 때 메세지 중에서 3306 포트가 이미 사용중이어서 그렇다고 한다.
Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:3306 -> 0.0.0.0:0: listen tcp 0.0.0.0:3306: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
workbench나 이외의 내 컴퓨터에 있는 프로젝트 때문에 그런것 같다
netstat -ano | findstr :3306
로 사용중인지 확인해주고
mysql을 kill 해주는게 좋을까...? 그래도 한 번 해보자..
관리자모드로 열어서 taskkill /f /pid [pid]
해주기
그리고 다시 docker 실행
docker-compose up -d
헬시한 mysql...
mysql아 ... 건강하게 ... 잘 살아야한다~~~
다시 시도해본다
꺄홋
굿이다..
6장 .. 와 내용 많았고 에러도 많았고 나는 쓰러진다 ...
6장에서는 마이크로서비스 데이터에 영속성을 부여하는 방법을 배웠다. 스프링 데이터 프로젝트를 사용하며, 이의 핵심인 entity, repository를 사용해서 MongoDB 및 MySQL 데이터베이스에 데이터를 저장했다. spring boot의 @DataMongoTest, @DataJpaTest로 영속성 검증 테스트가 가능하다는 것도 봤고, 기존의 데이터 읽기용 API를 업데이트해 데이터베이스에 데이터를 생성하거나 삭제하는 오퍼레이션을 RESTful API에 추가함. 이전과 마찬가지로 docker compose가 관리하는 docker container를 사용해 DB를 실행하고 swagger를 통한 테스트도 해봤다.
책의 마지막엔 동기 API를 사용해 여러 마이크로서비스 엔티티로 구성된 복합 엔티티를 업데이트(=생성/삭제) 할때 관련된 마이크로서비스가 함께 업데이트되지 않아 일관성이 깨지는 경우가 생겨 바람직 하지 못하다고 해서
아.. 그렇구나... 하는 중
-> 7장에서 리액티브 마이크로서비스 필요성과 구축방법으로 설명해준다고 한다
아 대골빡아 🥵
지금 단계에서는 어려운데 진짜 어려운데 뭔가 멋지고 그렇다 ..