diff --git a/ddd/pom.xml b/ddd/pom.xml index 508bccdbf2..9a0523a4c6 100644 --- a/ddd/pom.xml +++ b/ddd/pom.xml @@ -76,6 +76,11 @@ spring-boot-starter-test test + + org.mockito + mockito-core + test + de.flapdoodle.embed de.flapdoodle.embed.mongo diff --git a/ddd/src/main/java/com/baeldung/ddd/PersistingDddAggregatesApplication.java b/ddd/src/main/java/com/baeldung/ddd/PersistingDddAggregatesApplication.java index cd9be34278..3a52fd0440 100644 --- a/ddd/src/main/java/com/baeldung/ddd/PersistingDddAggregatesApplication.java +++ b/ddd/src/main/java/com/baeldung/ddd/PersistingDddAggregatesApplication.java @@ -1,12 +1,12 @@ -package com.baeldung.ddd; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class PersistingDddAggregatesApplication { - - public static void main(String[] args) { - SpringApplication.run(PersistingDddAggregatesApplication.class, args); - } -} +package com.baeldung.ddd; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "com.baeldung.ddd.order") +public class PersistingDddAggregatesApplication { + + public static void main(String[] args) { + SpringApplication.run(PersistingDddAggregatesApplication.class, args); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/DomainLayerApplication.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/DomainLayerApplication.java new file mode 100644 index 0000000000..988f96042b --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/DomainLayerApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.dddhexagonalspring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; + +@SpringBootApplication +@PropertySource(value = { "classpath:ddd-layers.properties" }) +public class DomainLayerApplication { + public static void main(final String[] args) { + SpringApplication.run(DomainLayerApplication.class, args); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/controller/OrderController.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/controller/OrderController.java new file mode 100644 index 0000000000..80ba36d01b --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/controller/OrderController.java @@ -0,0 +1,45 @@ +package com.baeldung.dddhexagonalspring.application.controller; + +import com.baeldung.dddhexagonalspring.application.request.AddProductRequest; +import com.baeldung.dddhexagonalspring.application.request.CreateOrderRequest; +import com.baeldung.dddhexagonalspring.application.response.CreateOrderResponse; +import com.baeldung.dddhexagonalspring.domain.service.OrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +@RestController +@RequestMapping("/orders") +public class OrderController { + + private final OrderService orderService; + + @Autowired + public OrderController(OrderService orderService) { + this.orderService = orderService; + } + + @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) + CreateOrderResponse createOrder(@RequestBody final CreateOrderRequest createOrderRequest) { + final UUID id = orderService.createOrder(createOrderRequest.getProduct()); + + return new CreateOrderResponse(id); + } + + @PostMapping(value = "/{id}/products", consumes = MediaType.APPLICATION_JSON_VALUE) + void addProduct(@PathVariable final UUID id, @RequestBody final AddProductRequest addProductRequest) { + orderService.addProduct(id, addProductRequest.getProduct()); + } + + @DeleteMapping(value = "/{id}/products", consumes = MediaType.APPLICATION_JSON_VALUE) + void deleteProduct(@PathVariable final UUID id, @RequestParam final UUID productId) { + orderService.deleteProduct(id, productId); + } + + @PostMapping("/{id}/complete") + void completeOrder(@PathVariable final UUID id) { + orderService.completeOrder(id); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/request/AddProductRequest.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/request/AddProductRequest.java new file mode 100644 index 0000000000..ec107d635b --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/request/AddProductRequest.java @@ -0,0 +1,20 @@ +package com.baeldung.dddhexagonalspring.application.request; + +import com.baeldung.dddhexagonalspring.domain.Product; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.validation.constraints.NotNull; + +public class AddProductRequest { + @NotNull private Product product; + + @JsonCreator + public AddProductRequest(@JsonProperty("product") final Product product) { + this.product = product; + } + + public Product getProduct() { + return product; + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/request/CreateOrderRequest.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/request/CreateOrderRequest.java new file mode 100644 index 0000000000..8c51fbe479 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/request/CreateOrderRequest.java @@ -0,0 +1,20 @@ +package com.baeldung.dddhexagonalspring.application.request; + +import com.baeldung.dddhexagonalspring.domain.Product; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.validation.constraints.NotNull; + +public class CreateOrderRequest { + @NotNull private Product product; + + @JsonCreator + public CreateOrderRequest(@JsonProperty("product") @NotNull final Product product) { + this.product = product; + } + + public Product getProduct() { + return product; + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/response/CreateOrderResponse.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/response/CreateOrderResponse.java new file mode 100644 index 0000000000..72ee1134c3 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/application/response/CreateOrderResponse.java @@ -0,0 +1,15 @@ +package com.baeldung.dddhexagonalspring.application.response; + +import java.util.UUID; + +public class CreateOrderResponse { + private final UUID id; + + public CreateOrderResponse(final UUID id) { + this.id = id; + } + + public UUID getId() { + return id; + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/DomainException.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/DomainException.java new file mode 100644 index 0000000000..7baef7bab6 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/DomainException.java @@ -0,0 +1,7 @@ +package com.baeldung.dddhexagonalspring.domain; + +class DomainException extends RuntimeException { + DomainException(final String message) { + super(message); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/Order.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/Order.java new file mode 100644 index 0000000000..7d40007411 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/Order.java @@ -0,0 +1,82 @@ +package com.baeldung.dddhexagonalspring.domain; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class Order { + private UUID id; + private OrderStatus status; + private List orderItems; + private BigDecimal price; + + public Order(final UUID id, final Product product) { + this.id = id; + this.orderItems = new ArrayList<>(Collections.singletonList(new OrderItem(product))); + this.status = OrderStatus.CREATED; + this.price = product.getPrice(); + } + + public void complete() { + validateState(); + this.status = OrderStatus.COMPLETED; + } + + public void addOrder(final Product product) { + validateState(); + validateProduct(product); + orderItems.add(new OrderItem(product)); + price = price.add(product.getPrice()); + } + + public void removeOrder(final UUID id) { + validateState(); + final OrderItem orderItem = getOrderItem(id); + orderItems.remove(orderItem); + + price = price.subtract(orderItem.getPrice()); + } + + private OrderItem getOrderItem(final UUID id) { + return orderItems + .stream() + .filter(orderItem -> orderItem + .getProductId() + .equals(id)) + .findFirst() + .orElseThrow(() -> new DomainException("Product with " + id + " doesn't exist.")); + } + + private void validateState() { + if (OrderStatus.COMPLETED.equals(status)) { + throw new DomainException("The order is in completed state."); + } + } + + private void validateProduct(final Product product) { + if (product == null) { + throw new DomainException("The product cannot be null."); + } + } + + public UUID getId() { + return id; + } + + public OrderStatus getStatus() { + return status; + } + + public BigDecimal getPrice() { + return price; + } + + public List getOrderItems() { + return Collections.unmodifiableList(orderItems); + } + + private Order() { + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/OrderItem.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/OrderItem.java new file mode 100644 index 0000000000..9debb02680 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/OrderItem.java @@ -0,0 +1,39 @@ +package com.baeldung.dddhexagonalspring.domain; + +import java.math.BigDecimal; +import java.util.Objects; +import java.util.UUID; + +public class OrderItem { + private UUID productId; + private BigDecimal price; + + public OrderItem(final Product product) { + this.productId = product.getId(); + this.price = product.getPrice(); + } + + public UUID getProductId() { + return productId; + } + + public BigDecimal getPrice() { + return price; + } + + private OrderItem() { + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrderItem orderItem = (OrderItem) o; + return Objects.equals(productId, orderItem.productId) && Objects.equals(price, orderItem.price); + } + + @Override + public int hashCode() { + return Objects.hash(productId, price); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/OrderStatus.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/OrderStatus.java new file mode 100644 index 0000000000..2ee5df3ab7 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/OrderStatus.java @@ -0,0 +1,5 @@ +package com.baeldung.dddhexagonalspring.domain; + +public enum OrderStatus { + CREATED, COMPLETED +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/Product.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/Product.java new file mode 100644 index 0000000000..e05b4afe62 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/Product.java @@ -0,0 +1,46 @@ +package com.baeldung.dddhexagonalspring.domain; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigDecimal; +import java.util.Objects; +import java.util.UUID; + +public class Product { + private final UUID id; + private final BigDecimal price; + private final String name; + + @JsonCreator + public Product(@JsonProperty("id") final UUID id, @JsonProperty("price") final BigDecimal price, @JsonProperty("name") final String name) { + this.id = id; + this.price = price; + this.name = name; + } + + public BigDecimal getPrice() { + return price; + } + + public String getName() { + return name; + } + + public UUID getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Product product = (Product) o; + return Objects.equals(id, product.id) && Objects.equals(price, product.price) && Objects.equals(name, product.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, price, name); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/repository/OrderRepository.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/repository/OrderRepository.java new file mode 100644 index 0000000000..14b34e13f3 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/repository/OrderRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.dddhexagonalspring.domain.repository; + +import com.baeldung.dddhexagonalspring.domain.Order; + +import java.util.Optional; +import java.util.UUID; + +public interface OrderRepository { + Optional findById(UUID id); + + void save(Order order); +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/service/DomainOrderService.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/service/DomainOrderService.java new file mode 100644 index 0000000000..4fb2377745 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/service/DomainOrderService.java @@ -0,0 +1,54 @@ +package com.baeldung.dddhexagonalspring.domain.service; + +import com.baeldung.dddhexagonalspring.domain.Order; +import com.baeldung.dddhexagonalspring.domain.Product; +import com.baeldung.dddhexagonalspring.domain.repository.OrderRepository; + +import java.util.UUID; + +public class DomainOrderService implements OrderService { + + private final OrderRepository orderRepository; + + public DomainOrderService(final OrderRepository orderRepository) { + this.orderRepository = orderRepository; + } + + @Override + public UUID createOrder(final Product product) { + final Order order = new Order(UUID.randomUUID(), product); + orderRepository.save(order); + + return order.getId(); + } + + @Override + public void addProduct(final UUID id, final Product product) { + final Order order = getOrder(id); + order.addOrder(product); + + orderRepository.save(order); + } + + @Override + public void completeOrder(final UUID id) { + final Order order = getOrder(id); + order.complete(); + + orderRepository.save(order); + } + + @Override + public void deleteProduct(final UUID id, final UUID productId) { + final Order order = getOrder(id); + order.removeOrder(productId); + + orderRepository.save(order); + } + + private Order getOrder(UUID id) { + return orderRepository + .findById(id) + .orElseThrow(() -> new RuntimeException("Order with given id doesn't exist")); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/service/OrderService.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/service/OrderService.java new file mode 100644 index 0000000000..37297d74c4 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/domain/service/OrderService.java @@ -0,0 +1,15 @@ +package com.baeldung.dddhexagonalspring.domain.service; + +import com.baeldung.dddhexagonalspring.domain.Product; + +import java.util.UUID; + +public interface OrderService { + UUID createOrder(Product product); + + void addProduct(UUID id, Product product); + + void completeOrder(UUID id); + + void deleteProduct(UUID id, UUID productId); +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/configuration/BeanConfiguration.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/configuration/BeanConfiguration.java new file mode 100644 index 0000000000..4be5d84ba7 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/configuration/BeanConfiguration.java @@ -0,0 +1,19 @@ +package com.baeldung.dddhexagonalspring.infrastracture.configuration; + +import com.baeldung.dddhexagonalspring.DomainLayerApplication; +import com.baeldung.dddhexagonalspring.domain.repository.OrderRepository; +import com.baeldung.dddhexagonalspring.domain.service.DomainOrderService; +import com.baeldung.dddhexagonalspring.domain.service.OrderService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackageClasses = DomainLayerApplication.class) +public class BeanConfiguration { + + @Bean + OrderService orderService(final OrderRepository orderRepository) { + return new DomainOrderService(orderRepository); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/configuration/MongoDBConfiguration.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/configuration/MongoDBConfiguration.java new file mode 100644 index 0000000000..fd76b2eb0e --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/configuration/MongoDBConfiguration.java @@ -0,0 +1,8 @@ +package com.baeldung.dddhexagonalspring.infrastracture.configuration; + +import com.baeldung.dddhexagonalspring.infrastracture.repository.SpringDataOrderRepository; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +@EnableMongoRepositories(basePackageClasses = SpringDataOrderRepository.class) +public class MongoDBConfiguration { +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/repository/MongoDbOrderRepository.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/repository/MongoDbOrderRepository.java new file mode 100644 index 0000000000..3123ef3e2f --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/repository/MongoDbOrderRepository.java @@ -0,0 +1,30 @@ +package com.baeldung.dddhexagonalspring.infrastracture.repository; + +import com.baeldung.dddhexagonalspring.domain.Order; +import com.baeldung.dddhexagonalspring.domain.repository.OrderRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Optional; +import java.util.UUID; + +@Component +public class MongoDbOrderRepository implements OrderRepository { + + private final SpringDataOrderRepository orderRepository; + + @Autowired + public MongoDbOrderRepository(final SpringDataOrderRepository orderRepository) { + this.orderRepository = orderRepository; + } + + @Override + public Optional findById(final UUID id) { + return orderRepository.findById(id); + } + + @Override + public void save(final Order order) { + orderRepository.save(order); + } +} diff --git a/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/repository/SpringDataOrderRepository.java b/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/repository/SpringDataOrderRepository.java new file mode 100644 index 0000000000..0279a5ce4a --- /dev/null +++ b/ddd/src/main/java/com/baeldung/dddhexagonalspring/infrastracture/repository/SpringDataOrderRepository.java @@ -0,0 +1,11 @@ +package com.baeldung.dddhexagonalspring.infrastracture.repository; + +import com.baeldung.dddhexagonalspring.domain.Order; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface SpringDataOrderRepository extends MongoRepository { +} diff --git a/ddd/src/main/resources/ddd-layers.properties b/ddd/src/main/resources/ddd-layers.properties new file mode 100644 index 0000000000..0479996b17 --- /dev/null +++ b/ddd/src/main/resources/ddd-layers.properties @@ -0,0 +1,5 @@ +spring.data.mongodb.host=localhost +spring.data.mongodb.port=27017 +spring.data.mongodb.database=order-database +spring.data.mongodb.username=order +spring.data.mongodb.password=order \ No newline at end of file diff --git a/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/OrderProvider.java b/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/OrderProvider.java new file mode 100644 index 0000000000..c534713ca3 --- /dev/null +++ b/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/OrderProvider.java @@ -0,0 +1,17 @@ +package com.baeldung.dddhexagonalspring.domain; + +import java.math.BigDecimal; +import java.util.UUID; + +public class OrderProvider { + public static Order getCreatedOrder() { + return new Order(UUID.randomUUID(), new Product(UUID.randomUUID(), BigDecimal.TEN, "productName")); + } + + public static Order getCompletedOrder() { + final Order order = getCreatedOrder(); + order.complete(); + + return order; + } +} diff --git a/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/OrderUnitTest.java b/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/OrderUnitTest.java new file mode 100644 index 0000000000..eceed999d8 --- /dev/null +++ b/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/OrderUnitTest.java @@ -0,0 +1,65 @@ +package com.baeldung.dddhexagonalspring.domain; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.math.BigDecimal; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class OrderUnitTest { + + @Test + void shouldCompleteOrder_thenChangeStatus() { + final Order order = OrderProvider.getCreatedOrder(); + + order.complete(); + + assertEquals(OrderStatus.COMPLETED, order.getStatus()); + } + + @Test + void shouldAddProduct_thenUpdatePrice() { + final Order order = OrderProvider.getCreatedOrder(); + final int orderOriginalProductSize = order + .getOrderItems() + .size(); + final BigDecimal orderOriginalPrice = order.getPrice(); + final Product productToAdd = new Product(UUID.randomUUID(), new BigDecimal("20"), "secondProduct"); + + order.addOrder(productToAdd); + + assertEquals(orderOriginalProductSize + 1, order + .getOrderItems() + .size()); + assertEquals(orderOriginalPrice.add(productToAdd.getPrice()), order.getPrice()); + } + + @Test + void shouldAddProduct_thenThrowException() { + final Order order = OrderProvider.getCompletedOrder(); + final Product productToAdd = new Product(UUID.randomUUID(), new BigDecimal("20"), "secondProduct"); + + final Executable executable = () -> order.addOrder(productToAdd); + + Assertions.assertThrows(DomainException.class, executable); + } + + @Test + void shouldRemoveProduct_thenUpdatePrice() { + final Order order = OrderProvider.getCreatedOrder(); + final UUID productId = order + .getOrderItems() + .get(0) + .getProductId(); + + order.removeOrder(productId); + + assertEquals(0, order + .getOrderItems() + .size()); + assertEquals(BigDecimal.ZERO, order.getPrice()); + } +} \ No newline at end of file diff --git a/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/service/DomainOrderServiceUnitTest.java b/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/service/DomainOrderServiceUnitTest.java new file mode 100644 index 0000000000..797068a30a --- /dev/null +++ b/ddd/src/test/java/com/baeldung/dddhexagonalspring/domain/service/DomainOrderServiceUnitTest.java @@ -0,0 +1,91 @@ +package com.baeldung.dddhexagonalspring.domain.service; + +import com.baeldung.dddhexagonalspring.domain.Order; +import com.baeldung.dddhexagonalspring.domain.OrderProvider; +import com.baeldung.dddhexagonalspring.domain.Product; +import com.baeldung.dddhexagonalspring.domain.repository.OrderRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class DomainOrderServiceUnitTest { + + private OrderRepository orderRepository; + private DomainOrderService tested; + + @BeforeEach + void setUp() { + orderRepository = mock(OrderRepository.class); + tested = new DomainOrderService(orderRepository); + } + + @Test + void shouldCreateOrder_thenSaveIt() { + final Product product = new Product(UUID.randomUUID(), BigDecimal.TEN, "productName"); + + final UUID id = tested.createOrder(product); + + verify(orderRepository).save(any(Order.class)); + assertNotNull(id); + } + + @Test + void shouldAddProduct_thenSaveOrder() { + final Order order = spy(OrderProvider.getCreatedOrder()); + final Product product = new Product(UUID.randomUUID(), BigDecimal.TEN, "test"); + when(orderRepository.findById(order.getId())).thenReturn(Optional.of(order)); + + tested.addProduct(order.getId(), product); + + verify(orderRepository).save(order); + verify(order).addOrder(product); + } + + @Test + void shouldAddProduct_thenThrowException() { + final Product product = new Product(UUID.randomUUID(), BigDecimal.TEN, "test"); + final UUID id = UUID.randomUUID(); + when(orderRepository.findById(id)).thenReturn(Optional.empty()); + + final Executable executable = () -> tested.addProduct(id, product); + + verify(orderRepository, times(0)).save(any(Order.class)); + assertThrows(RuntimeException.class, executable); + } + + @Test + void shouldCompleteOrder_thenSaveIt() { + final Order order = spy(OrderProvider.getCreatedOrder()); + when(orderRepository.findById(order.getId())).thenReturn(Optional.of(order)); + + tested.completeOrder(order.getId()); + + verify(orderRepository).save(any(Order.class)); + verify(order).complete(); + } + + @Test + void shouldDeleteProduct_thenSaveOrder() { + final Order order = spy(OrderProvider.getCreatedOrder()); + final UUID productId = order + .getOrderItems() + .get(0) + .getProductId(); + + when(orderRepository.findById(order.getId())).thenReturn(Optional.of(order)); + + tested.deleteProduct(order.getId(), productId); + + verify(orderRepository).save(order); + verify(order).removeOrder(productId); + } +} \ No newline at end of file diff --git a/ddd/src/test/java/com/baeldung/dddhexagonalspring/infrastracture/repository/MongoDbOrderRepositoryUnitTest.java b/ddd/src/test/java/com/baeldung/dddhexagonalspring/infrastracture/repository/MongoDbOrderRepositoryUnitTest.java new file mode 100644 index 0000000000..8f7e8260a3 --- /dev/null +++ b/ddd/src/test/java/com/baeldung/dddhexagonalspring/infrastracture/repository/MongoDbOrderRepositoryUnitTest.java @@ -0,0 +1,51 @@ +package com.baeldung.dddhexagonalspring.infrastracture.repository; + +import com.baeldung.dddhexagonalspring.domain.Order; +import com.baeldung.dddhexagonalspring.domain.Product; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class MongoDbOrderRepositoryUnitTest { + + private SpringDataOrderRepository springDataOrderRepository; + private MongoDbOrderRepository tested; + + @BeforeEach + void setUp(){ + springDataOrderRepository = mock(SpringDataOrderRepository.class); + + tested = new MongoDbOrderRepository(springDataOrderRepository); + } + + @Test + void shouldFindById_thenReturnOrder() { + final UUID id = UUID.randomUUID(); + final Order order = createOrder(id); + when(springDataOrderRepository.findById(id)).thenReturn(Optional.of(order)); + + final Optional result = tested.findById(id); + + assertEquals(order, result.get()); + } + + @Test + void shouldSaveOrder_viaSpringDataOrderRepository() { + final UUID id = UUID.randomUUID(); + final Order order = createOrder(id); + + tested.save(order); + + verify(springDataOrderRepository).save(order); + } + + private Order createOrder(UUID id) { + return new Order(id, new Product(UUID.randomUUID(), BigDecimal.TEN, "product")); + } +} \ No newline at end of file diff --git a/ddd/src/test/resources/com/baeldung/dddhexagonalspring/README.md b/ddd/src/test/resources/com/baeldung/dddhexagonalspring/README.md new file mode 100644 index 0000000000..e0337498fc --- /dev/null +++ b/ddd/src/test/resources/com/baeldung/dddhexagonalspring/README.md @@ -0,0 +1,7 @@ +## Setup DDD Hexagonal Spring Application + +To run this project, follow these steps: + +* Run the application database by executing `docker-compose up` in this directory. +* Launch the Spring Boot Application (DomainLayerApplication). +* By default, application will connect to this database (configuration in *ddd-layers.properties*) \ No newline at end of file diff --git a/ddd/src/test/resources/com/baeldung/dddhexagonalspring/docker-compose.yml b/ddd/src/test/resources/com/baeldung/dddhexagonalspring/docker-compose.yml new file mode 100644 index 0000000000..d85ddf4a0e --- /dev/null +++ b/ddd/src/test/resources/com/baeldung/dddhexagonalspring/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3' + +services: + order-mongo-database: + image: mongo:3.4.13 + restart: always + ports: + - 27017:27017 + environment: + MONGO_INITDB_ROOT_USERNAME: admin + MONGO_INITDB_ROOT_PASSWORD: admin + MONGO_INITDB_DATABASE: order-database + volumes: + - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro \ No newline at end of file diff --git a/ddd/src/test/resources/com/baeldung/dddhexagonalspring/mongo-init.js b/ddd/src/test/resources/com/baeldung/dddhexagonalspring/mongo-init.js new file mode 100644 index 0000000000..b1564df50a --- /dev/null +++ b/ddd/src/test/resources/com/baeldung/dddhexagonalspring/mongo-init.js @@ -0,0 +1,12 @@ +db.createUser( + { + user: "order", + pwd: "order", + roles: [ + { + role: "readWrite", + db: "order-database" + } + ] + } +); \ No newline at end of file