diff --git a/docs/pr-assets/pr-44/graphql-create-product-pr44.png b/docs/pr-assets/pr-44/graphql-create-product-pr44.png new file mode 100644 index 0000000..71e1b85 Binary files /dev/null and b/docs/pr-assets/pr-44/graphql-create-product-pr44.png differ diff --git a/docs/pr-assets/pr-44/graphql-products-pr44.png b/docs/pr-assets/pr-44/graphql-products-pr44.png new file mode 100644 index 0000000..a46bde3 Binary files /dev/null and b/docs/pr-assets/pr-44/graphql-products-pr44.png differ diff --git a/docs/pr-assets/pr-44/order-swagger-pr44.png b/docs/pr-assets/pr-44/order-swagger-pr44.png new file mode 100644 index 0000000..cfc7d95 Binary files /dev/null and b/docs/pr-assets/pr-44/order-swagger-pr44.png differ diff --git a/docs/pr-assets/pr-44/product-db-pr44.png b/docs/pr-assets/pr-44/product-db-pr44.png new file mode 100644 index 0000000..4f2c9e9 Binary files /dev/null and b/docs/pr-assets/pr-44/product-db-pr44.png differ diff --git a/docs/pr-assets/pr-44/product-swagger-pr44.png b/docs/pr-assets/pr-44/product-swagger-pr44.png new file mode 100644 index 0000000..e83a5cb Binary files /dev/null and b/docs/pr-assets/pr-44/product-swagger-pr44.png differ diff --git a/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/client/BaseWebClient.java b/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/client/BaseWebClient.java index 36f32d1..527eb90 100644 --- a/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/client/BaseWebClient.java +++ b/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/client/BaseWebClient.java @@ -37,6 +37,18 @@ public T getObject(String baseUrl, String uri, Class responseType) { .block(); } + public T post(String baseUrl, String uri, R requestBody, Class responseType) { + return webClientBuilder + .baseUrl(baseUrl) + .build() + .post() + .uri(uri) + .bodyValue(requestBody) + .retrieve() + .bodyToMono(responseType) + .block(); + } + public void delete(String baseUrl, String uri) { webClientBuilder .baseUrl(baseUrl) diff --git a/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/client/ProductServiceClient.java b/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/client/ProductServiceClient.java new file mode 100644 index 0000000..21ec6d0 --- /dev/null +++ b/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/client/ProductServiceClient.java @@ -0,0 +1,32 @@ +package com.pooranjoyb.graphql.gateway.client; + +import com.pooranjoyb.graphql.gateway.core.Constants; +import com.pooranjoyb.graphql.gateway.dto.ProductDto; +import com.pooranjoyb.graphql.gateway.dto.ProductRequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class ProductServiceClient { + + private final BaseWebClient baseWebClient; + + @Value("${product.service.url}") + private String productServiceUrl; + + public List getProducts() { + return baseWebClient.getList(productServiceUrl, Constants.PRODUCT_GET_PRODUCTS, ProductDto.class); + } + + public ProductDto createProduct(ProductRequestDto productRequestDto) { + return baseWebClient.post( + productServiceUrl, + Constants.PRODUCT_CREATE_PRODUCT, + productRequestDto, + ProductDto.class); + } +} diff --git a/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/core/Constants.java b/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/core/Constants.java index b63701e..6a29c64 100644 --- a/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/core/Constants.java +++ b/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/core/Constants.java @@ -7,4 +7,6 @@ public class Constants { public static String ORDER_GET_ORDERS = "/orders"; public static String ORDER_GET_BY_ID = "/orders/{id}"; public static String ORDER_DELETE_BY_ID = "/orders/{id}"; + public static String PRODUCT_GET_PRODUCTS = "/products"; + public static String PRODUCT_CREATE_PRODUCT = "/products"; } diff --git a/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/resolver/ProductResolver.java b/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/resolver/ProductResolver.java new file mode 100644 index 0000000..1b22e36 --- /dev/null +++ b/graphql.gateway/src/main/java/com/pooranjoyb/graphql/gateway/resolver/ProductResolver.java @@ -0,0 +1,29 @@ +package com.pooranjoyb.graphql.gateway.resolver; + +import com.pooranjoyb.graphql.gateway.client.ProductServiceClient; +import com.pooranjoyb.graphql.gateway.dto.ProductDto; +import com.pooranjoyb.graphql.gateway.dto.ProductRequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.stereotype.Controller; + +import java.util.List; + +@Controller +@RequiredArgsConstructor +public class ProductResolver { + + private final ProductServiceClient productServiceClient; + + @QueryMapping + public List getProducts() { + return productServiceClient.getProducts(); + } + + @MutationMapping + public ProductDto createProduct(@Argument("product") ProductRequestDto productRequestDto) { + return productServiceClient.createProduct(productRequestDto); + } +} diff --git a/graphql.gateway/src/main/resources/graphql/schema.graphqls b/graphql.gateway/src/main/resources/graphql/schema.graphqls index 1790910..e7399aa 100644 --- a/graphql.gateway/src/main/resources/graphql/schema.graphqls +++ b/graphql.gateway/src/main/resources/graphql/schema.graphqls @@ -9,8 +9,32 @@ type Order { status: String } +type Product { + name: String, + inStock: Boolean, + image: String, + description: String, + brand: String, + rating: Int +} + +input ProductInput { + name: String!, + stock: Int!, + image: String, + description: String, + brand: String, + rating: Int, + sku: Int! +} + # queries type Query { getOrders: [Order], - createOrder: Order -} \ No newline at end of file + createOrder: Order, + getProducts: [Product] +} + +type Mutation { + createProduct(product: ProductInput!): Product +} diff --git a/graphql.gateway/src/test/java/com/pooranjoyb/graphql/gateway/client/ProductServiceClientTests.java b/graphql.gateway/src/test/java/com/pooranjoyb/graphql/gateway/client/ProductServiceClientTests.java new file mode 100644 index 0000000..7679b14 --- /dev/null +++ b/graphql.gateway/src/test/java/com/pooranjoyb/graphql/gateway/client/ProductServiceClientTests.java @@ -0,0 +1,65 @@ +package com.pooranjoyb.graphql.gateway.client; + +import com.pooranjoyb.graphql.gateway.core.Constants; +import com.pooranjoyb.graphql.gateway.dto.ProductDto; +import com.pooranjoyb.graphql.gateway.dto.ProductRequestDto; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ProductServiceClientTests { + + private final BaseWebClient baseWebClient = mock(BaseWebClient.class); + private final ProductServiceClient productServiceClient = new ProductServiceClient(baseWebClient); + + ProductServiceClientTests() { + ReflectionTestUtils.setField(productServiceClient, "productServiceUrl", "http://product-service"); + } + + @Test + void getProductsCallsProductServiceListEndpoint() { + ProductDto product = ProductDto.builder().name("monitor").inStock(true).build(); + when(baseWebClient.getList( + "http://product-service", + Constants.PRODUCT_GET_PRODUCTS, + ProductDto.class)).thenReturn(List.of(product)); + + List products = productServiceClient.getProducts(); + + assertThat(products).containsExactly(product); + verify(baseWebClient).getList( + "http://product-service", + Constants.PRODUCT_GET_PRODUCTS, + ProductDto.class); + } + + @Test + void createProductPostsProductPayloadToProductService() { + ProductRequestDto request = ProductRequestDto.builder() + .name("monitor") + .stock(10L) + .sku(12345L) + .build(); + ProductDto createdProduct = ProductDto.builder().name("monitor").inStock(true).build(); + when(baseWebClient.post( + "http://product-service", + Constants.PRODUCT_CREATE_PRODUCT, + request, + ProductDto.class)).thenReturn(createdProduct); + + ProductDto response = productServiceClient.createProduct(request); + + assertThat(response).isEqualTo(createdProduct); + verify(baseWebClient).post( + "http://product-service", + Constants.PRODUCT_CREATE_PRODUCT, + request, + ProductDto.class); + } +} diff --git a/graphql.gateway/src/test/java/com/pooranjoyb/graphql/gateway/resolver/ProductResolverTests.java b/graphql.gateway/src/test/java/com/pooranjoyb/graphql/gateway/resolver/ProductResolverTests.java new file mode 100644 index 0000000..e1dfe02 --- /dev/null +++ b/graphql.gateway/src/test/java/com/pooranjoyb/graphql/gateway/resolver/ProductResolverTests.java @@ -0,0 +1,56 @@ +package com.pooranjoyb.graphql.gateway.resolver; + +import com.pooranjoyb.graphql.gateway.client.ProductServiceClient; +import com.pooranjoyb.graphql.gateway.dto.ProductDto; +import com.pooranjoyb.graphql.gateway.dto.ProductRequestDto; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ProductResolverTests { + + private final ProductServiceClient productServiceClient = mock(ProductServiceClient.class); + private final ProductResolver productResolver = new ProductResolver(productServiceClient); + + @Test + void getProductsDelegatesToProductServiceClient() { + ProductDto product = ProductDto.builder() + .name("monitor") + .inStock(true) + .brand("Acme") + .build(); + when(productServiceClient.getProducts()).thenReturn(List.of(product)); + + List products = productResolver.getProducts(); + + assertThat(products).containsExactly(product); + verify(productServiceClient).getProducts(); + } + + @Test + void createProductDelegatesToProductServiceClient() { + ProductRequestDto request = ProductRequestDto.builder() + .name("monitor") + .stock(10L) + .image("monitor.png") + .description("27 inch display") + .brand("Acme") + .rating(5L) + .sku(12345L) + .build(); + ProductDto createdProduct = ProductDto.builder() + .name("monitor") + .inStock(true) + .brand("Acme") + .build(); + when(productServiceClient.createProduct(request)).thenReturn(createdProduct); + + assertThat(productResolver.createProduct(request)).isEqualTo(createdProduct); + verify(productServiceClient).createProduct(request); + } +}