Merge remote-tracking branch 'remotes/upstream/master' into wip-customer
This commit is contained in:
120
README.md
120
README.md
@@ -1,4 +1,4 @@
|
|||||||
#Event-Sourcing+CQRS example application
|
# Event-Sourcing+CQRS example application
|
||||||
|
|
||||||
This example application is the money transfer application described in my talk [Building and deploying microservices with event sourcing, CQRS and Docker](http://plainoldobjects.com/presentations/building-and-deploying-microservices-with-event-sourcing-cqrs-and-docker/).
|
This example application is the money transfer application described in my talk [Building and deploying microservices with event sourcing, CQRS and Docker](http://plainoldobjects.com/presentations/building-and-deploying-microservices-with-event-sourcing-cqrs-and-docker/).
|
||||||
This talk describes a way of architecting highly scalable and available applications that is based on microservices, polyglot persistence,
|
This talk describes a way of architecting highly scalable and available applications that is based on microservices, polyglot persistence,
|
||||||
@@ -35,111 +35,99 @@ The following diagram shows the architecture:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
There are four logical services:
|
There are the following logical services:
|
||||||
|
|
||||||
|
* Customers (command-side) - REST API for creating customers
|
||||||
* Accounts (command-side) - REST API for creating accounts
|
* Accounts (command-side) - REST API for creating accounts
|
||||||
* Money transfers (command-side) - REST API for transferring money
|
* Money transfers (command-side) - REST API for transferring money
|
||||||
* Account view updater (query-side) - subscribes to events and updates a MongoDB View
|
* Customers (query-side) - subscribes to events and updates a MongoDB View, and provides an API for retrieving customers
|
||||||
* Account view reader (query-side) - REST API for retrieving accounts
|
* Accounts (query-side) - subscribes to events and updates a MongoDB View, and provides an API for retrieving accounts
|
||||||
|
|
||||||
|
There is also an [API gateway](http://microservices.io/patterns/apigateway.html) service that acts as a Facade in front of the services.
|
||||||
|
|
||||||
One of the neat things about the modular architecture is that there are two ways to deploy these four services:
|
One of the neat things about the modular architecture is that there are two ways to deploy these four services:
|
||||||
|
|
||||||
* monolithic-service - all services are packaged as a single Spring Boot executable JAR
|
* monolithic-service - all services are packaged as a single Spring Boot executable JAR
|
||||||
* Microservices - three separate Spring Boot executable JARs
|
* Microservices - three separate Spring Boot executable JARs
|
||||||
|
* customer-command-side-service - command-side customers
|
||||||
* accounts-command-side-service - command-side accounts
|
* accounts-command-side-service - command-side accounts
|
||||||
* transactions-command-side-service - command-side money transfers
|
* transactions-command-side-service - command-side money transfers
|
||||||
* accounts-query-side-service - Account View Updater and Account View Reader
|
* customers-query-side-service - query-side customers
|
||||||
|
* accounts-query-side-service - query-side accounts
|
||||||
|
* api-gateway-service - API gateway service
|
||||||
|
|
||||||
# About the examples
|
# About the examples
|
||||||
|
|
||||||
There are currently the following versions of the example application:
|
There are currently the following versions of the example application:
|
||||||
|
|
||||||
* java-spring - a Java and Spring Boot example
|
* java-spring - a Java and Spring Boot example
|
||||||
* scala-spring - a Scala and Spring Boot example
|
* scala-spring - a Scala and Spring Boot example (NOTE: this version is lagging the Java Spring and hasn't been updated in a longtime.)
|
||||||
|
|
||||||
Other examples will be added shortly including a Scala/Play example.
|
Other examples will be added shortly including a Scala/Play example.
|
||||||
|
|
||||||
For more information, please see the [wiki](../../wiki)
|
For more information, please see the [wiki](../../wiki)
|
||||||
|
|
||||||
# About the Event Store
|
# About the Eventuate Platform
|
||||||
|
|
||||||
The application uses one of two event stores:
|
The application is built using [Eventuate](http://eventuate.io/), which is an application platform for writing transactional microservices.
|
||||||
|
It provides a simple yet powerful event-driven programming model that is based on event sourcing and Command Query Responsibility Segregation (CQRS).
|
||||||
|
Eventuate solves the distributed data management problems inherent in a microservice architecture.
|
||||||
|
It consists of a scalable, distributed event store and client libraries for various languages and frameworks including Java, Scala, and the Spring framework.
|
||||||
|
|
||||||
* Embedded SQL-based event store, which is great for integration tests.
|
There are two versions of Eventuate:
|
||||||
It is also used when running the monolithic version of the application.
|
|
||||||
* Event Store server - this is a full featured event store.
|
|
||||||
See this [wiki page](../../wiki/AboutTheEventStoreServer) for more details.
|
|
||||||
|
|
||||||
# Building the application (and running the tests)
|
* [Eventuate SaaS server](http://eventuate.io/usingeventuate.html) - this is a full featured event store that is hosted on AWS
|
||||||
|
* [Eventuate Local](http://eventuate.io/usingeventuate.html) - an open-source event store that is built using MySQL and Kafka
|
||||||
|
|
||||||
Both versions of the application use Gradle.
|
There is also an embedded test event store, which is great for integration tests.
|
||||||
To build an application, execute this command in the application's top-level directory:
|
|
||||||
|
# Building and running the microservices
|
||||||
|
|
||||||
|
This is a Gradle project.
|
||||||
|
However, you do not need to install Gradle since it will be downloaded automatically.
|
||||||
|
You just need to have Java 8 installed.
|
||||||
|
|
||||||
|
The details of how to build and run the services depend slightly on whether you are using Eventuate SaaS or Eventuate Local.
|
||||||
|
|
||||||
|
## Building and running using Eventuate SaaS
|
||||||
|
|
||||||
|
First, must [sign up to get your credentials](https://signup.eventuate.io/) in order to get free access to the SaaS version.
|
||||||
|
|
||||||
|
Next, build the application
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew assemble
|
cd java-spring
|
||||||
|
./gradlew assemble -P eventuateLocal=true
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: you do not need to install Gradle.
|
Next, you can launch the services using [Docker Compose](https://docs.docker.com/compose/):
|
||||||
It will be automatically downloaded by `./gradlew`.
|
|
||||||
|
|
||||||
This will build a Spring Boot jar in each of the `*-service` directories.
|
|
||||||
|
|
||||||
You can also run the tests using `gradle build`.
|
|
||||||
However, you must set some environment variables.
|
|
||||||
|
|
||||||
First, you need to tell the query side code how to connect to MongoDB:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
export SPRING_DATA_MONGODB_URI=mongodb://192.168.59.103/yourdb
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
[Docker Compose](https://docs.docker.com/compose/) is a great way to run MongoDB.
|
Finally, you can open the home page, which is served up by the API Gateway: `http://$DOCKER_HOST_IP:8080`
|
||||||
You can run the `docker-compose up -d mongodb` to run MongoDB and then set `SPRING_DATA_MONGODB_URI` as follows:
|
|
||||||
```
|
|
||||||
export SPRING_DATA_MONGODB_URI=mongodb://$(docker-machine ip default)/yourdb
|
|
||||||
```
|
|
||||||
|
|
||||||
Second, some of the tests in accounts-command-side-service, transactions-command-side-service, accounts-query-side-service and e2e-test require you to set some environment variables that tell them how to connect to the Event Store server.
|
Note: `DOCKER_HOST_IP` is the IP address of the machine where Docker is running, e.g. the IP address of the VirtualBox VM.
|
||||||
But don't worry.
|
|
||||||
The build is configured to ignore failures for those projects.
|
|
||||||
|
|
||||||
# Running the application
|
## Building and running using Eventuate Local
|
||||||
|
|
||||||
To run the application, you must to set the SPRING_DATA_MONGODB_URI environment variable, which tells the query services how to connect to MongoDB.
|
First, build the application
|
||||||
|
|
||||||
There are a couple of different ways of running the application.
|
|
||||||
|
|
||||||
## Running the monolithic application
|
|
||||||
|
|
||||||
One option is to run the self-contained monolithic application.
|
|
||||||
It uses the embedded event store.
|
|
||||||
|
|
||||||
Simply use this command:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
java -jar monolithic-service/build/libs/monolithic-service.jar
|
cd java-spring
|
||||||
|
./gradlew assemble -P eventuateDriver=local
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start the service running on port 8080 (you can change using the --server.port=9999 option).
|
Next, launch the services using [Docker Compose](https://docs.docker.com/compose/):
|
||||||
|
|
||||||
Once the service has started you can open the Swagger UI: http://localhost:8080/swagger-ui.html.
|
```
|
||||||
You can then:
|
export DOCKER_HOST_IP=...
|
||||||
|
docker-compose -f docker-compose-eventuate-local.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
1. Create two accounts (save the account ids)
|
Note: You need to set `DOCKER_HOST_IP` before running Docker Compose.
|
||||||
2. Create a money transfer
|
This must be an IP address or resolvable hostname.
|
||||||
3. View the updated account balances
|
It cannot be `localhost`.
|
||||||
|
|
||||||
## Running the microservices
|
Finally, you can open the home page, which is served up by the API Gateway: `http://$DOCKER_HOST_IP:8080`
|
||||||
|
|
||||||
The other option is to run the services separately.
|
|
||||||
However, in order to do this you need to [get credentials for the Event Store](../../wiki/AboutTheEventStoreServer).
|
|
||||||
|
|
||||||
One way to run the services is to use the scripts `run-all-services.sh`, which runs the services, and `kill-all-services.sh`, which kills the processes.
|
|
||||||
|
|
||||||
A much better way, however, is to use Docker Compose.
|
|
||||||
Simply run the command `docker-compose up` to launch the services.
|
|
||||||
This will create containers for MongoDB and each of the services.
|
|
||||||
|
|
||||||
You can now, for example, use the curl commands in `handy-curl-commands.sh` to interact with the server.
|
|
||||||
|
|
||||||
You can also use the Swagger UI exposed by each service `http://host:port/swagger-ui.html`.
|
|
||||||
|
|||||||
103
Vagrantfile
vendored
103
Vagrantfile
vendored
@@ -1,103 +0,0 @@
|
|||||||
# -*- mode: ruby -*-
|
|
||||||
# vi: set ft=ruby :
|
|
||||||
|
|
||||||
# All Vagrant configuration is done below. The "2" in Vagrant.configure
|
|
||||||
# configures the configuration version (we support older styles for
|
|
||||||
# backwards compatibility). Please don't change it unless you know what
|
|
||||||
# you're doing.
|
|
||||||
Vagrant.configure(2) do |config|
|
|
||||||
# The most common configuration options are documented and commented below.
|
|
||||||
# For a complete reference, please see the online documentation at
|
|
||||||
# https://docs.vagrantup.com.
|
|
||||||
|
|
||||||
# Every Vagrant development environment requires a box. You can search for
|
|
||||||
# boxes at https://atlas.hashicorp.com/search.
|
|
||||||
config.vm.box = "ubuntu/trusty64"
|
|
||||||
|
|
||||||
config.vm.provider "virtualbox" do |v|
|
|
||||||
v.memory = 2048
|
|
||||||
v.cpus = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
# Disable automatic box update checking. If you disable this, then
|
|
||||||
# boxes will only be checked for updates when the user runs
|
|
||||||
# `vagrant box outdated`. This is not recommended.
|
|
||||||
# config.vm.box_check_update = false
|
|
||||||
|
|
||||||
# Create a forwarded port mapping which allows access to a specific port
|
|
||||||
# within the machine from a port on the host machine. In the example below,
|
|
||||||
# accessing "localhost:8080" will access port 80 on the guest machine.
|
|
||||||
# config.vm.network "forwarded_port", guest: 80, host: 8080
|
|
||||||
|
|
||||||
# Create a private network, which allows host-only access to the machine
|
|
||||||
# using a specific IP.
|
|
||||||
# config.vm.network "private_network", ip: "192.168.33.10"
|
|
||||||
|
|
||||||
# Create a public network, which generally matched to bridged network.
|
|
||||||
# Bridged networks make the machine appear as another physical device on
|
|
||||||
# your network.
|
|
||||||
# config.vm.network "public_network"
|
|
||||||
|
|
||||||
# Share an additional folder to the guest VM. The first argument is
|
|
||||||
# the path on the host to the actual folder. The second argument is
|
|
||||||
# the path on the guest to mount the folder. And the optional third
|
|
||||||
# argument is a set of non-required options.
|
|
||||||
# config.vm.synced_folder "../data", "/vagrant_data"
|
|
||||||
|
|
||||||
# Provider-specific configuration so you can fine-tune various
|
|
||||||
# backing providers for Vagrant. These expose provider-specific options.
|
|
||||||
# Example for VirtualBox:
|
|
||||||
#
|
|
||||||
# config.vm.provider "virtualbox" do |vb|
|
|
||||||
# # Display the VirtualBox GUI when booting the machine
|
|
||||||
# vb.gui = true
|
|
||||||
#
|
|
||||||
# # Customize the amount of memory on the VM:
|
|
||||||
# vb.memory = "1024"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# View the documentation for the provider you are using for more
|
|
||||||
# information on available options.
|
|
||||||
|
|
||||||
# Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
|
|
||||||
# such as FTP and Heroku are also available. See the documentation at
|
|
||||||
# https://docs.vagrantup.com/v2/push/atlas.html for more information.
|
|
||||||
# config.push.define "atlas" do |push|
|
|
||||||
# push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Enable provisioning with a shell script. Additional provisioners such as
|
|
||||||
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
|
|
||||||
# documentation for more information about their specific syntax and use.
|
|
||||||
|
|
||||||
config.vm.provision "shell", inline: <<-SHELL
|
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# https://github.com/pussinboots/vagrant-devel/blob/master/provision/packages/java8.sh
|
|
||||||
|
|
||||||
if which java >/dev/null; then
|
|
||||||
echo "skip java 8 installation"
|
|
||||||
else
|
|
||||||
echo "java 8 installation"
|
|
||||||
apt-get install --yes python-software-properties
|
|
||||||
add-apt-repository ppa:webupd8team/java
|
|
||||||
apt-get update -qq
|
|
||||||
echo debconf shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections
|
|
||||||
echo debconf shared/accepted-oracle-license-v1-1 seen true | /usr/bin/debconf-set-selections
|
|
||||||
apt-get install --yes oracle-java8-installer
|
|
||||||
yes "" | apt-get -f install
|
|
||||||
fi
|
|
||||||
SHELL
|
|
||||||
|
|
||||||
config.vm.provision "docker" do |d|
|
|
||||||
end
|
|
||||||
|
|
||||||
config.vm.provision "shell", inline: <<-SHELL
|
|
||||||
if which docker-compose >/dev/null; then
|
|
||||||
echo "skip docker-compose installation"
|
|
||||||
else
|
|
||||||
sudo bash -c "curl -L https://github.com/docker/compose/releases/download/1.6.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose ; chmod +x /usr/local/bin/docker-compose"
|
|
||||||
fi
|
|
||||||
SHELL
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#! /bin/bash -e
|
|
||||||
|
|
||||||
for dir in java-spring scala-spring; do
|
|
||||||
(cd $dir ; ./gradlew -b build.gradle $*)
|
|
||||||
done
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
kill `cat account-cs.pid account-qs.pid transfers-cs.pid`
|
|
||||||
rm account-cs.pid account-qs.pid transfers-cs.pid
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#! /bin/bash -e
|
|
||||||
|
|
||||||
# Execute this script in the java-spring or scala-spring directory
|
|
||||||
# Runs all of the services
|
|
||||||
|
|
||||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
|
|
||||||
if [[ -f account-cs.pid ]]; then
|
|
||||||
echo pid file exists
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
java -jar accounts-command-side-service/build/libs/accounts-command-side-service.jar > account-cs.log &
|
|
||||||
echo $! > account-cs.pid
|
|
||||||
|
|
||||||
java -jar accounts-query-side-service/build/libs/accounts-query-side-service.jar --server.port=8081 > account-qs.log &
|
|
||||||
echo $! > account-qs.pid
|
|
||||||
|
|
||||||
java -jar transactions-command-side-service/build/libs/transactions-command-side-service.jar --server.port=8082 > transfers-cs.log &
|
|
||||||
echo $! > transfers-cs.pid
|
|
||||||
|
|
||||||
echo -n waiting for services....
|
|
||||||
|
|
||||||
$DIR/wait-for-services.sh localhost
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#! /bin/bash -e
|
|
||||||
|
|
||||||
for dir in java-spring scala-spring; do
|
|
||||||
(cd $dir ; ../run-e2e-test.sh $*)
|
|
||||||
done
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#! /bin/bash -e
|
|
||||||
|
|
||||||
# Must be run in the java-spring or scala-spring directories
|
|
||||||
|
|
||||||
echo starting services
|
|
||||||
|
|
||||||
../run-all-services.sh
|
|
||||||
|
|
||||||
echo running test
|
|
||||||
|
|
||||||
./gradlew $* -P ignoreE2EFailures=false :e2e-test:cleanTest :e2e-test:test
|
|
||||||
|
|
||||||
echo killing services
|
|
||||||
|
|
||||||
../kill-all-services.sh
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#! /bin/bash -e
|
|
||||||
|
|
||||||
export EVENTUATE_API_KEY_ID=Aladdin
|
|
||||||
export EVENTUATE_API_KEY_SECRET="open sesame"
|
|
||||||
|
|
||||||
export SPRING_DATA_MONGODB_URI=mongodb://192.168.59.103/mydb
|
|
||||||
Reference in New Issue
Block a user