Replies: 2 comments 4 replies
-
I/O와 트랜잭션을 분리해라책을 검색하는 서비스 레이어를 살펴보면 다음과 같아요. @Service
@RequiredArgsConstructor
public class SearchBookUseCase {
private final KakaoApiClient kakaoApiClient;
public SearchBookResponse search(String query, String sort, int size, int page, String target) {
return SearchBookResponse.from(kakaoApiClient.searchBooks(query, sort, page, size, target));
}
} 현재는 단순히 요청을 받고, 외부 API를 호출한 후 그 데이터를 가공해서 응답하는 형태로만 구현이 되어있기 때문에 별도의 트랜잭션을 관리할 필요가 없어보입니다. 하지만, 요구사항이 변경되어 검색어 순위를 보고싶다면, 어떻게 구현을 해야할지 생각을 해보겠습니다. (MySQL 기준) @Service
@RequiredArgsConstructor
public class SearchBookUseCase {
private final KakaoApiClient kakaoApiClient;
private final BookCountRepository bookCountRepository;
@Transactional
public SearchBookResponse search(String query, String sort, int size, int page, String target) {
bookCountRepository.findByQuery(query)
.ifPresentOrElse(
bookCount -> bookCountRepository.updateCount(bookCount.getId(), bookCount.getCount() + 1),
() -> bookCountRepository.save(new BookCount(query, 1))
);
return SearchBookResponse.from(kakaoApiClient.searchBooks(query, sort, page, size, target));
}
} 위와 같이 구현할 경우, 불필요하게 트랜잭션을 길게 잡아버릴 수 있는 문제점이 존재하기 때문에 분리하는 것을 고려해보면 좋다고 합니다. 퍼사드 패턴을 활용한 분리퍼사드 패턴을 통해 외부 결합도를 낮춰주는 방식으로 적용할 수 있습니다. SearchCountService.java @Service
@RequiredArgsConstructor
public class SearchCountService {
private final BookCountRepository bookCountRepository;
@Transactional
public void incrementSearchCount(String query) {
bookCountRepository.findByQuery(query)
.ifPresentOrElse(
bookCount -> bookCountRepository.updateCount(bookCount.getId(), bookCount.getCount() + 1),
() -> bookCountRepository.save(new BookCount(query, 1))
);
}
} SearchBookFacade.java @Service
@RequiredArgsConstructor
public class SearchBookFacade {
private final KakaoApiClient kakaoApiClient;
private final SearchCountService searchCountService;
public SearchBookResponse search(String query, String sort, int size, int page, String target) {
// 외부 API 호출 먼저 수행
SearchBookResponse response = SearchBookResponse.from(
kakaoApiClient.searchBooks(query, sort, page, size, target)
);
// DB 작업은 별도로 수행
searchCountService.incrementSearchCount(query);
return response;
}
} 장점
단점:
이벤트를 활용한 분리트랜잭션 완료 후 비동기적으로 처리해도 되는 작업인 경우, 이벤트를 고려해볼 수 있습니다. SearchCountEventListener.java @Component
@RequiredArgsConstructor
public class SearchCountEventListener {
private final SearchCountService searchCountService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleSearchEvent(SearchPerformedEvent event) {
searchCountService.incrementSearchCount(event.getQuery());
}
} SearchBookService.java @Service
@RequiredArgsConstructor
public class SearchBookService {
private final KakaoApiClient kakaoApiClient;
private final ApplicationEventPublisher eventPublisher;
public SearchBookResponse search(String query, String sort, int size, int page, String target) {
SearchBookResponse response = SearchBookResponse.from(
kakaoApiClient.searchBooks(query, sort, page, size, target)
);
// 이벤트 발행
eventPublisher.publishEvent(new SearchPerformedEvent(query));
return response;
}
} 장점
단점
|
Beta Was this translation helpful? Give feedback.
-
타임아웃을 생각해라
OpenFeign은 두 가지 타임아웃 매개변수와 함께 작동을 한다.
즉,
OpenFeign Deafult timeout은 다음과 같습니다.
만약 변경하고 싶은 경우, 다음과 같이 변경할 수 있습니다. application.yml 방식 spring:
cloud:
openfeign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000 Configuration 방식 @Configuration
@EnableFeignClients(basePackages = "com.dnd.sbooky.clients")
class FeignClientConfig {
private static final long CONNECT_TIMEOUT_MILLIS = 5000L;
private static final long READ_TIMEOUT_MILLIS = 5000L;
/**
* Timeout Setting
*/
@Bean
Request.Options feignOptions() {
return new Request.Options(
Duration.ofMillis(CONNECT_TIMEOUT_MILLIS),
Duration.ofMillis(READ_TIMEOUT_MILLIS),
true
);
}
} 보통 연결 타임아웃(Connection Timeout)과 읽기 타임아웃(Read Timeout)의 경우 1 ~ 5초 이내로 설정한다고 합니다. 일반적인 통신의 경우 이 안에 완료되기 때문이라고 하는데 물론, 상황에 따라 달라질 수 있으므로 상황에 알맞게 적용하면 됩니다. 적절한 값은 어떻게 설정할까?아래와 같은 부분들을 생각해봐야 할 것 같다:
로컬 환경과 배포 환경에서의 실제 호출 속도 또한 다를 것이며, 적절한 시간을 찾기 위해서는 실제 타임아웃이 발생하는 빈도와 원인을 파악하는 것이 가장 중요하다고 판단을 했습니다. 이러한 원인을 바탕으로 각각의 값을 적절하게 조절하는 방향이 가장 알맞는 방법인 것으로 보입니다. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
위의 글을 읽어보고 우리 시스템에서 개선할 수 있는 사항이 있는지 하나씩 생각해보면 좋을 것 같아요!
Beta Was this translation helpful? Give feedback.
All reactions