Chapter 1: In a nutshell
What on earth is this?
This book is not about introducing microservices. It is about a greenfield project where we will design, build and rollout a “reactive” distributed application. We set the goals for our application, define the application architecture and define a plan how to iteratively implement it. Why iteratively you might think? Based on the characteristics of a reactive system we require a proper infrastructure and application design to achieve this. For example, we want our system to be resilient and elastic. This requires more than just our coding efforts. We have a very strong dependency on infrastructure too. This means that we will cover both infrastrucute and coding in this book. We will define an infrastructure and automate its setup, running both locally and in production using advanced tooling. We will have to write our reactive distributed application and have it deployed automatically into our infrastucute using Continuous Deployment (CD). For CD we require specific tools. This book also covers the provisioning of these tools. Just reading this book is not enough. All code and scripts can be found on GitHub. So that is what you can expect!
What we will cover
Build a reactive distributed application
It is great writing a book about a topic for which there are tons of explanations to be found. For definitions you can refer to several sources on the net like Wikipedia, or the Reactive Manifesto where reactive systems are described. We are not looking at any history here. Remember that we want to implement a distributed system targeting a cloud infrastrucute. The Reactive Manifesto defines four characteristics of a reactive system. A reactive system should:
- be responsive
- be resilient
- be elastic
- use asynchronous message passing
How would these map to our application? We want our application to be responsive which means our solution offers fast and consistent response times. We want our application users feel comfortable with our solution and try to achieve a long lasting relationship with our product.
When our system is under high load we have any failing service we still want our application be responsive. A system that is not resilient won’t be responsive under such circumstances. It should be transparent to the client in case parts of our solution fails. Our solution should provide functionality for recovery.
Under different load our application stays responsive. Our solution should provide elasticity. This means we assign the proper resources once our load on the systems demans this. As we are targeting a cloud infrastructure we have to be cost-effective. Any resources running idle for a longer time means higher costs.
The reactive manifesto also states that reactive systems rely on asynchronous message passing. So, how can we contribute making our system reactive by our coding efforts? As we will focus on Spring 5 and Java 9, we have a special interest in implementations that have some a strong reference with either Reactive Streams and Reactive Extentensions. Of course we could look at other solutions as well like actors but we have to make a choice, and that one is based our special interest in Spring 5 and Java 9. The reactive streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. When we dive into the solutions that Java 9, JavaRx and Spring 5 offer we go into detail here. So what about the front-end? Here will focus on solutions that are based on (yes again) Reactive Extensions. In short we focus on RxJS in combination with React. As we will use RxJS in the front-end, we will also look at its “counterpart” RxJava, serving the back-end. We will also look at how Spring addresses features which were formely based on ThreadLocal, like e.g. security and transaction management. Also, we will look at reactive stream based database drivers, like the MongoDB Reactive Streams Java Driver. And much more!
Our goal is to have end to end reactive implementations for both the front and back-end.
Microservices
We won’t focus exclusively on writing microservices. We also look at some important concepts with regard to deploying microservices. We won’t cover any pros and cons about monolithic and microservices concepts. As you are already reading this, it is already clear you are interested in microservices. What we will cover is what we need to take care of when we deploy a reactive distribution application using microservices. You could think of:
- API gateways
- Service discovery
- Deployment strategy (without downtime)
- Fail-over
- Elasticity
API gateways
Front-ends will have to access backend services. Most often this is not limited to accessing only one service. Exposing all services directly to the client is not the best practice. We are looking for loose coupling and locations transparency here. We can implement this by introducing a special “service” between the client and the services. The API gateway acts as a kind of facade to our microservices landscape. However, it’s value is not only limited to the facade. We could assign other responsibilities to it as well which will offload certain tasks from our microservices. Some core responsibilites could be authentication and request composition. This book will cover implementing our API gateway in detail.
Service discovery
We need service discovery for making our microservices visible to other microservices and load balancers. Microservices need to be accessible from our load balancer so it needs to know which options are available. Especially when we scale horizontally and have multiple instances availabe behind our load balancer. So our load balancer needs to be aware of changes in our deployed, undeployed, crashed or even bad performing services. We won’t get this for free and it will require some investment to make us familiar with this topic. So expect coverage of this topic in detail.
Deployment strategy (without downtime)
There are many options for deploying our microservices. We could deploy one service per host or virtual machine, or even multiple different per machine. As Docker is really popular these days and many companies are adopting it we choose to dockerize our services and have multiple docker containers deployed on a host. Imagine we would have four different microservices. We could dockerize each of them and have them deployed on a host. We front the host with an API gateway, et voila. Forget scalability for, just focus on deploying all Dockerized services to one single host. The interesting thing here is that we can map this deployment architecture 1-to-1 to our development environment. By creating a virtual machine locally which mirrors our production host we described earlier, we can almost exactly replicate locally what we will do in production “later”. Docker plays an important role here. This book will make it more complex later when we scale horizontally. We could add one or more extra nodes. At least one for failover, and more for serving moments of high load. Having multiple instances of the same microservices in place (i.e. Docker containers) we can introduce the concept of deployments without downtime. The green/blue deployment strategy is the one we will implement.
Fail-over
Elasticity
Environment provisioning
Production
Our production environment will enable our customers to consume our product. We will have to architect a solution that is able to run our application at minimal costs, but being responsive at all times. We know that there are a lot of factors that make a system responsive or fail. Our application will run in the cloud so we can delegate a lot of responsibilities to the cloud provider because we want to focus on our application as much as possible. Some responsibilities we explicitly don’t handover because of several reasons:
- We want to learn how to implement core concepts
- We don’t want to have a vendor lock-in
- We have no experience with certain concepts
We want to gain knowledge on certain topics. We want to minimize any vendor lock-in which enables us to target any different cloud provider if necessary. As we will have to define and implement our infrastructure we need provisioning tools. There are a few good ones out there, like puppet and chef. However we choose Ansible. Ansible is a great tool for configuration management and orchestration. Ansible will send instructions over SSH to any node we would like to provision. It is up to us to define those instructions. Instructions could be:
- build a new docker image
- run a docker container with a specific configuration
- install debian packages
- run shell scripts
- copy files
But before we start provisioning any production node we have to design, implement and test them first in our local (virtual) environment.
Locally
In order to dry-run our provisioning activities we need a representative environment which somehow closely matches production. We do this by configuring a virtual machine where we will install everything we need to mirror our production architecture. Of course there will have to be one or more test environments between our VM and production to get confident we can continuously deploy our application.
Build and deployment automation
Without any automation our development efforts become too expensive. As our work is a repetitive chain of tasks we will have to automate it. It should not be surprising that we need a build pipeline, serving continuous integration (CI). This build pipeline will be build gradually through the chapters. For each code commit the build pipeline will:
- checkout the latest code from Github
- compile our code
- determine the code coverage when running our unit tests
- run static code analys
- run integration tests
- run in-process component tests
- bake Docker images
- run out of process component tests
- run user journey tests
These are just a few examples, but we can make it a lot more advanced by introducing continuous deployment. This is what we will cover in chapter [chapter: Continuous Deployment].
Our application
We will write a realistic distributed application where you might expect quite some complexity and challenges. Think about APIs which will serve tablet and mobile clients, and desktops. Of course a ToDo application would be an option, but there are already thousands of this kind and a ToDo application wouldn’t fit in our context. Let’s write a dating application. We can make a dating application just complex enough to cover all of our aspects that we addressed before. And even more interestingly, you can find the source code in Github. Also, it should not be too difficult to image that a dating application is the right candidate for a distributed system as it easy to define different bounded contexts. So let’s try to summarize what we will do:
- write a dating application, which is distributed
- write code that will be in a reactive style, both front and backend
- automate all tasks that we would have to do manually and repetitively
- set up all required tools we need for our automated build pipeline
- provision a local virtual environment, mirroring our target production environment
- provision our target production environment
- deploy without downtime
To give you already an idea what kind of deployment architecture I am thinking of, please have a look at the following illustration. We will refine it in later chapters.
I love feedback
Writing a book is like a journey. Along the way, text and code will be replaced or improved. In case you have suggestions, please send them to me. Your comments are highly welcome!