Cleaning up

This commit is contained in:
Kenny Bastani
2016-12-20 17:30:24 -08:00
parent 878e40a2e5
commit 2af2788bc7
154 changed files with 2507 additions and 451 deletions

28
.gitmodules vendored
View File

@@ -1,21 +1,21 @@
[submodule "account-parent/functions/account-created-function"]
path = account-parent/functions/account-created-function
[submodule "account/functions/account-created-function"]
path = account/functions/account-created-function
url = https://github.com/event-stream-processing-microservices/account-created-function.git
[submodule "account-parent/functions/account-confirmed-function"]
path = account-parent/functions/account-confirmed-function
[submodule "account/functions/account-confirmed-function"]
path = account/functions/account-confirmed-function
url = https://github.com/event-stream-processing-microservices/account-confirmed-function.git
[submodule "account-parent/functions/account-suspended-function"]
path = account-parent/functions/account-suspended-function
[submodule "account/functions/account-suspended-function"]
path = account/functions/account-suspended-function
url = https://github.com/event-stream-processing-microservices/account-suspended-function.git
[submodule "account-parent/functions/account-activated-function"]
path = account-parent/functions/account-activated-function
[submodule "account/functions/account-activated-function"]
path = account/functions/account-activated-function
url = https://github.com/event-stream-processing-microservices/account-activated-function.git
[submodule "account-parent/functions/account-archived-function"]
path = account-parent/functions/account-archived-function
[submodule "account/functions/account-archived-function"]
path = account/functions/account-archived-function
url = https://github.com/event-stream-processing-microservices/account-archived-function.git
[submodule "account-parent/functions/account-unsuspended-function"]
path = account-parent/functions/account-unsuspended-function
[submodule "account/functions/account-unsuspended-function"]
path = account/functions/account-unsuspended-function
url = https://github.com/event-stream-processing-microservices/account-unsuspended-function.git
[submodule "account-parent/functions/account-unarchived-function"]
path = account-parent/functions/account-unarchived-function
[submodule "account/functions/account-unarchived-function"]
path = account/functions/account-unarchived-function
url = https://github.com/event-stream-processing-microservices/account-unarchived-function.git

View File

@@ -7,11 +7,11 @@
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>│   │   ├── account-web</name>
<name>account-web</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>account-parent</artifactId>
<artifactId>account</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>

View File

@@ -7,11 +7,11 @@
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>│   │   └── account-worker</name>
<name>account-worker</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>account-parent</artifactId>
<artifactId>account</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
@@ -46,17 +46,17 @@
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>1.1.1.RELEASE</version>
<version>${spring-statemachine-core.version}</version>
</dependency>
<dependency>
<groupId>org.kbastani</groupId>
<artifactId>spring-boot-starter-aws-lambda</artifactId>
<version>1.0-SNAPSHOT</version>
<version>${spring-boot-starter-aws-lambda.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sts</artifactId>
<version>1.11.67</version>
<version>${aws-java-sdk-sts.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
@@ -66,7 +66,7 @@
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.2.0</version>
<version>${json-path.version}</version>
</dependency>
</dependencies>
@@ -75,7 +75,7 @@
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>
<version>1.11.67</version>
<version>${aws-java-sdk-sts.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

View File

@@ -4,15 +4,15 @@
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>account-parent</artifactId>
<artifactId>account</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>│   ├── account-parent</name>
<name>account</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>event-stream-processing-parent</artifactId>
<artifactId>event-stream-processing-microservices</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>

View File

@@ -1,3 +0,0 @@
# Inventory Microservice
This is the parent project that contains the modules of a microservice for the _Inventory_ domain context.

View File

@@ -1,39 +0,0 @@
<?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>
<artifactId>inventory-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>│   │   └── inventory-web</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>inventory-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,12 +0,0 @@
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class InventoryServiceApplication {
public static void main(String[] args) {
SpringApplication.run(InventoryServiceApplication.class, args);
}
}

View File

@@ -1,16 +0,0 @@
package demo;
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
public class InventoryServiceApplicationTests {
@Test
public void contextLoads() {
}
}

View File

@@ -1,29 +0,0 @@
<?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>
<artifactId>inventory-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>│   ├── inventory-parent</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>event-stream-processing-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modules>
<module>inventory-web</module>
</modules>
<dependencies>
</dependencies>
</project>

View File

@@ -1,3 +0,0 @@
# Order Microservice
This is the parent project that contains the modules of a microservice for the _Order_ domain context.

View File

@@ -1,39 +0,0 @@
<?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>
<artifactId>invoice-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>│   │   └── invoice-web</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>invoice-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,12 +0,0 @@
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class InvoiceServiceApplication {
public static void main(String[] args) {
SpringApplication.run(InvoiceServiceApplication.class, args);
}
}

View File

@@ -1,16 +0,0 @@
package demo;
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
public class InvoiceServiceApplicationTests {
@Test
public void contextLoads() {
}
}

View File

@@ -1,29 +0,0 @@
<?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>
<artifactId>invoice-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>│   ├── invoice-parent</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>event-stream-processing-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modules>
<module>invoice-web</module>
</modules>
<dependencies>
</dependencies>
</project>

View File

@@ -1,39 +0,0 @@
<?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>
<artifactId>order-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>│   │   └── order-web</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>order-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

15
order/README.md Normal file
View File

@@ -0,0 +1,15 @@
# Order Microservice
This is the parent project that contains modules of a microservice deployment for the _Order_ domain context. The two modules contained in this project are separated into separate deployment artifacts, one for synchronous HTTP-based interactions and one for asynchronous AMQP-based messaging.
## Order Web
The `order-web` module is a web application that produces a REST API that can be used by consumers to interact with and manage domain objects in the `Order` context. _Domain Events_ can be triggered directly over HTTP, and will also be produced in the response to actions that alter the state of the `Order` object. This web service also provides built-in hypermedia support for looking up the event logs on an aggregate domain object.
## Order Worker
The `order-worker` module is a event stream processing application that listens for `Order` domain events as AMQP messages. The domain events that are generated by the `order-web` application are processed in this module. The worker is responsible for durable transaction processing for workflows that are required to coordinate asynchronously with applications residing in other domain contexts.
## Work-in-progress
This microservice is unfinished. If you're looking for a more polished and complete reference, please take a look at the _Account_ module in the parent project.

71
order/order-web/pom.xml Normal file
View File

@@ -0,0 +1,71 @@
<?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>
<artifactId>order-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>order-web</name>
<parent>
<groupId>org.kbastani</groupId>
<artifactId>order</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,100 @@
package demo.address;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class Address implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String street1, street2, state, city, country;
private Integer zipCode;
@Enumerated(EnumType.STRING)
private AddressType addressType;
public Address() {
}
public Address(String street1, String street2, String state,
String city, String country, Integer zipCode) {
this.street1 = street1;
this.street2 = street2;
this.state = state;
this.city = city;
this.country = country;
this.zipCode = zipCode;
}
public String getStreet1() {
return street1;
}
public void setStreet1(String street1) {
this.street1 = street1;
}
public String getStreet2() {
return street2;
}
public void setStreet2(String street2) {
this.street2 = street2;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public Integer getZipCode() {
return zipCode;
}
public void setZipCode(Integer zipCode) {
this.zipCode = zipCode;
}
public AddressType getAddressType() {
return addressType;
}
public void setAddressType(AddressType addressType) {
this.addressType = addressType;
}
@Override
public String toString() {
return "Address{" +
"street1='" + street1 + '\'' +
", street2='" + street2 + '\'' +
", state='" + state + '\'' +
", city='" + city + '\'' +
", country='" + country + '\'' +
", zipCode=" + zipCode +
", addressType=" + addressType +
'}';
}
}

View File

@@ -0,0 +1,6 @@
package demo.address;
public enum AddressType {
SHIPPING,
BILLING
}

View File

@@ -0,0 +1,46 @@
package demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Arrays;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public JedisConnectionFactory redisConnectionFactory(
@Value("${spring.redis.port}") Integer redisPort,
@Value("${spring.redis.host}") String redisHost) {
JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
redisConnectionFactory.setHostName(redisHost);
redisConnectionFactory.setPort(redisPort);
return redisConnectionFactory;
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory cf) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(cf);
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(50000);
cacheManager.setCacheNames(Arrays.asList("orders", "order-events"));
cacheManager.setUsePrefix(true);
return cacheManager;
}
}

View File

@@ -0,0 +1,13 @@
package demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* Enable JPA auditing on an empty configuration class to disable auditing on
*
*/
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}

View File

@@ -0,0 +1,10 @@
package demo.config;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBinding(Source.class)
public class StreamConfig {
}

View File

@@ -0,0 +1,36 @@
package demo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.Collections;
import java.util.List;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
private ObjectMapper objectMapper;
public WebMvcConfig(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
converters.add(converter);
}
@Bean
protected RestTemplate restTemplate(ObjectMapper objectMapper) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
return new RestTemplate(Collections.singletonList(converter));
}
}

View File

@@ -0,0 +1,48 @@
package demo.domain;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.hateoas.ResourceSupport;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity extends ResourceSupport implements Serializable {
@CreatedDate
private Long createdAt;
@LastModifiedDate
private Long lastModified;
public BaseEntity() {
}
public Long getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Long createdAt) {
this.createdAt = createdAt;
}
public Long getLastModified() {
return lastModified;
}
public void setLastModified(Long lastModified) {
this.lastModified = lastModified;
}
@Override
public String toString() {
return "BaseEntity{" +
"createdAt=" + createdAt +
", lastModified=" + lastModified +
'}';
}
}

View File

@@ -0,0 +1,6 @@
package demo.event;
public enum ConsistencyModel {
BASE,
ACID
}

View File

@@ -0,0 +1,39 @@
package demo.event;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/v1")
public class EventController {
private final EventService eventService;
public EventController(EventService eventService) {
this.eventService = eventService;
}
@PostMapping(path = "/events/{id}")
public ResponseEntity createEvent(@RequestBody OrderEvent event, @PathVariable Long id) {
return Optional.ofNullable(eventService.createEvent(id, event, ConsistencyModel.ACID))
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
.orElseThrow(() -> new IllegalArgumentException("Event creation failed"));
}
@PutMapping(path = "/events/{id}")
public ResponseEntity updateEvent(@RequestBody OrderEvent event, @PathVariable Long id) {
return Optional.ofNullable(eventService.updateEvent(id, event))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
.orElseThrow(() -> new IllegalArgumentException("Event update failed"));
}
@GetMapping(path = "/events/{id}")
public ResponseEntity getEvent(@PathVariable Long id) {
return Optional.ofNullable(eventService.getEvent(id))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
}

View File

@@ -0,0 +1,10 @@
package demo.event;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
public interface EventRepository extends JpaRepository<OrderEvent, Long> {
Page<OrderEvent> findOrderEventsByOrderId(@Param("orderId") Long orderId, Pageable pageable);
}

View File

@@ -0,0 +1,214 @@
package demo.event;
import demo.order.Order;
import demo.order.OrderController;
import org.apache.log4j.Logger;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.data.domain.PageRequest;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.Resource;
import org.springframework.http.RequestEntity;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
/**
* The {@link EventService} provides transactional service methods for {@link OrderEvent}
* entities of the Order Service. Order domain events are generated with a {@link OrderEventType},
* and action logs are appended to the {@link OrderEvent}.
*
* @author kbastani
*/
@Service
@CacheConfig(cacheNames = {"order-events"})
public class EventService {
private final Logger log = Logger.getLogger(EventService.class);
private final EventRepository eventRepository;
private final Source orderStreamSource;
private final RestTemplate restTemplate;
public EventService(EventRepository eventRepository, Source orderStreamSource, RestTemplate restTemplate) {
this.eventRepository = eventRepository;
this.orderStreamSource = orderStreamSource;
this.restTemplate = restTemplate;
}
/**
* Create a new {@link OrderEvent} and append it to the event log of the referenced {@link Order}.
* After the {@link OrderEvent} has been persisted, send the event to the order stream. Events can
* be raised as a blocking or non-blocking operation depending on the {@link ConsistencyModel}.
*
* @param orderId is the unique identifier for the {@link Order}
* @param event is the {@link OrderEvent} to create
* @param consistencyModel is the desired consistency model for the response
* @return an {@link OrderEvent} that has been appended to the {@link Order}'s event log
*/
public OrderEvent createEvent(Long orderId, OrderEvent event, ConsistencyModel consistencyModel) {
event = createEvent(orderId, event);
return raiseEvent(event, consistencyModel);
}
/**
* Raise an {@link OrderEvent} that attempts to transition the state of an {@link Order}.
*
* @param event is an {@link OrderEvent} that will be raised
* @param consistencyModel is the consistency model for this request
* @return an {@link OrderEvent} that has been appended to the {@link Order}'s event log
*/
public OrderEvent raiseEvent(OrderEvent event, ConsistencyModel consistencyModel) {
switch (consistencyModel) {
case BASE:
asyncRaiseEvent(event);
break;
case ACID:
event = raiseEvent(event);
break;
}
return event;
}
/**
* Raise an asynchronous {@link OrderEvent} by sending an AMQP message to the order stream. Any
* state changes will be applied to the {@link Order} outside of the current HTTP request context.
* <p>
* Use this operation when a workflow can be processed asynchronously outside of the current HTTP
* request context.
*
* @param event is an {@link OrderEvent} that will be raised
*/
private void asyncRaiseEvent(OrderEvent event) {
// Append the order event to the stream
orderStreamSource.output()
.send(MessageBuilder
.withPayload(getOrderEventResource(event))
.build());
}
/**
* Raise a synchronous {@link OrderEvent} by sending a HTTP request to the order stream. The response
* is a blocking operation, which ensures that the result of a multi-step workflow will not return until
* the transaction reaches a consistent state.
* <p>
* Use this operation when the result of a workflow must be returned within the current HTTP request context.
*
* @param event is an {@link OrderEvent} that will be raised
* @return an {@link OrderEvent} which contains the consistent state of an {@link Order}
*/
private OrderEvent raiseEvent(OrderEvent event) {
try {
// Create a new request entity
RequestEntity<Resource<OrderEvent>> requestEntity = RequestEntity.post(
URI.create("http://localhost:8081/v1/events"))
.contentType(MediaTypes.HAL_JSON)
.body(getOrderEventResource(event), Resource.class);
// Update the order entity's status
Order result = restTemplate.exchange(requestEntity, Order.class)
.getBody();
log.info(result);
event.setOrder(result);
} catch (Exception ex) {
log.error(ex);
}
return event;
}
/**
* Create a new {@link OrderEvent} and publish it to the order stream.
*
* @param event is the {@link OrderEvent} to publish to the order stream
* @return a hypermedia {@link OrderEvent} resource
*/
@CacheEvict(cacheNames = "order-events", key = "#id.toString()")
public OrderEvent createEvent(Long id, OrderEvent event) {
// Save new event
event = addEvent(event);
Assert.notNull(event, "The event could not be appended to the order");
return event;
}
/**
* Get an {@link OrderEvent} with the supplied identifier.
*
* @param id is the unique identifier for the {@link OrderEvent}
* @return an {@link OrderEvent}
*/
public Resource<OrderEvent> getEvent(Long id) {
return getOrderEventResource(eventRepository.findOne(id));
}
/**
* Update an {@link OrderEvent} with the supplied identifier.
*
* @param id is the unique identifier for the {@link OrderEvent}
* @param event is the {@link OrderEvent} to update
* @return the updated {@link OrderEvent}
*/
@CacheEvict(cacheNames = "order-events", key = "#event.order().getOrderId().toString()")
public OrderEvent updateEvent(Long id, OrderEvent event) {
Assert.notNull(id);
Assert.isTrue(event.getId() == null || Objects.equals(id, event.getId()));
return eventRepository.save(event);
}
/**
* Get {@link OrderEvents} for the supplied {@link Order} identifier.
*
* @param id is the unique identifier of the {@link Order}
* @return a list of {@link OrderEvent} wrapped in a hypermedia {@link OrderEvents} resource
*/
@Cacheable(cacheNames = "order-events", key = "#id.toString()")
public List<OrderEvent> getOrderEvents(Long id) {
return eventRepository.findOrderEventsByOrderId(id,
new PageRequest(0, Integer.MAX_VALUE)).getContent();
}
/**
* Gets a hypermedia resource for a {@link OrderEvent} entity.
*
* @param event is the {@link OrderEvent} to enrich with hypermedia
* @return a hypermedia resource for the supplied {@link OrderEvent} entity
*/
private Resource<OrderEvent> getOrderEventResource(OrderEvent event) {
return new Resource<OrderEvent>(event, Arrays.asList(
linkTo(OrderController.class)
.slash("events")
.slash(event.getEventId())
.withSelfRel(),
linkTo(OrderController.class)
.slash("orders")
.slash(event.getOrder().getOrderId())
.withRel("order")));
}
/**
* Add a {@link OrderEvent} to an {@link Order} entity.
*
* @param event is the {@link OrderEvent} to append to an {@link Order} entity
* @return the newly appended {@link OrderEvent} entity
*/
@CacheEvict(cacheNames = "order-events", key = "#event.order().getOrderId().toString()")
private OrderEvent addEvent(OrderEvent event) {
event = eventRepository.saveAndFlush(event);
return event;
}
}

View File

@@ -0,0 +1,73 @@
package demo.event;
import com.fasterxml.jackson.annotation.JsonIgnore;
import demo.domain.BaseEntity;
import demo.order.Order;
import javax.persistence.*;
/**
* The domain event {@link OrderEvent} tracks the type and state of events as
* applied to the {@link Order} domain object. This event resource can be used
* to event source the aggregate state of {@link Order}.
* <p>
* This event resource also provides a transaction log that can be used to append
* actions to the event.
*
* @author kbastani
*/
@Entity
public class OrderEvent extends BaseEntity {
@Id
@GeneratedValue
private Long id;
@Enumerated(EnumType.STRING)
private OrderEventType type;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JsonIgnore
private Order order;
public OrderEvent() {
}
public OrderEvent(OrderEventType type) {
this.type = type;
}
@JsonIgnore
public Long getEventId() {
return id;
}
public void setEventId(Long id) {
this.id = id;
}
public OrderEventType getType() {
return type;
}
public void setType(OrderEventType type) {
this.type = type;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
@Override
public String toString() {
return "OrderEvent{" +
"id=" + id +
", type=" + type +
", order=" + order +
"} " + super.toString();
}
}

View File

@@ -0,0 +1,15 @@
package demo.event;
import demo.order.Order;
import demo.order.OrderStatus;
/**
* The {@link OrderEventType} represents a collection of possible events that describe
* state transitions of {@link OrderStatus} on the {@link Order} aggregate.
*
* @author kbastani
*/
public enum OrderEventType {
// TODO: Implement
ORDER_CREATED
}

View File

@@ -0,0 +1,74 @@
package demo.event;
import com.fasterxml.jackson.annotation.JsonIgnore;
import demo.order.Order;
import demo.order.OrderController;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.Resources;
import java.io.Serializable;
import java.util.List;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
/**
* The {@link OrderEvents} is a hypermedia collection of {@link OrderEvent} resources.
*
* @author kbastani
*/
public class OrderEvents extends Resources<OrderEvent> implements Serializable {
private Long orderId;
/**
* Create a new {@link OrderEvents} hypermedia resources collection for an {@link Order}.
*
* @param orderId is the unique identifier for the {@link Order}
* @param content is the collection of {@link OrderEvents} attached to the {@link Order}
*/
public OrderEvents(Long orderId, List<OrderEvent> content) {
this(content);
this.orderId = orderId;
// Add hypermedia links to resources parent
add(linkTo(OrderController.class)
.slash("orders")
.slash(orderId)
.slash("events")
.withSelfRel(),
linkTo(OrderController.class)
.slash("orders")
.slash(orderId)
.withRel("order"));
LinkBuilder linkBuilder = linkTo(EventController.class);
// Add hypermedia links to each item of the collection
content.stream().parallel().forEach(event -> event.add(
linkBuilder.slash("events")
.slash(event.getEventId())
.withSelfRel()
));
}
/**
* Creates a {@link Resources} instance with the given content and {@link Link}s (optional).
*
* @param content must not be {@literal null}.
* @param links the links to be added to the {@link Resources}.
*/
private OrderEvents(Iterable<OrderEvent> content, Link... links) {
super(content, links);
}
/**
* Get the {@link Order} identifier that the {@link OrderEvents} apply to.
*
* @return the order identifier
*/
@JsonIgnore
public Long getOrderId() {
return orderId;
}
}

View File

@@ -0,0 +1,93 @@
package demo.order;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
@Entity
public class LineItem implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name, productId;
private Integer quantity;
private Double price, tax;
public LineItem() {
}
public LineItem(String name, String productId, Integer quantity,
Double price, Double tax) {
this.name = name;
this.productId = productId;
this.quantity = quantity;
this.price = price;
this.tax = tax;
}
@JsonIgnore
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Double getTax() {
return tax;
}
public void setTax(Double tax) {
this.tax = tax;
}
@Override
public String toString() {
return "LineItem{" +
"name='" + name + '\'' +
", productId='" + productId + '\'' +
", quantity=" + quantity +
", price=" + price +
", tax=" + tax +
'}';
}
}

View File

@@ -0,0 +1,110 @@
package demo.order;
import com.fasterxml.jackson.annotation.JsonIgnore;
import demo.address.Address;
import demo.address.AddressType;
import demo.domain.BaseEntity;
import demo.event.OrderEvent;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity(name = "orders")
public class Order extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String accountNumber;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<OrderEvent> events = new HashSet<>();
@Enumerated(value = EnumType.STRING)
private OrderStatus status;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<LineItem> lineItems = new HashSet<>();
@OneToOne(cascade = CascadeType.ALL)
private Address shippingAddress;
public Order() {
this.status = OrderStatus.PURCHASED;
}
public Order(String accountNumber, Address shippingAddress) {
this();
this.accountNumber = accountNumber;
this.shippingAddress = shippingAddress;
if (shippingAddress.getAddressType() == null)
this.shippingAddress.setAddressType(AddressType.SHIPPING);
}
@JsonIgnore
public Long getOrderId() {
return id;
}
public void setOrderId(Long id) {
this.id = id;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public OrderStatus getStatus() {
return status;
}
public void setStatus(OrderStatus status) {
this.status = status;
}
@JsonIgnore
public Set<OrderEvent> getEvents() {
return events;
}
public void setEvents(Set<OrderEvent> events) {
this.events = events;
}
public Set<LineItem> getLineItems() {
return lineItems;
}
public void setLineItems(Set<LineItem> lineItems) {
this.lineItems = lineItems;
}
public Address getShippingAddress() {
return shippingAddress;
}
public void setShippingAddress(Address shippingAddress) {
this.shippingAddress = shippingAddress;
}
public void addLineItem(LineItem lineItem) {
lineItems.add(lineItem);
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", accountNumber='" + accountNumber + '\'' +
", events=" + events +
", status=" + status +
", lineItems=" + lineItems +
", shippingAddress=" + shippingAddress +
"} " + super.toString();
}
}

Some files were not shown because too many files have changed in this diff Show More