Java 26392 Move modules to patterns-module (#15060)
* JAVA-26392 Move axon module and ddd module to patterns-modules * JAVA-26292 Move ddd-contexts to patterns-module
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.baeldung.ddd.order;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.money.Money;
|
||||
|
||||
public class Order {
|
||||
private final List<OrderLine> orderLines;
|
||||
private Money totalCost;
|
||||
|
||||
public Order(List<OrderLine> orderLines) {
|
||||
checkNotNull(orderLines);
|
||||
if (orderLines.isEmpty()) {
|
||||
throw new IllegalArgumentException("Order must have at least one order line item");
|
||||
}
|
||||
this.orderLines = new ArrayList<>(orderLines);
|
||||
totalCost = calculateTotalCost();
|
||||
}
|
||||
|
||||
public void addLineItem(OrderLine orderLine) {
|
||||
checkNotNull(orderLine);
|
||||
orderLines.add(orderLine);
|
||||
totalCost = totalCost.plus(orderLine.cost());
|
||||
}
|
||||
|
||||
public List<OrderLine> getOrderLines() {
|
||||
return new ArrayList<>(orderLines);
|
||||
}
|
||||
|
||||
public void removeLineItem(int line) {
|
||||
OrderLine removedLine = orderLines.remove(line);
|
||||
totalCost = totalCost.minus(removedLine.cost());
|
||||
}
|
||||
|
||||
public Money totalCost() {
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
private Money calculateTotalCost() {
|
||||
return orderLines.stream()
|
||||
.map(OrderLine::cost)
|
||||
.reduce(Money::plus)
|
||||
.get();
|
||||
}
|
||||
|
||||
private static void checkNotNull(Object par) {
|
||||
if (par == null) {
|
||||
throw new NullPointerException("Parameter cannot be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.baeldung.ddd.order;
|
||||
|
||||
import org.joda.money.Money;
|
||||
|
||||
public class OrderLine {
|
||||
private final Product product;
|
||||
private final int quantity;
|
||||
|
||||
public OrderLine(Product product, int quantity) {
|
||||
super();
|
||||
this.product = product;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OrderLine other = (OrderLine) obj;
|
||||
if (product == null) {
|
||||
if (other.product != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!product.equals(other.product)) {
|
||||
return false;
|
||||
}
|
||||
if (quantity != other.quantity) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((product == null) ? 0 : product.hashCode());
|
||||
result = prime * result + quantity;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OrderLine [product=" + product + ", quantity=" + quantity + "]";
|
||||
}
|
||||
|
||||
Money cost() {
|
||||
return product.getPrice()
|
||||
.multipliedBy(quantity);
|
||||
}
|
||||
|
||||
Product getProduct() {
|
||||
return product;
|
||||
}
|
||||
|
||||
int getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.baeldung.ddd.order;
|
||||
|
||||
import org.joda.money.Money;
|
||||
|
||||
public class Product {
|
||||
private final Money price;
|
||||
|
||||
public Product(Money price) {
|
||||
super();
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Product other = (Product) obj;
|
||||
if (price == null) {
|
||||
if (other.price != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!price.equals(other.price)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((price == null) ? 0 : price.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Product [price=" + price + "]";
|
||||
}
|
||||
|
||||
Money getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.baeldung.ddd.order.config;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
|
||||
@Configuration
|
||||
public class CustomMongoConfiguration {
|
||||
|
||||
@Bean
|
||||
public MongoCustomConversions customConversions() {
|
||||
return new MongoCustomConversions(Collections.singletonList(DocumentToMoneyConverter.INSTANCE));
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
enum DocumentToMoneyConverter implements Converter<Document, Money> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Money convert(Document source) {
|
||||
Document money = source.get("money", Document.class);
|
||||
|
||||
return Money.of(getCurrency(money), getAmount(money));
|
||||
}
|
||||
|
||||
private CurrencyUnit getCurrency(Document money) {
|
||||
Document currency = money.get("currency", Document.class);
|
||||
String currencyCode = currency.getString("code");
|
||||
return CurrencyUnit.of(currencyCode);
|
||||
}
|
||||
|
||||
private BigDecimal getAmount(Document money) {
|
||||
String amount = money.getString("amount");
|
||||
return BigDecimal.valueOf(Double.parseDouble(amount));
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
|
||||
public class AmountBasedDiscountPolicy implements DiscountPolicy {
|
||||
@Override
|
||||
public double discount(Order order) {
|
||||
if (order.totalCost()
|
||||
.isGreaterThan(Money.of(CurrencyUnit.USD, 500.00))) {
|
||||
return 0.10;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
public interface DiscountPolicy {
|
||||
double discount(Order order);
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
public class FlatDiscountPolicy implements DiscountPolicy {
|
||||
@Override
|
||||
public double discount(Order order) {
|
||||
return 0.01;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.money.Money;
|
||||
|
||||
import com.baeldung.ddd.order.OrderLine;
|
||||
import com.baeldung.ddd.order.doubledispatch.visitor.OrderVisitor;
|
||||
import com.baeldung.ddd.order.doubledispatch.visitor.Visitable;
|
||||
|
||||
public class Order extends com.baeldung.ddd.order.Order implements Visitable<OrderVisitor> {
|
||||
public Order(List<OrderLine> orderLines) {
|
||||
super(orderLines);
|
||||
}
|
||||
|
||||
public Money totalCost(SpecialDiscountPolicy discountPolicy) {
|
||||
return totalCost().multipliedBy(1 - applyDiscountPolicy(discountPolicy), RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
protected double applyDiscountPolicy(SpecialDiscountPolicy discountPolicy) {
|
||||
return discountPolicy.discount(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(OrderVisitor visitor) {
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
public interface SpecialDiscountPolicy extends DiscountPolicy {
|
||||
double discount(SpecialOrder order);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.baeldung.ddd.order.OrderLine;
|
||||
import com.baeldung.ddd.order.doubledispatch.visitor.OrderVisitor;
|
||||
|
||||
public class SpecialOrder extends Order {
|
||||
|
||||
private boolean eligibleForExtraDiscount;
|
||||
|
||||
public SpecialOrder(List<OrderLine> orderLines) {
|
||||
super(orderLines);
|
||||
this.eligibleForExtraDiscount = false;
|
||||
}
|
||||
|
||||
public SpecialOrder(List<OrderLine> orderLines, boolean eligibleForSpecialDiscount) {
|
||||
super(orderLines);
|
||||
this.eligibleForExtraDiscount = eligibleForSpecialDiscount;
|
||||
}
|
||||
|
||||
public boolean isEligibleForExtraDiscount() {
|
||||
return eligibleForExtraDiscount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double applyDiscountPolicy(SpecialDiscountPolicy discountPolicy) {
|
||||
return discountPolicy.discount(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(OrderVisitor visitor) {
|
||||
visitor.visit(this);
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.baeldung.ddd.order.doubledispatch.visitor;
|
||||
|
||||
import com.baeldung.ddd.order.doubledispatch.Order;
|
||||
import com.baeldung.ddd.order.doubledispatch.SpecialOrder;
|
||||
|
||||
public class HtmlOrderViewCreator implements OrderVisitor {
|
||||
|
||||
private String html;
|
||||
|
||||
public String getHtml() {
|
||||
return html;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Order order) {
|
||||
html = String.format("<p>Regular order total cost: %s</p>", order.totalCost());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SpecialOrder order) {
|
||||
html = String.format("<h1>Special Order</h1><p>total cost: %s</p>", order.totalCost());
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.baeldung.ddd.order.doubledispatch.visitor;
|
||||
|
||||
import com.baeldung.ddd.order.doubledispatch.Order;
|
||||
import com.baeldung.ddd.order.doubledispatch.SpecialOrder;
|
||||
|
||||
public interface OrderVisitor {
|
||||
void visit(Order order);
|
||||
void visit(SpecialOrder order);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.baeldung.ddd.order.doubledispatch.visitor;
|
||||
|
||||
public interface Visitable<V> {
|
||||
void accept(V visitor);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.baeldung.ddd.order.jpa;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "order_table")
|
||||
class JpaOrder {
|
||||
private String currencyUnit;
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
private final List<JpaOrderLine> orderLines;
|
||||
private BigDecimal totalCost;
|
||||
|
||||
JpaOrder() {
|
||||
totalCost = null;
|
||||
orderLines = new ArrayList<>();
|
||||
}
|
||||
|
||||
JpaOrder(List<JpaOrderLine> orderLines) {
|
||||
checkNotNull(orderLines);
|
||||
if (orderLines.isEmpty()) {
|
||||
throw new IllegalArgumentException("Order must have at least one order line item");
|
||||
}
|
||||
this.orderLines = new ArrayList<>(orderLines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JpaOrder other = (JpaOrder) obj;
|
||||
if (id == null) {
|
||||
if (other.id != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!id.equals(other.id)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JpaOrder [currencyUnit=" + currencyUnit + ", id=" + id + ", orderLines=" + orderLines + ", totalCost=" + totalCost + "]";
|
||||
}
|
||||
|
||||
void addLineItem(JpaOrderLine orderLine) {
|
||||
checkNotNull(orderLine);
|
||||
orderLines.add(orderLine);
|
||||
}
|
||||
|
||||
String getCurrencyUnit() {
|
||||
return currencyUnit;
|
||||
}
|
||||
|
||||
Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
List<JpaOrderLine> getOrderLines() {
|
||||
return new ArrayList<>(orderLines);
|
||||
}
|
||||
|
||||
BigDecimal getTotalCost() {
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
void removeLineItem(int line) {
|
||||
orderLines.remove(line);
|
||||
}
|
||||
|
||||
void setCurrencyUnit(String currencyUnit) {
|
||||
this.currencyUnit = currencyUnit;
|
||||
}
|
||||
|
||||
void setTotalCost(BigDecimal totalCost) {
|
||||
this.totalCost = totalCost;
|
||||
}
|
||||
|
||||
private static void checkNotNull(Object par) {
|
||||
if (par == null) {
|
||||
throw new NullPointerException("Parameter cannot be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.baeldung.ddd.order.jpa;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Embedded;
|
||||
|
||||
@Embeddable
|
||||
class JpaOrderLine {
|
||||
@Embedded
|
||||
private final JpaProduct product;
|
||||
private final int quantity;
|
||||
|
||||
JpaOrderLine() {
|
||||
quantity = 0;
|
||||
product = null;
|
||||
}
|
||||
|
||||
JpaOrderLine(JpaProduct product, int quantity) {
|
||||
super();
|
||||
this.product = product;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JpaOrderLine other = (JpaOrderLine) obj;
|
||||
if (product == null) {
|
||||
if (other.product != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!product.equals(other.product)) {
|
||||
return false;
|
||||
}
|
||||
if (quantity != other.quantity) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((product == null) ? 0 : product.hashCode());
|
||||
result = prime * result + quantity;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JpaOrderLine [product=" + product + ", quantity=" + quantity + "]";
|
||||
}
|
||||
|
||||
JpaProduct getProduct() {
|
||||
return product;
|
||||
}
|
||||
|
||||
int getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.baeldung.ddd.order.jpa;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface JpaOrderRepository extends JpaRepository<JpaOrder, Long> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.baeldung.ddd.order.jpa;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
@Embeddable
|
||||
class JpaProduct {
|
||||
private String currencyUnit;
|
||||
private BigDecimal price;
|
||||
|
||||
public JpaProduct() {
|
||||
}
|
||||
|
||||
public JpaProduct(BigDecimal price, String currencyUnit) {
|
||||
super();
|
||||
this.price = price;
|
||||
this.currencyUnit = currencyUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JpaProduct other = (JpaProduct) obj;
|
||||
if (currencyUnit == null) {
|
||||
if (other.currencyUnit != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!currencyUnit.equals(other.currencyUnit)) {
|
||||
return false;
|
||||
}
|
||||
if (price == null) {
|
||||
if (other.price != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!price.equals(other.price)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getCurrencyUnit() {
|
||||
return currencyUnit;
|
||||
}
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((currencyUnit == null) ? 0 : currencyUnit.hashCode());
|
||||
result = prime * result + ((price == null) ? 0 : price.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setCurrencyUnit(String currencyUnit) {
|
||||
this.currencyUnit = currencyUnit;
|
||||
}
|
||||
|
||||
public void setPrice(BigDecimal price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JpaProduct [currencyUnit=" + currencyUnit + ", price=" + price + "]";
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.baeldung.ddd.order.mongo;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
import com.baeldung.ddd.order.Order;
|
||||
|
||||
public interface OrderMongoRepository extends MongoRepository<Order, String> {
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.baeldung.dddhexagonalspring;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.application.cli.CliOrderController;
|
||||
|
||||
@SpringBootApplication
|
||||
@PropertySource(value = { "classpath:ddd-layers.properties" })
|
||||
public class DomainLayerApplication implements CommandLineRunner {
|
||||
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication application = new SpringApplication(DomainLayerApplication.class);
|
||||
// uncomment to run just the console application
|
||||
// application.setWebApplicationType(WebApplicationType.NONE);
|
||||
application.run(args);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public CliOrderController orderController;
|
||||
|
||||
@Autowired
|
||||
public ConfigurableApplicationContext context;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
orderController.createCompleteOrder();
|
||||
orderController.createIncompleteOrder();
|
||||
// uncomment to stop the context when execution is done
|
||||
// context.close();
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.baeldung.dddhexagonalspring.application.cli;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Product;
|
||||
import com.baeldung.dddhexagonalspring.domain.service.OrderService;
|
||||
|
||||
@Component
|
||||
public class CliOrderController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CliOrderController.class);
|
||||
|
||||
private final OrderService orderService;
|
||||
|
||||
@Autowired
|
||||
public CliOrderController(OrderService orderService) {
|
||||
this.orderService = orderService;
|
||||
}
|
||||
|
||||
public void createCompleteOrder() {
|
||||
LOG.info("<<Create complete order>>");
|
||||
UUID orderId = createOrder();
|
||||
orderService.completeOrder(orderId);
|
||||
}
|
||||
|
||||
public void createIncompleteOrder() {
|
||||
LOG.info("<<Create incomplete order>>");
|
||||
UUID orderId = createOrder();
|
||||
}
|
||||
|
||||
private UUID createOrder() {
|
||||
LOG.info("Placing a new order with two products");
|
||||
Product mobilePhone = new Product(UUID.randomUUID(), BigDecimal.valueOf(200), "mobile");
|
||||
Product razor = new Product(UUID.randomUUID(), BigDecimal.valueOf(50), "razor");
|
||||
LOG.info("Creating order with mobile phone");
|
||||
UUID orderId = orderService.createOrder(mobilePhone);
|
||||
LOG.info("Adding a razor to the order");
|
||||
orderService.addProduct(orderId, razor);
|
||||
return orderId;
|
||||
}
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.baeldung.dddhexagonalspring.application.rest;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.baeldung.dddhexagonalspring.domain;
|
||||
|
||||
class DomainException extends RuntimeException {
|
||||
DomainException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.baeldung.dddhexagonalspring.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Order {
|
||||
private UUID id;
|
||||
private OrderStatus status;
|
||||
private List<OrderItem> 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<OrderItem> getOrderItems() {
|
||||
return Collections.unmodifiableList(orderItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, orderItems, price, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!(obj instanceof Order))
|
||||
return false;
|
||||
Order other = (Order) obj;
|
||||
return Objects.equals(id, other.id) && Objects.equals(orderItems, other.orderItems) && Objects.equals(price, other.price) && status == other.status;
|
||||
}
|
||||
|
||||
private Order() {
|
||||
}
|
||||
}
|
||||
+39
@@ -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);
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.baeldung.dddhexagonalspring.domain;
|
||||
|
||||
public enum OrderStatus {
|
||||
CREATED, COMPLETED
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
+12
@@ -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<Order> findById(UUID id);
|
||||
|
||||
void save(Order order);
|
||||
}
|
||||
+54
@@ -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"));
|
||||
}
|
||||
}
|
||||
+15
@@ -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);
|
||||
}
|
||||
+19
@@ -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);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.configuration;
|
||||
|
||||
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.infrastracture.repository.cassandra.SpringDataCassandraOrderRepository;
|
||||
|
||||
@EnableCassandraRepositories(basePackageClasses = SpringDataCassandraOrderRepository.class)
|
||||
public class CassandraConfiguration {
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.configuration;
|
||||
|
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.infrastracture.repository.mongo.SpringDataMongoOrderRepository;
|
||||
|
||||
@EnableMongoRepositories(basePackageClasses = SpringDataMongoOrderRepository.class)
|
||||
public class MongoDBConfiguration {
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository.cassandra;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Order;
|
||||
import com.baeldung.dddhexagonalspring.domain.repository.OrderRepository;
|
||||
|
||||
@Component
|
||||
public class CassandraDbOrderRepository implements OrderRepository {
|
||||
|
||||
private final SpringDataCassandraOrderRepository orderRepository;
|
||||
|
||||
@Autowired
|
||||
public CassandraDbOrderRepository(SpringDataCassandraOrderRepository orderRepository) {
|
||||
this.orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Order> findById(UUID id) {
|
||||
Optional<OrderEntity> orderEntity = orderRepository.findById(id);
|
||||
if (orderEntity.isPresent()) {
|
||||
return Optional.of(orderEntity.get()
|
||||
.toOrder());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Order order) {
|
||||
orderRepository.save(new OrderEntity(order));
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository.cassandra;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Order;
|
||||
import com.baeldung.dddhexagonalspring.domain.OrderItem;
|
||||
import com.baeldung.dddhexagonalspring.domain.OrderStatus;
|
||||
import com.baeldung.dddhexagonalspring.domain.Product;
|
||||
|
||||
public class OrderEntity {
|
||||
|
||||
@PrimaryKey
|
||||
private UUID id;
|
||||
private OrderStatus status;
|
||||
private List<OrderItemEntity> orderItemEntities;
|
||||
private BigDecimal price;
|
||||
|
||||
public OrderEntity(UUID id, OrderStatus status, List<OrderItemEntity> orderItemEntities, BigDecimal price) {
|
||||
this.id = id;
|
||||
this.status = status;
|
||||
this.orderItemEntities = orderItemEntities;
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public OrderEntity() {
|
||||
}
|
||||
|
||||
public OrderEntity(Order order) {
|
||||
this.id = order.getId();
|
||||
this.price = order.getPrice();
|
||||
this.status = order.getStatus();
|
||||
this.orderItemEntities = order.getOrderItems()
|
||||
.stream()
|
||||
.map(OrderItemEntity::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
public Order toOrder() {
|
||||
List<OrderItem> orderItems = orderItemEntities.stream()
|
||||
.map(OrderItemEntity::toOrderItem)
|
||||
.collect(Collectors.toList());
|
||||
List<Product> namelessProducts = orderItems.stream()
|
||||
.map(orderItem -> new Product(orderItem.getProductId(), orderItem.getPrice(), ""))
|
||||
.collect(Collectors.toList());
|
||||
Order order = new Order(id, namelessProducts.remove(0));
|
||||
namelessProducts.forEach(product -> order.addOrder(product));
|
||||
if (status == OrderStatus.COMPLETED) {
|
||||
order.complete();
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public OrderStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public List<OrderItemEntity> getOrderItems() {
|
||||
return orderItemEntities;
|
||||
}
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository.cassandra;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.OrderItem;
|
||||
import com.baeldung.dddhexagonalspring.domain.Product;
|
||||
|
||||
@UserDefinedType
|
||||
public class OrderItemEntity {
|
||||
|
||||
private UUID productId;
|
||||
private BigDecimal price;
|
||||
|
||||
public OrderItemEntity() {
|
||||
}
|
||||
|
||||
public OrderItemEntity(final OrderItem orderItem) {
|
||||
this.productId = orderItem.getProductId();
|
||||
this.price = orderItem.getPrice();
|
||||
}
|
||||
|
||||
public OrderItem toOrderItem() {
|
||||
return new OrderItem(new Product(productId, price, ""));
|
||||
}
|
||||
|
||||
public UUID getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(UUID productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(BigDecimal price) {
|
||||
this.price = price;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository.cassandra;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.cassandra.repository.CassandraRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SpringDataCassandraOrderRepository extends CassandraRepository<OrderEntity, UUID> {
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository.mongo;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Order;
|
||||
import com.baeldung.dddhexagonalspring.domain.repository.OrderRepository;
|
||||
|
||||
@Component
|
||||
@Primary
|
||||
public class MongoDbOrderRepository implements OrderRepository {
|
||||
|
||||
private final SpringDataMongoOrderRepository orderRepository;
|
||||
|
||||
@Autowired
|
||||
public MongoDbOrderRepository(final SpringDataMongoOrderRepository orderRepository) {
|
||||
this.orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Order> findById(final UUID id) {
|
||||
return orderRepository.findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(final Order order) {
|
||||
orderRepository.save(order);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository.mongo;
|
||||
|
||||
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 SpringDataMongoOrderRepository extends MongoRepository<Order, UUID> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
|
||||
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
|
||||
|
||||
spring.data.cassandra.keyspaceName=order_database
|
||||
spring.data.cassandra.username=cassandra
|
||||
spring.data.cassandra.password=cassandra
|
||||
spring.data.cassandra.contactPoints=localhost
|
||||
spring.data.cassandra.port=9042
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.ddd.order;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
|
||||
public class OrderFixtureUtils {
|
||||
public static List<OrderLine> anyOrderLines() {
|
||||
return Arrays.asList(new OrderLine(new Product(Money.of(CurrencyUnit.USD, 100)), 1));
|
||||
}
|
||||
|
||||
public static List<OrderLine> orderLineItemsWorthNDollars(int totalCost) {
|
||||
return Arrays.asList(new OrderLine(new Product(Money.of(CurrencyUnit.USD, totalCost)), 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.baeldung.ddd.order;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class OrderUnitTest {
|
||||
@DisplayName("given order with two items, when calculate total cost, then sum is returned")
|
||||
@Test
|
||||
void test0() throws Exception {
|
||||
// given
|
||||
OrderLine ol0 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 10.00)), 2);
|
||||
OrderLine ol1 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 5.00)), 10);
|
||||
Order order = new Order(Arrays.asList(ol0, ol1));
|
||||
|
||||
// when
|
||||
Money totalCost = order.totalCost();
|
||||
|
||||
// then
|
||||
assertThat(totalCost).isEqualTo(Money.of(CurrencyUnit.USD, 70.00));
|
||||
}
|
||||
|
||||
@DisplayName("when create order without line items, then exception is thrown")
|
||||
@Test
|
||||
void test1() throws Exception {
|
||||
// when
|
||||
Throwable throwable = catchThrowable(() -> new Order(new ArrayList<>()));
|
||||
|
||||
// then
|
||||
assertThat(throwable).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@DisplayName("given order with two line items, when add another line item, then total cost is updated")
|
||||
@Test
|
||||
void test2() throws Exception {
|
||||
// given
|
||||
OrderLine ol0 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 10.00)), 1);
|
||||
OrderLine ol1 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 5.00)), 1);
|
||||
Order order = new Order(Arrays.asList(ol0, ol1));
|
||||
|
||||
// when
|
||||
order.addLineItem(new OrderLine(new Product(Money.of(CurrencyUnit.USD, 20.00)), 2));
|
||||
|
||||
// then
|
||||
assertThat(order.totalCost()).isEqualTo(Money.of(CurrencyUnit.USD, 55));
|
||||
}
|
||||
|
||||
@DisplayName("given order with three line items, when remove item, then total cost is updated")
|
||||
@Test
|
||||
void test3() throws Exception {
|
||||
// given
|
||||
OrderLine ol0 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 10.00)), 1);
|
||||
OrderLine ol1 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 20.00)), 1);
|
||||
OrderLine ol2 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 30.00)), 1);
|
||||
Order order = new Order(Arrays.asList(ol0, ol1, ol2));
|
||||
|
||||
// when
|
||||
order.removeLineItem(1);
|
||||
|
||||
// then
|
||||
assertThat(order.totalCost()).isEqualTo(Money.of(CurrencyUnit.USD, 40.00));
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.baeldung.ddd.order.OrderFixtureUtils;
|
||||
|
||||
public class DoubleDispatchDiscountPolicyUnitTest {
|
||||
// @formatter:off
|
||||
@DisplayName(
|
||||
"given regular order with items worth $100 total, " +
|
||||
"when apply 10% discount policy, " +
|
||||
"then cost after discount is $90"
|
||||
)
|
||||
// @formatter:on
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
// given
|
||||
Order order = new Order(OrderFixtureUtils.orderLineItemsWorthNDollars(100));
|
||||
SpecialDiscountPolicy discountPolicy = new SpecialDiscountPolicy() {
|
||||
|
||||
@Override
|
||||
public double discount(Order order) {
|
||||
return 0.10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double discount(SpecialOrder order) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// when
|
||||
Money totalCostAfterDiscount = order.totalCost(discountPolicy);
|
||||
|
||||
// then
|
||||
assertThat(totalCostAfterDiscount).isEqualTo(Money.of(CurrencyUnit.USD, 90));
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
@DisplayName(
|
||||
"given special order eligible for extra discount with items worth $100 total, " +
|
||||
"when apply 20% discount policy for extra discount orders, " +
|
||||
"then cost after discount is $80"
|
||||
)
|
||||
// @formatter:on
|
||||
@Test
|
||||
void test1() throws Exception {
|
||||
// given
|
||||
boolean eligibleForExtraDiscount = true;
|
||||
Order order = new SpecialOrder(OrderFixtureUtils.orderLineItemsWorthNDollars(100), eligibleForExtraDiscount);
|
||||
SpecialDiscountPolicy discountPolicy = new SpecialDiscountPolicy() {
|
||||
|
||||
@Override
|
||||
public double discount(Order order) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double discount(SpecialOrder order) {
|
||||
if (order.isEligibleForExtraDiscount())
|
||||
return 0.20;
|
||||
return 0.10;
|
||||
}
|
||||
};
|
||||
|
||||
// when
|
||||
Money totalCostAfterDiscount = order.totalCost(discountPolicy);
|
||||
|
||||
// then
|
||||
assertThat(totalCostAfterDiscount).isEqualTo(Money.of(CurrencyUnit.USD, 80.00));
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.baeldung.ddd.order.doubledispatch.Order;
|
||||
import com.baeldung.ddd.order.OrderFixtureUtils;
|
||||
import com.baeldung.ddd.order.OrderLine;
|
||||
import com.baeldung.ddd.order.doubledispatch.visitor.HtmlOrderViewCreator;
|
||||
|
||||
public class HtmlOrderViewCreatorUnitTest {
|
||||
// @formatter:off
|
||||
@DisplayName(
|
||||
"given collection of regular and special orders, " +
|
||||
"when create HTML view using visitor for each order, " +
|
||||
"then the dedicated view is created for each order"
|
||||
)
|
||||
// @formatter:on
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
// given
|
||||
List<OrderLine> anyOrderLines = OrderFixtureUtils.anyOrderLines();
|
||||
List<Order> orders = Arrays.asList(new Order(anyOrderLines), new SpecialOrder(anyOrderLines));
|
||||
HtmlOrderViewCreator htmlOrderViewCreator = new HtmlOrderViewCreator();
|
||||
|
||||
// when
|
||||
orders.get(0)
|
||||
.accept(htmlOrderViewCreator);
|
||||
String regularOrderHtml = htmlOrderViewCreator.getHtml();
|
||||
orders.get(1)
|
||||
.accept(htmlOrderViewCreator);
|
||||
String specialOrderHtml = htmlOrderViewCreator.getHtml();
|
||||
|
||||
// then
|
||||
assertThat(regularOrderHtml).containsPattern("<p>Regular order total cost: .*</p>");
|
||||
assertThat(specialOrderHtml).containsPattern("<h1>Special Order</h1><p>total cost: .*</p>");
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.baeldung.ddd.order.doubledispatch.Order;
|
||||
import com.baeldung.ddd.order.OrderFixtureUtils;
|
||||
import com.baeldung.ddd.order.OrderLine;
|
||||
import com.baeldung.ddd.order.doubledispatch.SpecialDiscountPolicy;
|
||||
import com.baeldung.ddd.order.doubledispatch.SpecialOrder;
|
||||
|
||||
public class MethodOverloadExampleUnitTest {
|
||||
// @formatter:off
|
||||
@DisplayName(
|
||||
"given discount policy accepting special orders, " +
|
||||
"when apply the policy on special order declared as regular order, " +
|
||||
"then regular discount method is used"
|
||||
)
|
||||
// @formatter:on
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
// given
|
||||
SpecialDiscountPolicy specialPolicy = new SpecialDiscountPolicy() {
|
||||
@Override
|
||||
public double discount(Order order) {
|
||||
return 0.01;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double discount(SpecialOrder order) {
|
||||
return 0.10;
|
||||
}
|
||||
};
|
||||
Order specialOrder = new SpecialOrder(anyOrderLines());
|
||||
|
||||
// when
|
||||
double discount = specialPolicy.discount(specialOrder);
|
||||
|
||||
// then
|
||||
assertThat(discount).isEqualTo(0.01);
|
||||
}
|
||||
|
||||
private List<OrderLine> anyOrderLines() {
|
||||
return OrderFixtureUtils.anyOrderLines();
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.baeldung.ddd.order.doubledispatch;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.baeldung.ddd.order.OrderFixtureUtils;
|
||||
|
||||
public class SingleDispatchDiscountPolicyUnitTest {
|
||||
// @formatter:off
|
||||
@DisplayName(
|
||||
"given two discount policies, " +
|
||||
"when use these policies, " +
|
||||
"then single dispatch chooses the implementation based on runtime type"
|
||||
)
|
||||
// @formatter:on
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
// given
|
||||
DiscountPolicy flatPolicy = new FlatDiscountPolicy();
|
||||
DiscountPolicy amountPolicy = new AmountBasedDiscountPolicy();
|
||||
Order orderWorth501Dollars = orderWorthNDollars(501);
|
||||
|
||||
// when
|
||||
double flatDiscount = flatPolicy.discount(orderWorth501Dollars);
|
||||
double amountDiscount = amountPolicy.discount(orderWorth501Dollars);
|
||||
|
||||
// then
|
||||
assertThat(flatDiscount).isEqualTo(0.01);
|
||||
assertThat(amountDiscount).isEqualTo(0.1);
|
||||
}
|
||||
|
||||
private Order orderWorthNDollars(int totalCost) {
|
||||
return new Order(OrderFixtureUtils.orderLineItemsWorthNDollars(totalCost));
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.baeldung.ddd.order.jpa;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/*
|
||||
To run this test we need to run the databases first.
|
||||
A dedicated docker-compose.yml file is located under the resources directory.
|
||||
We can run it by simple executing `docker-compose up`.
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@SpringBootTest
|
||||
public class PersistOrderLiveTest {
|
||||
@Autowired
|
||||
private JpaOrderRepository repository;
|
||||
|
||||
@DisplayName("given order with two line items, when persist, then order is saved")
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
// given
|
||||
JpaOrder order = prepareTestOrderWithTwoLineItems();
|
||||
|
||||
// when
|
||||
JpaOrder savedOrder = repository.save(order);
|
||||
|
||||
// then
|
||||
JpaOrder foundOrder = repository.findById(savedOrder.getId())
|
||||
.get();
|
||||
assertThat(foundOrder.getOrderLines()).hasSize(2);
|
||||
}
|
||||
|
||||
private JpaOrder prepareTestOrderWithTwoLineItems() {
|
||||
JpaOrderLine ol0 = new JpaOrderLine(new JpaProduct(BigDecimal.valueOf(10.00), "USD"), 2);
|
||||
JpaOrderLine ol1 = new JpaOrderLine(new JpaProduct(BigDecimal.valueOf(5.00), "USD"), 10);
|
||||
return new JpaOrder(Arrays.asList(ol0, ol1));
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.baeldung.ddd.order.jpa;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ViolateOrderBusinessRulesUnitTest {
|
||||
@DisplayName("given two non-zero order line items, when create an order with them, it's possible to set total cost to zero")
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
// given
|
||||
// available products
|
||||
JpaProduct lungChingTea = new JpaProduct(BigDecimal.valueOf(10.00), "USD");
|
||||
JpaProduct gyokuroMiyazakiTea = new JpaProduct(BigDecimal.valueOf(20.00), "USD");
|
||||
// Lung Ching tea order line
|
||||
JpaOrderLine orderLine0 = new JpaOrderLine(lungChingTea, 2);
|
||||
// Gyokuro Miyazaki tea order line
|
||||
JpaOrderLine orderLine1 = new JpaOrderLine(gyokuroMiyazakiTea, 3);
|
||||
|
||||
// when
|
||||
// create the order
|
||||
JpaOrder order = new JpaOrder();
|
||||
order.addLineItem(orderLine0);
|
||||
order.addLineItem(orderLine1);
|
||||
order.setTotalCost(BigDecimal.ZERO);
|
||||
order.setCurrencyUnit("USD");
|
||||
|
||||
// then
|
||||
// this doesn't look good...
|
||||
assertThat(order.getTotalCost()).isEqualTo(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.baeldung.ddd.order.mongo;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.baeldung.ddd.order.Order;
|
||||
import com.baeldung.ddd.order.OrderLine;
|
||||
import com.baeldung.ddd.order.Product;
|
||||
|
||||
/*
|
||||
To run this test we need to run the databases first.
|
||||
A dedicated docker-compose.yml file is located under the resources directory.
|
||||
We can run it by simple executing `docker-compose up`.
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@SpringBootTest
|
||||
public class OrderMongoLiveTest {
|
||||
@Autowired
|
||||
private OrderMongoRepository repo;
|
||||
|
||||
@DisplayName("given order with two line items, when persist using mongo repository, then order is saved")
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
// given
|
||||
Order order = prepareTestOrderWithTwoLineItems();
|
||||
|
||||
// when
|
||||
repo.save(order);
|
||||
|
||||
// then
|
||||
List<Order> foundOrders = repo.findAll();
|
||||
assertThat(foundOrders).hasSize(1);
|
||||
List<OrderLine> foundOrderLines = foundOrders.iterator()
|
||||
.next()
|
||||
.getOrderLines();
|
||||
assertThat(foundOrderLines).hasSize(2);
|
||||
assertThat(foundOrderLines).containsOnlyElementsOf(order.getOrderLines());
|
||||
}
|
||||
|
||||
private Order prepareTestOrderWithTwoLineItems() {
|
||||
OrderLine ol0 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 10.00)), 2);
|
||||
OrderLine ol1 = new OrderLine(new Product(Money.of(CurrencyUnit.USD, 5.00)), 10);
|
||||
return new Order(Arrays.asList(ol0, ol1));
|
||||
}
|
||||
}
|
||||
+17
@@ -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;
|
||||
}
|
||||
}
|
||||
+65
@@ -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());
|
||||
}
|
||||
}
|
||||
+91
@@ -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);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Order;
|
||||
import com.baeldung.dddhexagonalspring.domain.Product;
|
||||
import com.baeldung.dddhexagonalspring.domain.repository.OrderRepository;
|
||||
import com.baeldung.dddhexagonalspring.infrastracture.repository.cassandra.SpringDataCassandraOrderRepository;
|
||||
|
||||
/*
|
||||
To run this test we need to run the databases first.
|
||||
A dedicated docker-compose.yml file is located under the resources directory.
|
||||
We can run it by simple executing `docker-compose up`.
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@SpringBootTest
|
||||
@TestPropertySource("classpath:ddd-layers-test.properties")
|
||||
class CassandraDbOrderRepositoryLiveTest {
|
||||
|
||||
@Autowired
|
||||
private SpringDataCassandraOrderRepository cassandraOrderRepository;
|
||||
|
||||
@Autowired
|
||||
private OrderRepository orderRepository;
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
cassandraOrderRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindById_thenReturnOrder() {
|
||||
|
||||
// given
|
||||
final UUID id = UUID.randomUUID();
|
||||
final Order order = createOrder(id);
|
||||
order.addOrder(new Product(UUID.randomUUID(), BigDecimal.TEN, "second"));
|
||||
order.complete();
|
||||
|
||||
// when
|
||||
orderRepository.save(order);
|
||||
|
||||
final Optional<Order> result = orderRepository.findById(id);
|
||||
|
||||
assertEquals(order, result.get());
|
||||
}
|
||||
|
||||
private Order createOrder(UUID id) {
|
||||
return new Order(id, new Product(UUID.randomUUID(), BigDecimal.TEN, "product"));
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Order;
|
||||
import com.baeldung.dddhexagonalspring.domain.Product;
|
||||
import com.baeldung.dddhexagonalspring.domain.repository.OrderRepository;
|
||||
import com.baeldung.dddhexagonalspring.infrastracture.repository.mongo.SpringDataMongoOrderRepository;
|
||||
|
||||
/*
|
||||
To run this test we need to run the databases first.
|
||||
A dedicated docker-compose.yml file is located under the resources directory.
|
||||
We can run it by simple executing `docker-compose up`.
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@SpringBootTest
|
||||
@TestPropertySource("classpath:ddd-layers-test.properties")
|
||||
class MongoDbOrderRepositoryLiveTest {
|
||||
|
||||
@Autowired
|
||||
private SpringDataMongoOrderRepository mongoOrderRepository;
|
||||
|
||||
@Autowired
|
||||
private OrderRepository orderRepository;
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
mongoOrderRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindById_thenReturnOrder() {
|
||||
|
||||
// given
|
||||
final UUID id = UUID.randomUUID();
|
||||
final Order order = createOrder(id);
|
||||
|
||||
// when
|
||||
orderRepository.save(order);
|
||||
|
||||
final Optional<Order> result = orderRepository.findById(id);
|
||||
|
||||
assertEquals(order, result.get());
|
||||
}
|
||||
|
||||
private Order createOrder(UUID id) {
|
||||
return new Order(id, new Product(UUID.randomUUID(), BigDecimal.TEN, "product"));
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.baeldung.dddhexagonalspring.infrastracture.repository;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Order;
|
||||
import com.baeldung.dddhexagonalspring.domain.Product;
|
||||
import com.baeldung.dddhexagonalspring.infrastracture.repository.mongo.MongoDbOrderRepository;
|
||||
import com.baeldung.dddhexagonalspring.infrastracture.repository.mongo.SpringDataMongoOrderRepository;
|
||||
|
||||
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 SpringDataMongoOrderRepository springDataOrderRepository;
|
||||
private MongoDbOrderRepository tested;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
springDataOrderRepository = mock(SpringDataMongoOrderRepository.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<Order> 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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
## 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, the application will connect to the one of the two databases (configuration in *ddd-layers.properties*)
|
||||
* check `CassandraDbOrderRepository.java` and `MongoDbOrderRepository.java`
|
||||
* switch between the databases by making one of the above beans primary using the `@Primary` annotation
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
CREATE KEYSPACE IF NOT exists order_database
|
||||
WITH replication = {'class':'SimpleStrategy', 'replication_factor':1};
|
||||
|
||||
CREATE TYPE IF NOT EXISTS order_database.orderitementity (productid uuid, price decimal);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS order_database.orderentity(
|
||||
id uuid,
|
||||
status text,
|
||||
orderitementities list<frozen<orderitementity>>,
|
||||
price decimal,
|
||||
primary key(id)
|
||||
);
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
order-mongo-database:
|
||||
image: mongo:3.4.13
|
||||
container_name: order-mongo-db
|
||||
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
|
||||
order-cassandra-database:
|
||||
image: cassandra:3.11.5
|
||||
container_name: order-cassandra-db
|
||||
restart: always
|
||||
ports:
|
||||
- 9042:9042
|
||||
order-cassandra-init:
|
||||
image: cassandra:3.11.5
|
||||
container_name: order-cassandra-db-init
|
||||
depends_on:
|
||||
- order-cassandra-database
|
||||
volumes:
|
||||
- ./cassandra-init.cql:/cassandra-init.cql:ro
|
||||
command: bin/bash -c "echo Initializing cassandra schema... && sleep 30 && cqlsh -u cassandra -p cassandra -f cassandra-init.cql order-cassandra-db"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
db.createUser(
|
||||
{
|
||||
user: "order",
|
||||
pwd: "order",
|
||||
roles: [
|
||||
{
|
||||
role: "readWrite",
|
||||
db: "order-database"
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
|
||||
spring.data.mongodb.host=127.0.0.1
|
||||
spring.data.mongodb.port=27017
|
||||
spring.data.mongodb.database=order-database
|
||||
spring.data.mongodb.username=order
|
||||
spring.data.mongodb.password=order
|
||||
|
||||
spring.data.cassandra.keyspaceName=order_database
|
||||
spring.data.cassandra.username=cassandra
|
||||
spring.data.cassandra.password=cassandra
|
||||
spring.data.cassandra.contactPoints=127.0.0.1
|
||||
spring.data.cassandra.port=9042
|
||||
Reference in New Issue
Block a user