Initial commit

This commit is contained in:
Alexander Molochko
2019-03-17 08:46:15 +02:00
commit c2df4f148f
54 changed files with 1860 additions and 0 deletions

75
.gitignore vendored Normal file
View File

@@ -0,0 +1,75 @@
*/bin/*
*.class
# Package Files #
*.war
*.ear
# Eclipse
.settings/
*.project
*.classpath
.prefs
*.prefs
.metadata/
# Intellij
.idea/
*.iml
*.iws
# Mac
.DS_Store
# Maven
log/
target/
spring-openid/src/main/resources/application.properties
.recommenders/
/spring-hibernate4/nbproject/
spring-security-openid/src/main/resources/application.properties
spring-all/*.log
SpringDataInjectionDemo/.mvn/wrapper/maven-wrapper.properties
spring-call-getters-using-reflection/.mvn/wrapper/maven-wrapper.properties
spring-check-if-a-property-is-null/.mvn/wrapper/maven-wrapper.properties
*.springBeans
20171220-JMeter.csv
.factorypath
dependency-reduced-pom.xml
*.so
*.dylib
*.dll
xml/src/test/resources/example_dom4j_new.xml
xml/src/test/resources/example_dom4j_updated.xml
xml/src/test/resources/example_jaxb_new.xml
core-java-io/hard_link.txt
core-java-io/target_link.txt
core-java/src/main/java/com/baeldung/manifest/MANIFEST.MF
ethereum/logs/
jmeter/src/main/resources/*-JMeter.csv
**/node_modules/
**/dist
**/tmp
**/out-tsc
**/nbproject/
**/nb-configuration.xml
core-scala/.cache-main
core-scala/.cache-tests
persistence-modules/hibernate5/transaction.log
apache-avro/src/main/java/com/baeldung/avro/model/
jta/transaction-logs/
software-security/sql-injection-samples/derby.log
spring-soap/src/main/java/com/baeldung/springsoap/gen/

2
README.md Normal file
View File

@@ -0,0 +1,2 @@
### Relevant Articles:
- [A quick and practical example of Hexagonal Architecture in Java](http://www.baeldung.com/a-quick-and-practical-example-of-hexagonal-architecture-in-java/)

227
pom.xml Normal file
View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung</groupId>
<artifactId>hexagonal</artifactId>
<version>1.0-SNAPSHOT</version>
<description>A quick and practical example of Hexagonal Architecture in Java</description>
<packaging>pom</packaging>
<modules>
<module>store-core</module>
<module>store-persistence</module>
<module>store-email-sender</module>
<module>store-application</module>
</modules>
<dependencies>
<!-- logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${org.hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${org.hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>${org.hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-logger-api</artifactId>
<version>${maven-surefire-plugin.version}</version>
<!-- to get around bug https://github.com/junit-team/junit5/issues/801 -->
<scope>test</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
<configuration>
<executable>maven</executable>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<forkCount>3</forkCount>
<reuseForks>true</reuseForks>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
<exclude>**/*IntTest.java</exclude>
<exclude>**/*LongRunningUnitTest.java</exclude>
<exclude>**/*ManualTest.java</exclude>
<exclude>**/JdbcTest.java</exclude>
<exclude>**/*LiveTest.java</exclude>
</excludes>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit-platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit-jupiter.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.commonjava.maven.plugins
</groupId>
<artifactId>
directory-maven-plugin
</artifactId>
<versionRange>
[0.3.1,)
</versionRange>
<goals>
<goal>directory-of</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-install-plugin
</artifactId>
<versionRange>
[2.5.1,)
</versionRange>
<goals>
<goal>install-file</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<properties>
<junit.version>4.12</junit.version>
<org.hamcrest.version>1.3</org.hamcrest.version>
<mockito.version>2.21.0</mockito.version>
<!-- plugins -->
<maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version>
<maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
<exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
<java.version>1.8</java.version>
<junit-jupiter.version>5.2.0</junit-jupiter.version>
<!-- logging -->
<org.slf4j.version>1.7.21</org.slf4j.version>
<junit-platform.version>1.2.0</junit-platform.version>
<logback.version>1.1.7</logback.version>
</properties>
</project>

89
store-application/pom.xml Normal file
View File

@@ -0,0 +1,89 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>store-application</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<artifactId>hexagonal-architecture</artifactId>
<groupId>com.baeldung</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../hexagonal-architecture</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baeldung</groupId>
<artifactId>store-core</artifactId>
<version>${store.core.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.baeldung</groupId>
<artifactId>store-persistence</artifactId>
<version>${store.core.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.baeldung</groupId>
<artifactId>store-email-sender</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<spring.boot.version>2.1.3.RELEASE</spring.boot.version>
<store.core.version>1.0-SNAPSHOT</store.core.version>
<store.persistence.version>1.0-SNAPSHOT</store.persistence.version>
</properties>
</project>

View File

@@ -0,0 +1,18 @@
package com.baeldung.hexagonal.store.application;
import com.baeldung.hexagonal.store.emailsender.EmailSenderConfig;
import com.baeldung.hexagonal.store.persistence.PersistenceConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@ComponentScan(basePackages = {"com.baeldung.hexagonal.store.application","com.baeldung.hexagonal.store.core"})
@Import({PersistenceConfig.class, EmailSenderConfig.class})
public class StoreApplication {
public static void main(String[] args) {
SpringApplication.run(StoreApplication.class, args);
}
}

View File

@@ -0,0 +1,12 @@
package com.baeldung.hexagonal.store.application.base;
import java.util.List;
import java.util.stream.Collectors;
public interface Mapper<F, T> {
T map(F source);
default List<T> mapList(List<F> sourceList) {
return sourceList.stream().map(this::map).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,44 @@
package com.baeldung.hexagonal.store.application.controller;
import com.baeldung.hexagonal.store.application.base.Mapper;
import com.baeldung.hexagonal.store.application.dto.request.OrderCreateRequestDto;
import com.baeldung.hexagonal.store.application.dto.response.OrderResponseDto;
import com.baeldung.hexagonal.store.core.context.customer.exception.OrderNotFoundException;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import com.baeldung.hexagonal.store.core.context.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/orders")
public class OrderController {
Mapper<Order, OrderResponseDto> orderResponseDtoMapper;
OrderService orderService;
@Autowired
public OrderController(OrderService orderService, Mapper<Order, OrderResponseDto> orderResponseDtoMapper) {
this.orderService = orderService;
this.orderResponseDtoMapper = orderResponseDtoMapper;
}
@GetMapping("/{id}")
public ResponseEntity<OrderResponseDto> getOrder(@PathVariable("id") int id) {
Optional<Order> order = this.orderService.getOrderById(id);
if (order.isPresent()) {
return new ResponseEntity<>(this.orderResponseDtoMapper.map(order.get()), HttpStatus.OK);
} else {
throw new OrderNotFoundException();
}
}
@PostMapping("/customer/{customerId}")
public ResponseEntity<OrderResponseDto> createOrder(@PathVariable("customerId") int customerId, @RequestBody OrderCreateRequestDto requestDto) {
Optional<Order> order = this.orderService.processNewCustomerOrder(customerId, requestDto.getProductQuantityMap());
return new ResponseEntity<>(this.orderResponseDtoMapper.map(order.get()), HttpStatus.OK);
}
}

View File

@@ -0,0 +1,52 @@
package com.baeldung.hexagonal.store.application.controller.exception;
import com.baeldung.hexagonal.store.core.context.customer.exception.CustomerNotFoundException;
import com.baeldung.hexagonal.store.core.context.customer.exception.NotEnoughFundsException;
import com.baeldung.hexagonal.store.core.context.customer.exception.OrderNotFoundException;
import com.baeldung.hexagonal.store.core.context.order.exception.ProductNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@RestControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(value = {ProductNotFoundException.class, CustomerNotFoundException.class})
protected ResponseEntity<JsonResponse> handleNotFoundException(
RuntimeException ex, WebRequest request) {
return new ResponseEntity<>(new JsonResponse(ex.getMessage()), HttpStatus.NOT_FOUND);
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = {OrderNotFoundException.class, NotEnoughFundsException.class})
protected ResponseEntity<JsonResponse> handleBadRequestException(
RuntimeException ex, WebRequest request) {
return new ResponseEntity<>(new JsonResponse(ex.getMessage()), HttpStatus.BAD_REQUEST);
}
private static class JsonResponse {
String message;
public JsonResponse() {
}
public JsonResponse(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
}

View File

@@ -0,0 +1,5 @@
package com.baeldung.hexagonal.store.application.dto.entity;
public class CustomerDto {
}

View File

@@ -0,0 +1,26 @@
package com.baeldung.hexagonal.store.application.dto.mapper;
import com.baeldung.hexagonal.store.application.base.Mapper;
import com.baeldung.hexagonal.store.application.dto.response.OrderProductResponseDto;
import com.baeldung.hexagonal.store.application.dto.response.OrderResponseDto;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProduct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderEntityToDtoMapper implements Mapper<Order, OrderResponseDto> {
@Autowired
Mapper<OrderProduct, OrderProductResponseDto> orderProductResponseDtoMapper;
@Override
public OrderResponseDto map(Order source) {
OrderResponseDto responseDto = new OrderResponseDto();
responseDto.setId(source.getId());
responseDto.setStatus(source.getStatus());
responseDto.setOrderProducts(orderProductResponseDtoMapper.mapList(source.getOrderProducts()));
return responseDto;
}
}

View File

@@ -0,0 +1,28 @@
package com.baeldung.hexagonal.store.application.dto.mapper;
import com.baeldung.hexagonal.store.application.base.Mapper;
import com.baeldung.hexagonal.store.application.dto.response.OrderProductResponseDto;
import com.baeldung.hexagonal.store.application.dto.response.ProductResponseDto;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProduct;
import com.baeldung.hexagonal.store.core.context.order.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderProductEntityToDtoMapper implements Mapper<OrderProduct, OrderProductResponseDto> {
final Mapper<Product, ProductResponseDto> productResponseDtoMapper;
@Autowired
public OrderProductEntityToDtoMapper(Mapper<Product, ProductResponseDto> productResponseDtoMapper) {
this.productResponseDtoMapper = productResponseDtoMapper;
}
@Override
public OrderProductResponseDto map(OrderProduct source) {
OrderProductResponseDto orderProductResponseDto = new OrderProductResponseDto();
orderProductResponseDto.setProduct(this.productResponseDtoMapper.map(source.getProduct()));
orderProductResponseDto.setQuantity(source.getQuantity());
return orderProductResponseDto;
}
}

View File

@@ -0,0 +1,19 @@
package com.baeldung.hexagonal.store.application.dto.mapper;
import com.baeldung.hexagonal.store.application.base.Mapper;
import com.baeldung.hexagonal.store.application.dto.response.ProductResponseDto;
import com.baeldung.hexagonal.store.core.context.order.entity.Product;
import org.springframework.stereotype.Component;
@Component
public class ProductEntityToDtoMapper implements Mapper<Product, ProductResponseDto> {
@Override
public ProductResponseDto map(Product source) {
ProductResponseDto responseDto = new ProductResponseDto();
responseDto.setId(source.getId());
responseDto.setName(source.getName());
responseDto.setPrice(source.getPrice());
return responseDto;
}
}

View File

@@ -0,0 +1,44 @@
package com.baeldung.hexagonal.store.application.dto.request;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class OrderCreateRequestDto {
List<OrderProductDescriptor> productIds;
public List<OrderProductDescriptor> getProductIds() {
return productIds;
}
public void setProductIds(List<OrderProductDescriptor> productIds) {
this.productIds = productIds;
}
public Map<Long, Integer> getProductQuantityMap() {
return this.productIds
.stream()
.collect(Collectors.toMap(OrderProductDescriptor::getProductId, OrderProductDescriptor::getQuantity));
}
public static class OrderProductDescriptor {
private long productId;
private int quantity;
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public long getProductId() {
return productId;
}
public void setProductId(long productId) {
this.productId = productId;
}
}
}

View File

@@ -0,0 +1,23 @@
package com.baeldung.hexagonal.store.application.dto.response;
public class OrderProductResponseDto {
private Integer quantity;
private ProductResponseDto product;
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public ProductResponseDto getProduct() {
return product;
}
public void setProduct(ProductResponseDto product) {
this.product = product;
}
}

View File

@@ -0,0 +1,33 @@
package com.baeldung.hexagonal.store.application.dto.response;
import java.util.List;
public class OrderResponseDto {
private String status;
private Long id;
private List<OrderProductResponseDto> orderProducts;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<OrderProductResponseDto> getOrderProducts() {
return orderProducts;
}
public void setOrderProducts(List<OrderProductResponseDto> orderProducts) {
this.orderProducts = orderProducts;
}
}

View File

@@ -0,0 +1,31 @@
package com.baeldung.hexagonal.store.application.dto.response;
public class ProductResponseDto {
private String name;
private Double price;
private Long id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}

View File

@@ -0,0 +1 @@
spring.h2.console.enabled=true

View File

@@ -0,0 +1,16 @@
package org.baeldung;
import com.baeldung.hexagonal.store.application.StoreApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StoreApplication.class)
public class SpringContextIntegrationTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}

46
store-core/pom.xml Normal file
View File

@@ -0,0 +1,46 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>store-core</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<parent>
<artifactId>hexagonal-architecture</artifactId>
<groupId>com.baeldung</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../hexagonal-architecture</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.context.version}</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>${javax.persistence.version}</version>
</dependency>
</dependencies>
<properties>
<lombok.version>1.16.8</lombok.version>
<spring.context.version>5.1.5.RELEASE</spring.context.version>
<javax.persistence.version>2.2</javax.persistence.version>
</properties>
</project>

View File

@@ -0,0 +1,91 @@
package com.baeldung.hexagonal.store.core.context.customer.entity;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Customer implements StoreCustomer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
String firstname, lastname;
String email;
Double balance;
@OneToMany(
mappedBy = "customer",
cascade = CascadeType.ALL,
orphanRemoval = true
)
List<Order> orders = new ArrayList<>();
@Override
public boolean isNegativeBalanceAllowed() {
return true;
}
@Override
public Double getBalance() {
return balance;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public Long getId() {
return id;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public void setBalance(Double balance) {
this.balance = balance;
}
public void withdrawFunds(Double sum) {
this.balance -= sum;
}
public boolean hasEnoughFunds(Double amount) {
return this.balance >= amount;
}
public void topUpFunds(Double sum) {
this.balance += sum;
}
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
public void addOrder(Order order) {
this.orders.add(order);
}
}

View File

@@ -0,0 +1,7 @@
package com.baeldung.hexagonal.store.core.context.customer.entity;
public interface StoreCustomer {
boolean isNegativeBalanceAllowed();
Double getBalance();
}

View File

@@ -0,0 +1,7 @@
package com.baeldung.hexagonal.store.core.context.customer.exception;
public class CustomerNotFoundException extends RuntimeException {
public CustomerNotFoundException() {
super("Customer not found!");
}
}

View File

@@ -0,0 +1,7 @@
package com.baeldung.hexagonal.store.core.context.customer.exception;
public class NotEnoughFundsException extends RuntimeException {
public NotEnoughFundsException() {
super("Not enough funds!");
}
}

View File

@@ -0,0 +1,7 @@
package com.baeldung.hexagonal.store.core.context.customer.exception;
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException() {
super("Order not found!");
}
}

View File

@@ -0,0 +1,13 @@
package com.baeldung.hexagonal.store.core.context.customer.infrastructure;
import com.baeldung.hexagonal.store.core.context.customer.entity.Customer;
import java.util.Optional;
public interface CustomerDataStore {
Customer save(Customer customer);
Optional<Customer> findById(Long customerId);
Iterable<Customer> findAll();
}

View File

@@ -0,0 +1,7 @@
package com.baeldung.hexagonal.store.core.context.customer.service;
import com.baeldung.hexagonal.store.core.context.customer.entity.Customer;
public interface CustomerService {
Iterable<Customer> getAllCustomers();
}

View File

@@ -0,0 +1,21 @@
package com.baeldung.hexagonal.store.core.context.customer.service;
import com.baeldung.hexagonal.store.core.context.customer.entity.Customer;
import com.baeldung.hexagonal.store.core.context.customer.infrastructure.CustomerDataStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerServiceImpl implements CustomerService {
private CustomerDataStore customerDataStore;
@Autowired
public CustomerServiceImpl(CustomerDataStore customerDataStore) {
this.customerDataStore = customerDataStore;
}
@Override
public Iterable<Customer> getAllCustomers() {
return this.customerDataStore.findAll();
}
}

View File

@@ -0,0 +1,76 @@
package com.baeldung.hexagonal.store.core.context.order.entity;
import com.baeldung.hexagonal.store.core.context.customer.entity.Customer;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "orders")
public class Order {
private String status;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
@OneToMany(mappedBy = "id.order", cascade = CascadeType.ALL)
private List<OrderProduct> orderProducts = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;
@Transient
public Double getTotalOrderPrice() {
return getOrderProducts()
.stream()
.reduce(
0D,
(aDouble, orderProduct) -> orderProduct.getTotalPrice(),
(aDouble, aDouble2) -> aDouble + aDouble2);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<OrderProduct> getOrderProducts() {
return orderProducts;
}
public void setOrderProducts(List<OrderProduct> orderProducts) {
this.orderProducts = orderProducts;
}
public void addOrderProduct(OrderProduct product) {
this.orderProducts.add(product);
}
@Transient
public int getNumberOfProducts() {
return this.orderProducts.size();
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}

View File

@@ -0,0 +1,85 @@
package com.baeldung.hexagonal.store.core.context.order.entity;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Transient;
@Entity
public class OrderProduct {
@EmbeddedId
private OrderProductId id;
@Column(nullable = false)
private Integer quantity;
public OrderProduct() {
super();
}
public OrderProduct(Order order, Product product, Integer quantity) {
id = new OrderProductId();
id.setOrder(order);
id.setProduct(product);
this.quantity = quantity;
}
@Transient
public Product getProduct() {
return this.id.getProduct();
}
@Transient
public Double getTotalPrice() {
return getProduct().getPrice() * getQuantity();
}
public OrderProductId getId() {
return id;
}
public void setId(OrderProductId id) {
this.id = id;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
OrderProduct other = (OrderProduct) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,97 @@
package com.baeldung.hexagonal.store.core.context.order.entity;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.io.Serializable;
@Embeddable
public class OrderProductId implements Serializable {
private static final long serialVersionUID = 476151177562655457L;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
public OrderProductId(Order order, Product product) {
this.order = order;
this.product = product;
}
public OrderProductId() {
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((order.getId() == null)
? 0
: order
.getId()
.hashCode());
result = prime * result + ((product.getId() == null)
? 0
: product
.getId()
.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
OrderProductId other = (OrderProductId) obj;
if (order == null) {
if (other.order != null) {
return false;
}
} else if (!order.equals(other.order)) {
return false;
}
if (product == null) {
if (other.product != null) {
return false;
}
} else if (!product.equals(other.product)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,40 @@
package com.baeldung.hexagonal.store.core.context.order.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
private String name;
private Double price;
public Product() {
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
}

View File

@@ -0,0 +1,7 @@
package com.baeldung.hexagonal.store.core.context.order.exception;
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException() {
super("Product not found!");
}
}

View File

@@ -0,0 +1,5 @@
package com.baeldung.hexagonal.store.core.context.order.infrastructure;
public interface EmailNotificationSender {
void sendEmailMessage(String targetEmail, String subject, String body);
}

View File

@@ -0,0 +1,11 @@
package com.baeldung.hexagonal.store.core.context.order.infrastructure;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import java.util.Optional;
public interface OrderDataStore {
Order save(Order orderProduct);
Optional<Order> findById(Long orderId);
Iterable<Order> findAll();
}

View File

@@ -0,0 +1,12 @@
package com.baeldung.hexagonal.store.core.context.order.infrastructure;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProductId;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProduct;
import java.util.Optional;
public interface OrderProductDataStore {
OrderProduct save(OrderProduct orderProduct);
Optional<OrderProduct> findById(OrderProductId orderProductPkId);
}

View File

@@ -0,0 +1,11 @@
package com.baeldung.hexagonal.store.core.context.order.infrastructure;
import com.baeldung.hexagonal.store.core.context.order.entity.Product;
import java.util.Optional;
public interface ProductDataStore {
Product save(Product product);
Optional<Product> findById(Long productId);
}

View File

@@ -0,0 +1,17 @@
package com.baeldung.hexagonal.store.core.context.order.service;
import com.baeldung.hexagonal.store.core.context.customer.exception.CustomerNotFoundException;
import com.baeldung.hexagonal.store.core.context.customer.exception.NotEnoughFundsException;
import com.baeldung.hexagonal.store.core.context.order.exception.ProductNotFoundException;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import java.util.Map;
import java.util.Optional;
public interface OrderService {
Iterable<Order> getAllOrders();
Optional<Order> getOrderById(int id);
Optional<Order> processNewCustomerOrder(long customerId, Map<Long, Integer> productQuantityMap) throws CustomerNotFoundException, ProductNotFoundException, NotEnoughFundsException;
}

View File

@@ -0,0 +1,84 @@
package com.baeldung.hexagonal.store.core.context.order.service;
import com.baeldung.hexagonal.store.core.context.customer.entity.Customer;
import com.baeldung.hexagonal.store.core.context.customer.exception.CustomerNotFoundException;
import com.baeldung.hexagonal.store.core.context.customer.exception.NotEnoughFundsException;
import com.baeldung.hexagonal.store.core.context.customer.infrastructure.CustomerDataStore;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.EmailNotificationSender;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.OrderDataStore;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProduct;
import com.baeldung.hexagonal.store.core.context.order.entity.Product;
import com.baeldung.hexagonal.store.core.context.order.exception.ProductNotFoundException;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.ProductDataStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class OrderServiceImpl implements OrderService {
private CustomerDataStore customerDataStore;
private OrderDataStore orderDataStore;
private ProductDataStore productDataStore;
private EmailNotificationSender emailNotificationSender;
@Autowired
public OrderServiceImpl(CustomerDataStore customerDataStore, OrderDataStore orderDataStore, ProductDataStore productDataStore, EmailNotificationSender emailNotificationSender) {
this.customerDataStore = customerDataStore;
this.orderDataStore = orderDataStore;
this.productDataStore = productDataStore;
this.emailNotificationSender = emailNotificationSender;
}
@Override
public Iterable<Order> getAllOrders() {
return orderDataStore.findAll();
}
@Override
public Optional<Order> getOrderById(int id) {
return this.orderDataStore.findById((long) id);
}
@Override
public Optional<Order> processNewCustomerOrder(long customerId, Map<Long, Integer> productQuantityMap)
throws CustomerNotFoundException, ProductNotFoundException, NotEnoughFundsException {
Optional<Customer> customer = this.customerDataStore.findById(customerId);
if (!customer.isPresent()) {
throw new CustomerNotFoundException();
}
Customer existingCustomer = customer.get();
Order order = new Order();
List<OrderProduct> orderProducts = new ArrayList<>();
for (Map.Entry<Long, Integer> productEntry : productQuantityMap.entrySet()) {
Optional<Product> product = productDataStore.findById(productEntry.getKey());
if (!product.isPresent()) {
throw new ProductNotFoundException();
}
OrderProduct orderProduct = new OrderProduct(order, product.get(), productEntry.getValue());
orderProducts.add(orderProduct);
}
order.setOrderProducts(orderProducts);
Double finalPrice = order.getTotalOrderPrice();
if (!existingCustomer.hasEnoughFunds(finalPrice) && !existingCustomer.isNegativeBalanceAllowed()) {
throw new NotEnoughFundsException();
}
existingCustomer.withdrawFunds(finalPrice);
order.setStatus("Completed");
order.setCustomer(existingCustomer);
this.orderDataStore.save(order);
this.notifyCustomerAboutNewOrder(existingCustomer, order);
return Optional.of(order);
}
private void notifyCustomerAboutNewOrder(Customer customer, Order order) {
this.emailNotificationSender.sendEmailMessage(
customer.getEmail(),
"New order " + order.getId() + " was created in the Baeldung store",
"Order total is: " + order.getTotalOrderPrice());
}
}

View File

@@ -0,0 +1,53 @@
package com.baeldung.hexagonal.store.core;
import com.baeldung.hexagonal.store.core.context.customer.infrastructure.CustomerDataStore;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.EmailNotificationSender;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.OrderDataStore;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.ProductDataStore;
import com.baeldung.hexagonal.store.core.context.order.service.OrderServiceImpl;
import org.junit.Test;
import java.util.Collections;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MockDataStoreUnitTest {
@Test
public void givenMockedRepo_whenCalledGetAll_thenReturnMockedData() {
// Arrange
EmailNotificationSender emailSender = mock(EmailNotificationSender.class);
OrderDataStore orderDataStore = mock(OrderDataStore.class);
CustomerDataStore customerDataStore = mock(CustomerDataStore.class);
ProductDataStore productDataStore = mock(ProductDataStore.class);
OrderServiceImpl orderService = new OrderServiceImpl(customerDataStore, orderDataStore, productDataStore, emailSender);
long orderId = 2323L;
String status = "Pending";
Order order = createFakeOrder(orderId, status);
when(orderDataStore.findAll()).thenReturn(Collections.singletonList(order));
// Act
Iterable<Order> orders = orderService.getAllOrders();
// Assert
assertTrue(orders.iterator().hasNext());
Order firstItem = orders.iterator().next();
assertNotNull(firstItem);
assertEquals(orderId, (long) firstItem.getId());
assertEquals(status, firstItem.getStatus());
}
private static Order createFakeOrder(long id, String status) {
Order order = new Order();
order.setId(id);
order.setStatus(status);
return order;
}
}

View File

@@ -0,0 +1,38 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>store-email-sender</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>hexagonal-architecture</artifactId>
<groupId>com.baeldung</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../hexagonal-architecture</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>com.baeldung</groupId>
<artifactId>store-core</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>${javax.mail.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.context.version}</version>
</dependency>
</dependencies>
<properties>
<spring.context.version>5.1.5.RELEASE</spring.context.version>
<javax.mail.version>1.5.5</javax.mail.version>
</properties>
</project>

View File

@@ -0,0 +1,9 @@
package com.baeldung.hexagonal.store.emailsender;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"com.baeldung.hexagonal.store.emailsender"})
public class EmailSenderConfig {
}

View File

@@ -0,0 +1,42 @@
package com.baeldung.hexagonal.store.emailsender;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.EmailNotificationSender;
import org.springframework.stereotype.Component;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
@Component
public class SMTPEmailSender implements EmailNotificationSender {
public static final String FROM_MAIL = "example@baeldung.com";
@Override
public void sendEmailMessage(String targetEmail, String subject, String body) {
try {
Session session = createSession();
MimeMessage message = new MimeMessage(session);
message.setContent(body, "text/html; charset=utf-8");
message.setFrom(new InternetAddress(FROM_MAIL));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(targetEmail));
message.setSubject(subject);
Transport.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}
private static Session createSession() {
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", "smtp.baeldung.com");
props.put("mail.smtp.port", "587");
return Session.getInstance(props);
}
}

62
store-persistence/pom.xml Normal file
View File

@@ -0,0 +1,62 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>store-persistence</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>hexagonal-architecture</artifactId>
<groupId>com.baeldung</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../hexagonal-architecture</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>${faker.version}</version>
</dependency>
<dependency>
<groupId>com.baeldung</groupId>
<artifactId>store-core</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<spring.boot.version>2.1.3.RELEASE</spring.boot.version>
<faker.version>0.17.2</faker.version>
</properties>
</project>

View File

@@ -0,0 +1,16 @@
package com.baeldung.hexagonal.store.persistence;
import com.baeldung.hexagonal.store.persistence.config.EntityConfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.baeldung.hexagonal.store.persistence")
@Import({EntityConfig.class})
@ComponentScan(basePackages = "com.baeldung.hexagonal.store.persistence")
public class PersistenceConfig {
}

View File

@@ -0,0 +1,9 @@
package com.baeldung.hexagonal.store.persistence.config;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EntityScan(basePackages = {"com.baeldung.hexagonal.store.core"})
public class EntityConfig {
}

View File

@@ -0,0 +1,91 @@
package com.baeldung.hexagonal.store.persistence.listener;
import com.baeldung.hexagonal.store.core.context.customer.entity.Customer;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProduct;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProductId;
import com.baeldung.hexagonal.store.core.context.order.entity.Product;
import com.baeldung.hexagonal.store.persistence.repo.customer.CustomerRepository;
import com.baeldung.hexagonal.store.persistence.repo.order.OrderProductRepository;
import com.baeldung.hexagonal.store.persistence.repo.order.OrderRepository;
import com.baeldung.hexagonal.store.persistence.repo.product.ProductRepository;
import com.github.javafaker.Faker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class StartupFakeDataSeeder implements ApplicationListener<ApplicationReadyEvent> {
public static final int FAKE_CUSTOMER_COUNT = 10;
public static final int FAKE_PRODUCT_COUNT = 40;
private final OrderRepository orderRepository;
private final OrderProductRepository orderProductRepository;
private final ProductRepository productRepository;
private final CustomerRepository customerRepository;
private Faker faker = new Faker();
@Autowired
public StartupFakeDataSeeder(CustomerRepository customerRepository, OrderRepository orderRepository, OrderProductRepository orderProductRepository, ProductRepository productRepository) {
this.customerRepository = customerRepository;
this.orderRepository = orderRepository;
this.orderProductRepository = orderProductRepository;
this.productRepository = productRepository;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
seedData();
}
private void seedData() {
List<Product> products = generateRandomProducts();
for (int i = 0; i < FAKE_CUSTOMER_COUNT; i++) {
Customer customer = new Customer();
customer.setFirstname(faker.name().firstName());
customer.setLastname(faker.name().lastName());
customer.setBalance(faker.number().randomDouble(3, 0, 2000));
customer.setEmail(faker.internet().emailAddress());
Order order = generateRandomOrder(products);
order.setCustomer(customer);
order.setStatus(faker.code().asin());
customer.addOrder(order);
customerRepository.save(customer);
}
}
private Order generateRandomOrder(List<Product> products) {
Order order = new Order();
long orderItems = faker.number().randomNumber(1, false);
List<OrderProduct> orderProducts = new ArrayList<>();
List<Product> addedProducts = new ArrayList<>();
for (int i = 0; i < orderItems; i++) {
Product product = null;
while (product == null || addedProducts.contains(product)) {
product = products.get(faker.random().nextInt(0, products.size() - 1));
}
OrderProduct orderProduct = new OrderProduct();
orderProduct.setId(new OrderProductId(order, product));
addedProducts.add(product);
orderProduct.setQuantity((int) faker.number().randomNumber(1, false));
orderProducts.add(orderProduct);
}
order.setOrderProducts(orderProducts);
return order;
}
private List<Product> generateRandomProducts() {
List<Product> products = new ArrayList<>();
for (int i = 0; i < FAKE_PRODUCT_COUNT; i++) {
Product product = new Product();
product.setName(faker.commerce().productName());
product.setPrice(faker.random().nextDouble() * 100);
productRepository.save(product);
products.add(product);
}
return products;
}
}

View File

@@ -0,0 +1,9 @@
package com.baeldung.hexagonal.store.persistence.repo.customer;
import com.baeldung.hexagonal.store.core.context.customer.entity.Customer;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
}

View File

@@ -0,0 +1,29 @@
package com.baeldung.hexagonal.store.persistence.repo.customer;
import com.baeldung.hexagonal.store.core.context.customer.entity.Customer;
import com.baeldung.hexagonal.store.core.context.customer.infrastructure.CustomerDataStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class CustomerRepositoryImpl implements CustomerDataStore {
@Autowired
private CustomerRepository customerRepository;
@Override
public Customer save(Customer customer) {
return customerRepository.save(customer);
}
@Override
public Optional<Customer> findById(Long customerId) {
return customerRepository.findById(customerId);
}
@Override
public Iterable<Customer> findAll() {
return customerRepository.findAll();
}
}

View File

@@ -0,0 +1,10 @@
package com.baeldung.hexagonal.store.persistence.repo.order;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProduct;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProductId;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderProductRepository extends CrudRepository<OrderProduct, OrderProductId> {
}

View File

@@ -0,0 +1,25 @@
package com.baeldung.hexagonal.store.persistence.repo.order;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProduct;
import com.baeldung.hexagonal.store.core.context.order.entity.OrderProductId;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.OrderProductDataStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class OrderProductRepositoryImpl implements OrderProductDataStore {
@Autowired
private OrderProductRepository orderProductRepository;
@Override
public OrderProduct save(OrderProduct orderProduct) {
return this.orderProductRepository.save(orderProduct);
}
@Override
public Optional<OrderProduct> findById(OrderProductId orderProductPkId) {
return this.orderProductRepository.findById(orderProductPkId);
}
}

View File

@@ -0,0 +1,9 @@
package com.baeldung.hexagonal.store.persistence.repo.order;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderRepository extends CrudRepository<Order, Long> {
}

View File

@@ -0,0 +1,29 @@
package com.baeldung.hexagonal.store.persistence.repo.order;
import com.baeldung.hexagonal.store.core.context.order.entity.Order;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.OrderDataStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class OrderRepositoryImpl implements OrderDataStore {
@Autowired
private OrderRepository orderRepository;
@Override
public Order save(Order order) {
return this.orderRepository.save(order);
}
@Override
public Optional<Order> findById(Long orderId) {
return this.orderRepository.findById(orderId);
}
@Override
public Iterable<Order> findAll() {
return this.orderRepository.findAll();
}
}

View File

@@ -0,0 +1,9 @@
package com.baeldung.hexagonal.store.persistence.repo.product;
import com.baeldung.hexagonal.store.core.context.order.entity.Product;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductRepository extends CrudRepository<Product, Long> {
}

View File

@@ -0,0 +1,24 @@
package com.baeldung.hexagonal.store.persistence.repo.product;
import com.baeldung.hexagonal.store.core.context.order.entity.Product;
import com.baeldung.hexagonal.store.core.context.order.infrastructure.ProductDataStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class ProductRepositoryImpl implements ProductDataStore {
@Autowired
private ProductRepository productRepository;
@Override
public Product save(Product product) {
return this.productRepository.save(product);
}
@Override
public Optional<Product> findById(Long productId) {
return this.productRepository.findById(productId);
}
}