diff --git a/README.md b/README.md index 57649cf..8a10d36 100644 --- a/README.md +++ b/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 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: ![Money transfer architecture](https://github.com/cer/event-sourcing-examples/wiki/i/applicationarchitecture.png) -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 * Money transfers (command-side) - REST API for transferring money -* Account view updater (query-side) - subscribes to events and updates a MongoDB View -* Account view reader (query-side) - REST API for retrieving accounts +* Customers (query-side) - subscribes to events and updates a MongoDB View, and provides an API for retrieving customers +* 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: * monolithic-service - all services are packaged as a single Spring Boot executable JAR * Microservices - three separate Spring Boot executable JARs + * customer-command-side-service - command-side customers * accounts-command-side-service - command-side accounts * 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 There are currently the following versions of the example application: * 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. 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. -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. +There are two versions of Eventuate: -# 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. -To build an application, execute this command in the application's top-level directory: +There is also an embedded test event store, which is great for integration tests. + +# 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. -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: +Next, you can launch the services using [Docker Compose](https://docs.docker.com/compose/): ``` - 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. -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 -``` +Finally, you can open the home page, which is served up by the API Gateway: `http://$DOCKER_HOST_IP:8080` -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. -But don't worry. -The build is configured to ignore failures for those projects. +Note: `DOCKER_HOST_IP` is the IP address of the machine where Docker is running, e.g. the IP address of the VirtualBox VM. -# 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. - -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: +First, build the application ``` -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) -2. Create a money transfer -3. View the updated account balances +Note: You need to set `DOCKER_HOST_IP` before running Docker Compose. +This must be an IP address or resolvable hostname. +It cannot be `localhost`. -## Running the microservices - -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`. +Finally, you can open the home page, which is served up by the API Gateway: `http://$DOCKER_HOST_IP:8080` diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 66eeec1..0000000 --- a/Vagrantfile +++ /dev/null @@ -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 diff --git a/gradle-all.sh b/gradle-all.sh deleted file mode 100755 index 6a47aed..0000000 --- a/gradle-all.sh +++ /dev/null @@ -1,6 +0,0 @@ -#! /bin/bash -e - -for dir in java-spring scala-spring; do - (cd $dir ; ./gradlew -b build.gradle $*) -done - diff --git a/kill-all-services.sh b/kill-all-services.sh deleted file mode 100755 index 0497b8e..0000000 --- a/kill-all-services.sh +++ /dev/null @@ -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 diff --git a/run-all-services.sh b/run-all-services.sh deleted file mode 100755 index 9051492..0000000 --- a/run-all-services.sh +++ /dev/null @@ -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 diff --git a/run-e2e-test-all.sh b/run-e2e-test-all.sh deleted file mode 100755 index f8a11b2..0000000 --- a/run-e2e-test-all.sh +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/bash -e - -for dir in java-spring scala-spring; do - (cd $dir ; ../run-e2e-test.sh $*) -done diff --git a/run-e2e-test.sh b/run-e2e-test.sh deleted file mode 100755 index 5c8dc90..0000000 --- a/run-e2e-test.sh +++ /dev/null @@ -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 diff --git a/sample-set-server-env.sh b/sample-set-server-env.sh deleted file mode 100755 index 5b37704..0000000 --- a/sample-set-server-env.sh +++ /dev/null @@ -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