Refactoring: 의존관계 역방향 제거 - takeoff-26/logistics-service GitHub Wiki
MSA에서 아키텍처에서 중요한 점은 서비스가 독립적이고 자율적으로 동작할 수 있도록 설계하는 것이다.
독립적이고 자율적인 부분이 서비스들에 한정되는 것이 아닌 내부적으로도 적용이 된다.
내부적인 계층 간에도 presentation -> application -> domain -> infrastructure의 방향으로 흐르게 되고 이때,
역행해서 다음 계층의 의존성을 가지면 안 되고 도메인은 순수히 다른 계층 및 특정 프레임 워크나 라이브러리에 종속되어선 안 된다. 이는 각 계층이 독립적으로 동작해 결합을 낮추고 응집을 높여 유지보수를 용이하게 하며, 변경에 대한 유연성을 가지게 되는 것이다.
다음으로는 추상화된 상위 인터페이스를 사용해야 하는 부분인 DIP를 통해 의존성을 풀어낸 부분과 DMS로 순환 참조나 의존성을 확인해 보자.
대표적으로 DIP를 통해 의존성을 풀어낸 것으로는 feign client를 사용하는 부분이다. feign client는 다른 서비스, 즉 외부에서 갚을 받아오기 때문에 infrastructure에 존재해야 하며 받아오는 값을 사용할 service인 application에서는 infrastructure의 의존성을 가지게 되면 안 된다.
feign client이 위치하는 곳은 infrastructure에 위치하고 사용은 보통 서비스에서 사용된다. 반환 값을 바로 받게 되면 의존성이 역방향으로 흐르게 된다. 이 부분을 ACL을 통해 개선해보자.
public interface HubClient {
List<GetRouteResponseDto> findByToHubIdAndFromHubId(HubIdsDto hubIdsDto);
List<HubAllListResponseDto> findAllHubs();
}
@FeignClient(name = "hub", url = "http://localhost:19042", configuration = FeignClientConfig.class)
public interface FeignHubClient {
@PostMapping("/api/v1/app/hubs/stopover")
List<GetRouteResponse> findByToHubIdAndFromHubId(@RequestBody HubIds hubIds);
@GetMapping("/api/v1/app/hubs/allHub")
List<GetAllHubs> findAllHubs();
}
DIP를 적용해 상위 인터페이스를 사용하기로 하고 아래 feign Client는 실제 요청을 보낼 클래스이다. 이 둘을 이어주고 의존성이 역행하게 될 때 각 계층에 데이터를 전달할 DTO를 별도로 구성해야 한다. 그럴 때 사용하게 되는 구현체를 확인해 보자.
@Component
@Slf4j
@RequiredArgsConstructor
public class FeignHubClientImpl implements HubClient {
private final FeignHubClient feignHubClient;
@Override
public List<GetRouteResponseDto> findByToHubIdAndFromHubId(HubIdsDto hubIdsDto) {
try {
return feignHubClient.findByToHubIdAndFromHubId(HubIds.from(hubIdsDto))
.stream()
.map(GetRouteResponse::from)
.toList();
} catch (FeignClientException e) {
throw handleFeignException(e);
}
}
@Override
public List<HubAllListResponseDto> findAllHubs() {
try {
return feignHubClient.findAllHubs()
.stream()
.map(GetAllHubs::from)
.toList();
} catch (FeignClientException e) {
throw handleFeignException(e);
}
}
private BusinessException handleFeignException(FeignClientException e) {
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
return HubRouteBusinessException.from(HubRouteErrorCode.INVALID_HUB_ROUTE_REQUEST);
} else if (e.status() == HttpStatus.NOT_FOUND.value()) {
return HubRouteBusinessException.from(HubRouteErrorCode.HUB_ROUTE_NOT_FOUND);
} else {
return HubRouteBusinessException.from(CommonErrorCode.BAD_GATEWAY);
}
}
}
FeignHubClientImpl과 FeignHubClient는 infrastructure에 HubClient는 application에 존재하게 된다.
FeignHubClientImpl는 응답을 받아 왔을 때 상위 계층으로 이동해야 하는 시점에서 DTO로 변환시켜 의존성을 끊어내게 되고 FeignHubClientImpl는 application 계층의 HubClient에 구현체이며, FeignHubClient에 대해서는 실제 동작을 하고 응답을 받기 위해 의존성을 가지고 있다.
이렇게 분리하고 구성하게 되면 의존성 역행은 발생하지 않고 상위 계층에 대한 접근도 안정적이게 된다. 이런 방식을 ACL(부패 방지 계층)이라 한다.
이런 방식으로 의존성을 DTO나 별도의 계층을 구성해 끊어내게 되고 인텔리제이에서 제공하는 DSM으로 의존성에 대해 확인해볼 수 있다.