CompleteableFutures
Mono
ApiGatewayMain
패키지: API 게이트웨이 메인 프로그램orderProxyRouting
빈에 정의되어 있다.
OrderService
로orderProxyRouting
, orderHandlerRouting
빈에 존재
orderHandlers
빈은 API를 조합하는 요청 핸들러@Configuration
@EnableConfigurationProperties(OrderDestinations.class)
public class OrderConfiguration {
@Bean
public RouteLocator orderProxyRouting(RouteLocatorBuilder builder,
OrderDestinations orderDestinations) {
return builder.routes()
.route(r -> r.path("/orders") // 기본적으로 /orders로 시작하는 요청은 모두 orderDestinations.getOrderServiceUrl로 라우팅
.and().method("POST").uri(orderDestinations.getOrderServiceUrl()))
.route(r -> r.path("/orders")
.and().method("PUT").uri(orderDestinations.getOrderServiceURL()))
.route(r -> r.path("/orders/**")
.and().method("POST").uri(orderDestinations.getOrderServiceURL()))
.route(r -> r.path("/orders/**")
.and().method("PUT").uri(orderDestinations.getOrderServiceURL()))
.route(r -> r.path("/orders")
.and().method("GET").uri(orderDestinations.getOrderHistoryServiceUrl()))
.build();
}
@Bean
public RouterFunction<ServiceResponse> orderHandlerRouting(OrderHandlers orderHandlers) {
return RouterFunctions.route(GET("/orders/{orderId}"), orderHandlers::getOrderDetails);
}
@Bean
public OrderHandlers orderHandlers(OrderService orderService,
KitchenService kitchenService,
DeliveryService deliveryService,
AccountingService accountingService) {
return new OrderHandlers(orderService, kitchenService, deliveryService, accountingService);
}
}
OrderDestinations
는 백엔드 서비스 URL의 외부화 구성이 가능한 스프링 구성 프로퍼티 클래스이다.
order.destinations.orderServiceUrl
로 지정 가능하다.@ConfigurationProperties(prefix = "order.destinations")
public class OrderDestinations {
@NotNull
public String orderServiceUrl;
public String getOrderServiceUrl() {
return orderServiceUrl;
}
public void setOrderServiceUrl(String orderServiceUrl) {
this.orderServiceUrl = orderServiceUrl;
}
// ...
}
OrderHandlers
에는 API 조합을 비롯한 사용자 정의 로직이 구현된 핸들러 메서드가 존재
public class OrderHandlers {
private OrderServiceProxy orderService;
private KitchenService kitchenService
private DeliveryService deliveryService;
private AccountintService accountingService;
// 생성자
// API를 조합하여 주문 내역을 조회
// 리액티브 스타일로 네 서비스를 병렬 호출한 결과를 조합
public Mono<ServerResponse> getOrderDetails(ServerRequest serverRequest {
String orderId = serverRequest.pathVariable("orderId");
Mono<OrderInfo> orderInfo = orderService.findOrderById(orderId);
Mono<Optional<TicketInfo>> ticketInfo =
kitchenService
.findTicketByOrderId(orderId)
.map(Optional::of)
.orErrorReturn(Optional.empty());
Mono<Optional<DeliveryInfo>> deliveryInfo =
deliveryService
.findDeliveryByOrderId(orderId)
.map(Optional::of)
.orErrorReturn(Optional.empty());
Mono<Optional<BillInfo>> billInfo =
accountingService
.findBillByOrderId(orderId)
.map(Optional::of)
.orErrorReturn(Optional.empty());
Mono<Tuple4<OrderInfo, Optional<TicketInfo>,
Optional<DeliveryInfo>, Optional<BillInfo>>> combined =
Mono.when(orderInfo, ticketInfo, deliveryInfo, billInfo);
Mono<OrderDetails> orderDetails = combined.map(OrderDetails::makeOrderDetails);
return orderDetails.flatMap(person -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(fromObject(person)))
.onErrorResume(OrderNotFoundException.class,
e -> ServerResponse.notFound().build());
}
}
OrderServiceProxy
는 주문 서비스용 원격 프록시 클래스이다.
WebClient
로 주문 서비스를 호출@Service
public class OrderServiceProxy {
private OrderDestinations orderDestinations;
private WebClient client;
// 생성자
public Mono<OrderInfo> findOrderById(String orderId) {
Mono<ClientResponse> response = client
.get()
.url(orderDestinations.orderServiceUrl + "/orders/{orderId}", orderId)
.exchange(); // 서비스 호출
return response.flatMap(resp ->
switch (resp.statusCode()) {
case OK:
return resp.bodyToMono(OrderInfo.class);
case NOT_FOUND:
return Mono.error(new OrderNotFoundException());
default:
return Mono.error(new RuntimeException("Unkown" + resp.statusCode()));
}
}
Query
라는 객체형을 선언하여 정의Query
객체의 각 필드는 값을 반환하는 함수 개념이다.type Query { # 클라이언트에서 실행 가능한 쿼리를 정의
orders(consumerId : Int!): [Order] # Consumer가 주문한 여러 Order 반환
order(orderId : Int!): Order # 주어진 Order 반환
consumer(consumerId: Int!): Consumer # 주어진 Consumer 반환
}
type Consumer {
id: ID
firtName: String
lastName: String
Orders: [Order] # 한 소비자는 여러 주문이 가능
}
type Order {
orderId: ID
consumerId: Int
consumer: Consumer
restaurant: Restaurant
deliveryInfo: DeliveryInfo
# ...
}
type Restaurant {
id: ID
name: String
# ...
}
type DeliveryInfo {
status: DeliveryStatus
# ...
}
enum DeliveryStatus {
PREPARING
READY_FOR_PICKUP
PICKED_UP
DELIVERED
}
# 단건 조회
query {
consumer(consumerId:1) # 소비자 정보를 조회하는 consumer 쿼리 지정
{ # 반환할 Consumer 필드
firstName
lastName
}
}
query { # 앨리어스로 두 소비자를 구분해서 각각 조회
c1: consumer(consumerId:1) {id, firstName, lastName}
c2: consumer(consumerId:2) {id, firstName, lastName}
}
# 복잡한 쿼리
query {
consumer(consumerId:1) {
id
firstName
lastName
orders {
orderId
restaruant {
id
name
}
deliveryInfo {
estimatedDeliveryTime
name
}
}
}
}
const resolvers = {
Query: {
orders: resolveOrders, # orders 쿼리 리졸버
consumer: resolveConsumer,
order: resolveOrder
},
Order: {
consumer: resolveOrderConsumer, # order.consumer 필드 리졸버
restaurant: resolveOrderRestaurant,
deliveryInfo: resolveOrderDeliveryInfo
}
# ...
}
resolveOrders
같은 최상위 쿼리 필드의 경우 루트 객체 object는 보통 리졸버 함수가 무시한다.order.consumer
필드의 리졸버 함수에는 Order
의 리졸버 함수가 반환한 값이 전달된다.resolveOrders()
function resolveOrders(_, { consumerId }, context) {
return context.orderServiceProxy.findOrders(consumerId);
}
Query
문서의 최상위 쿼리 리졸버 함수를 실행Query
문서에 지정된 필드 하나씩 순회Query
문서의 인수를 리졸버에 전달하여 호출