Cracking Spring Microservices Interviews 1690628902
Cracking Spring Microservices Interviews 1690628902
development
We need a set of tooling to start developing spring-boot based microservices.
Please be noted that not all of these softwares are mandatory for microservices
development.
I don’t think it requires any introduction. You can straight away download
the latest Java 8 from the below location
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-
downloads-2133151.html
IntelliJ IDEA
https://www.jetbrains.com/idea/
Eclipse is a powerful free alternative to IntelliJ IDEA. You can use it along
with Spring Tool Set (STS) for developing spring powered applications.
You can download it from
http://www.eclipse.org/downloads/
Jenkins
https://jenkins.io/
Spring Boot and Spring Cloud
https://projects.spring.io/spring-boot/
Swagger
https://swagger.io
Postman
Postman makes API development faster, easier, and better. Its one of the
best tools for testing restful apis. Standalone app for Mac/Ubuntu/Windows
can be downloaded from:
https://www.getpostman.com/apps
Docker
Docker is an open platform for developers and sysadmins to build, ship, and
run distributed applications, whether on laptops, data center VMs, or the
cloud.
https://www.docker.com/
Git
https://git-scm.com/
Gradle
https://gradle.org/
RabbitMQ
RabbitMQ is the most widely deployed open source message broker. Get it
from the official website:
https://www.rabbitmq.com/
https://www.rabbitmq.com/getstarted.html
Database
Let’s Encrypt
https://letsencrypt.org
Core Concepts in Microservices
Before we delve deep into microservices architecture, we must get familiar with
few basic concepts. We will use terms like Cohesion, Coupling, Immutability,
DRY, Open/Close Principle, Single Responsibility Principle in upcoming
sections. If you are already aware of these basic terms, then probably you can
skip this chapter.
Cohesion
Cohesion refers to the degree of focus that a particular software component has.
A multi-purpose mobile phone with camera, radio, torch, etc. for example has
low cohesion compared to a dedicated DLSR that does one thing better.
Example of cohesion:
Lets suppose we have a monolithic application for a fictitious e-shop, that does
order management, inventory management, user management, marketing,
delivery management etc. This monolithic software has very low cohesion
compared to a microservices based architecture where each microservice is
responsible for a particular business functionality, for example -
High cohesion often correlates with loose coupling, and vice versa.
A very good day to day example of coupling is Mobile handset that has battery
sealed into the handset. Design in this case is tightly coupled because battery or
motherboard can not be replaced from each other without affecting each other.
In Object Oriented Design we always look for low coupling among various
components so as to achieve flexibility and good maintainability. Changes in one
components shall not affect other components of the system.
Note
In DRY people develop libraries and share these libraries. But we shall always
keep in mind the Bounded Context Principle along with DRY principle, we shall
never share a code that violates the Bounded Context Principle. And we shall
never create shared unified models across Bounded Contexts, for example its
really a bad idea to create a single large unified model for Customer class and
share it across microservices.
The basic design principle behind DRY is that we should not try to reinvent the
wheel. At the same time, we should not share the same wheel for multiple
purposes.
Consider a reporting module that creates content for a report and send it through
email. If we embed both these functionalities into single class/module, then we
are not following Single Responsibility Principle. Such software should be split
into two module, one for creating the report content, another for sending the
email, each one having single responsibility.
— Robert C. Martin
8 Fallacies of Distributed Computing
In 1994, Peter Deutsch, a sun fellow at the time, drafted 7 assumptions architects
and designers of distributed systems are likely to make, which prove wrong in
the long run - resulting in all sorts of troubles and pains for the solution and
architects who made the assumptions. In 1997 James Gosling added another
such fallacy [JDJ2004]. The assumptions are now collectively known as the
"The 8 fallacies of distributed computing" [Gosling]:
2. Latency is zero
3. Bandwidth is infinite
These fallacies written 20 years back are increasingly becoming irrelevant with
the advancement of science & technology.
However, we shall make sure that our distributed system design is resilient to the
above mentioned 8 fallacies. In the upcoming chapters, we will see how Spring
Cloud provide us with the appropriate libraries (circuit-breaker, OAuth 2.0 JWT,
API Gateway, etc.) to take care of these issues.
https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing
https://www.infoworld.com/article/3114195/system-management/the-8-
fallacies-of-distributed-computing-are-becoming-irrelevant.html
http://www.rgoarchitects.com/Files/fallacies.pdf
Continuous Integration (CI)
Continuous Integration is a software development approach where developers
merge their code into a common repository, several times in a day.
CI provides the benefit of early feedback for any code that is developed and
merged into common repository.
This ensures that the common repository is always ready for the production
deployment.
Tools like Git (Github/BitBucket, etc.), Jenkins and Maven/Gradle are normally
used together to setup the build pipelines.
You can setup a Jenkins job that continuously monitors for commit on a pre-
specified branch of git repository. Once developer commits his changes to this
branch, Jenkins will trigger the build & test job and provide the feedback as
early as possible. Dependency among two or more jobs can also be specified so
that any affected dependency is also built & tested.
CAP Theorem
In theoretical computer science, the CAP theorem, also named Brewer’s theorem
after computer scientist Eric Brewer, states that it is impossible for a distributed
data store to simultaneously provide more than two out of the following three
guarantees
CA databases
AP & CP databases
Codebase
One codebase, multiple deploys. This means that we should only have one
codebase for different versions of a microservices. Branches are ok, but
different repositories are not.
Dependencies
Config
Backing services
Correct approach.
Creating named alias for a service, in bootstrap.yml
/src/main/resources/bootstrap.yml.
product-service:
ribbon:
eureka:
enabled: false
listOfServers: localhost:8090,localhost:9092,localhost:9999
ServerListRefreshInterval: 15000
And then using named alias to call the service inside code.
String greeting = this.restTemplate.getForObject("http://product-service/i
Strictly separate build and run stages. In other words, you should be able to
build or compile the code, then combine that with specific configuration
information to create a specific release, then deliberately run that release. It
should be impossible to make code changes at runtime, for e.g. changing
the class files in tomcat directly. There should always be a unique id for
each version of release, mostly a timestamp. Release information should be
immutable, any changes should lead to a new release.
Build-Release-Run Principle
Processes
Execute the app as one or more stateless processes. This means that our
microservices should be stateless in nature, and should not rely on any state
being present in memory or in filesystem. Indeed the state does not belong
in the code. So no sticky sessions, no in memory cache, no local filesystem
storage, etc. Distributed cache like memcache, ehcache or Redis should be
used instead.
Port Binding
Export services via port binding. This is about having your application as
standalone, instead of relying on a running instance of an application server,
where you deploy. Spring boot provides mechanism to create an self-
executable uber jar that contains all dependencies and embedded servlet
container (jetty or tomcat).
Concurrency
Scale out via the process model. In the twelve-factor app, processes are a
first class citizen. This does not exclude individual processes from handling
their own internal multiplexing, via threads inside the runtime VM, or the
async/evented model found in tools such as EventMachine, Twisted, or
Node.js. But an individual VM can only grow so large (vertical scale), so
the application must also be able to span multiple processes running on
multiple physical machines. Twelve-factor app processes should never write
PID files, rather it should rely on operating system process manager such as
systemd - a distributed process manager on a cloud platform.
Disposability
Maximize robustness with fast startup and graceful shutdown. The twelve-
factor app’s processes are disposable, meaning they can be started or
stopped at a moment’s notice. This facilitates fast elastic scaling, rapid
deployment of code or config changes, and robustness of production
deploys. Processes should strive to minimize startup time. Ideally, a process
takes a few seconds from the time the launch command is executed until the
process is up and ready to receive requests or jobs. Short startup time
provides more agility for the release process and scaling up; and it aids
robustness, because the process manager can more easily move processes to
new physical machines when warranted.
Dev/Prod parity
Logs
Treat logs as event streams, sending all logs only to stdout. Most Java
Developers would not agree to this advise, though.
Admin processes
https://12factor.net/
https://cloud.spring.io/spring-cloud-config/
https://spring.io/guides/gs/client-side-load-balancing/
https://12factor.net/build-release-run
Typical Git workflow for a real project
You can setup git workflow as per project’s need. Most common git workflow in
any enterprise grade project would be a variant of the the following:
Git workflow and deployment pipeline
1. Sprint is created with set of user stories. These user stories will map to set
of features in your application.
Note
If you have recently migrated from SVN, then you might think that creating
separate branch for each feature might be an overhead. But that’s not the
case with Git. Git branches are very light weight and you can create
branches on the fly for each bug you resolve, each feature/task you
complete. In fact you should.
Tip
Git was created by Linus Torvalds in 2005 for development of the Linux
kernel, with other kernel developers contributing to its initial development.
Its current maintainer since 2005 is Junio Hamano.
There is a beautiful book for learning git functionality, its freely hosted at:
https://git-scm.com/book/en/v2
Introduction to Microservices
The term microservices became popular in late 2000 after big giants started
moving their existing monolithic/SOA application into smaller autonomous
services. As of this writing (2018), any new enterprise grade application in Java
is potentially follows a microservices based architecture. Its a trend that will not
stop any sooner, until we find a better way to craft software applications.
7. Fault Tolerance - if one service fails, it will not affect rest of the system. For
example, if a microservices serving the comments and reviews for a e-
commerce fails, rest of the website should run fine.
10. Security - Every microservices should have capability to protect its own
resource from unauthorised access. This is achieved using stateless security
mechanisms like JSON Web Token (JWT pronounced as jot) with OAuth2.
Benefits of using Microservices Architecture
1. Each microservice is focussed on one business capability making them
easier to maintain and develop.
5. Resilience - Is one of the service goes down, it will not affect the entire
application. Outage in service serving the static images content will not
bring down the entire e-commerce web site.
Challenges in Microservices
1. DevOps is must because of explosion of number of processes in a
production system. How to start and stop fleet of services?
3. How to make configuration changes across the large fleet of services with
minimal effort.
SOA started gaining ground due to its distributed architecture approach and it
emerged to combat the problems of large monolithic applications, around 2006.
Both (SOA and Microservices) of these architectures share one common thing
that they both are distributed architecture and both allow high scalability. In
both, service components are accessed remotely through remote access protocol
(RMI, REST, SOAP, AMQP, JMS, etc.). both are modular and loosely coupled
by design and offer high scalability. Microservices started gaining buzz in late
2000 after emergence of light weight containers, Docker, Orchestration
Frameworks (Kubernetes, mesos). Microservices differ from SOA in a
significant manner conceptually -
4. Microservices should own their own data while SOA may share common
database. So one Microservices should not allow another Microservices to
change/read its data directly.
— Martin Fowler
"Gather together those things that change for the same reason, and separate
those things that change for different reasons"
References
1. http://www.oracle.com/technetwork/issue-archive/2015/15-
mar/o25architect-2458702.html
Microservices Interview Questions
This chapter is mostly Q&A oriented and our focus area would be:
— Sam Newman
— Adrian Cockcroft
— Toby Clemson
Typical Microservices Architecture Spring Boot
What is Domain Driven Design?
Domain Driven Design (DDD) is a modelling technique for organized
decomposition of complex problem domains. Eric Evans’s book on Domain
Driven Design has greatly influenced modern architectural thinking. DDD
technique can be used while partitioning a monolith application into
microservices architecture.
1. placing the project’s primary focus on the core domain and domain logic
Tip
The term "Domain Driven Design" was coined by Eric Evans in his book
of the same title.
Book link.
Reference.
https://en.wikipedia.org/wiki/Domain-driven_design
What is Bounded Context?
Bounded Context is a central pattern in Domain Driven Design. In Bounded
Context, everything related to the domain is visible within context internally but
opaque to other bounded contexts. DDD deals with large models by dividing
them into different Bounded Contexts and being explicit about their
interrelationships.
A single conceptual model for the entire organization is very tricky to deal with.
The only benefit of such unified model is that integration is easy across the
whole enterprise, but drawbacks are many, for example:
1. At first, its very hard to build a single model that works for entire
organization.
3. Its very difficult to change such shared model to accommodate the new
business requirements. The impact of such change will be widespread
across team boundaries.
4. Any large enterprise needs a model that is either very large or abstract.
DDD solves this problem by decomposing a large system into multiple Bounded
Contexts, each of which can have a unified model, opaque to other bounded
contexts.
Bounded Context for sales and shipment departments of an enterprise
As shown in image above, the two different bounded contexts, namely sales
department and shipment department have duplicate models for customer and
product that are opaque to other bounded context. In non DDD (domain driven
design) world, we could have created a single unified model for customer and
product and shared it using libraries across team boundaries.
Bounded Context lays stress on the important observation that each entity
works best within a localized context. So instead of creating a unified
Product and Customer class across the fictitious eshop system, each
problem domain (Sales, Support, Inventory, Shipment & Delivery etc.) can
create its own, and reconcile the difference at the integration points.
What is polyglot persistence? Can this idea be used in
monolithic applications as well?
Polyglot persistence is all about using different databases for different business
needs within a single distributed system. We already have different database
products in the market each for a specific business need, for example:
RDBMS
Relational databases are used for transactional needs (storing financial data,
reporting requirements, etc.)
MongoDB
Documented oriented databases are used for documents oriented needs (for
e.g. Product Catalog). Documents are schema free so changes in the schema
can be accommodated into application without much headache.
Cassandra/Amazon DynamoDB
Redis
Neo4j
https://www.mongodb.com/customers/guardian?c=2b0a518bc6
References.
https://martinfowler.com/bliki/PolyglotPersistence.html
Why Microservices are better than Monoliths?
Microservices architecture is meant for developing large distributed systems that
scale with safely. There are many benefits of microservices architecture over
monoliths, for example:
However, there was lack of consensus on how to do SOA well. Problems with
SOA revolve around communication protocol used for inter-service
communication (which is SOAP mostly), vendor middleware lockin (Enterprise
Service Bus) and there was no clear guidance on strategy to partition system into
fleet of services. SOA does not help you understand how to partition your system
correctly into multiple microservices, it does not give emphasis to bounded
context (and low coupling high cohesiveness principle).
Microservice architecture has emerged from real world use of SOA, taking our
understanding of system and architecture of doing SOA well.
SOA and Microservices are movements rather than Technology. These are
styles of architecting a scalable application.
— Martin Fowler
What is difference between small-services and
microservices?
Microservices are usually small but not all small services are microservices. If
any service is not following Bounded Context Principle, Single Responsibility
Principle, etc. then it is not a microservice irrespective of its size. So the size is
not the only eligibility criteria for a service to become microservice.
Autonomous Deployments
Culture Shift
Technology Diversification
DevOps Culture
Product catalogue
responsible for product information, searching products, filtering products
& products facets
Inventory
Orders
Payments
Shipments
Demand generation
User Accounts
Recommendations
Notifications
The client application (browser, mobile app) will interact with these services via
API gateway and render the relevant information to user.
When you open Amazon website, the api-gateway may be interacting with 100s
of microservices to just render a page that is most relevant for you. other
interesting facts about Amazon as of 2015:
Run by two-pizza teams (team size limited to two pizzas for a team meal)
Teams control their own destiny (product planning, Dev, Ops, and QA)
Reference.
https://apigee.com/about/blog/developer/microservices-amazon
How big a single microservice should be?
A good, albeit non-specific, rule of thumb is as small as possible but as big
as necessary to represent the domain concept they own.
— Martin Fowler
Synchronous Communication
RestTemplate, WebClient, FeignClient can be used for synchronous
communication between two microservices. Ideally we should minimize the
number of synchronous calls between microservices because networks are brittle
and they introduce latency. Ribbon - a client side load balancer can be used for
better utilization of resource on the top of RestTemplate. Hystrix circuit breaker
can be used to handle partial failures gracefully without cascading effect on the
entire ecosystem. Distributed commits should be avoided at any cost, instead we
shall opt for eventual consistency using asynchronous communication.
Asynchronous Communication
In this type of communication the client does not wait for a response, instead it
just sends the message to the message broker. AMQP (like RabbitMQ) or Kafka
can be used for asynchronous communication across microservices to achieve
eventual consistency.
What shall be preferred communication style in
microservices: synchronous or asynchronous?
Synchronous communication involves blocking call where the calling thread is
blocked from doing anything else till the response is returned. Asynchronous
communication, on the other hand is essentially event based, where the calling
thread does not need to wait for the immediate response. Thus higher throughput
can be achieved using asynchronous communication on the same hardware.
There are other problems with synchronous communication - that the networks
are not reliable. So client has posted a response that needs to be stored in
multiple microservices, then data might become inconsistent if synchronous
communication failure occurs.
Practical example.
ACID compliance
Atomicity
In a transaction involving two or more entities, either all of the records are
committed or none are.
Consistency
Isolation
Any transaction in progress (not yet committed) must remain isolated from
any other transaction.
Durability
Committed records are saved by database such that even in case of a failure
or database restart, the data is available in its correct state.
https://www.thoughtworks.com/insights/blog/case-continuous-delivery
https://githubengineering.com/move-fast/
4. Many Google services see releases multiple times a week, and almost
everything in Google is developed on mainline.
https://www.infoq.com/news/2014/03/etsy-deploy-50-times-a-day
Blue-green deployment.
You can run a smoke-test suite to verify that the functionality is running
correctly in the newly deployed version. Based on the results of smoke-test,
newer version can be released to become the live version.
/src/main/resources/application.yml.
spring.application.name: books-service
---
spring.profiles: blue
eureka.instance.hostname: books-service-blue.example.com
---
spring.profiles: green
eureka.instance.hostname: books-service-green.example.com
Now the client app that needs to make api calls to books-service may look like
below:
/sample/ClientApp.java.
@RestController
@SpringBootApplication
@EnableDiscoveryClient
public class ClientApp {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@RequestMapping("/hit-some-api")
public Object hitSomeApi() {
return restTemplate().getForObject("https://books-service/some-uri"
}
To fix this, we can use Spring Retry support in Ribbon client-side load balancer.
To enable Spring Retry, we need to follow the below steps:
Once this is done, Ribbon will automatically configure itself to use retry logic
and any failed request to books-service-green.example.com will be retried to
next available instance (in round-robins fashion) by Ribbon. You can customize
this behavior using the below properties:
/src/main/resources/application.yml.
ribbon:
MaxAutoRetries: 5
MaxAutoRetriesNextServer: 5
OkToRetryOnAllOperations: true
OkToRetryOnAllErrors: true
Note
1. https://github.com/spring-cloud/spring-cloud-netflix/issues/1290
2. https://www.quora.com/What-is-the-role-played-by-Netflix-Eureka-in-blue-
green-deployment-done-on-Pivotal-Cloud-Foundry
How to achieve zero downtime
deployment(blue/green) when there is a database
change?
The deployment scenario becomes complex when there are database changes
during the upgrade. There can be two different scenarios: 1. database change is
backward compatible (e.g. adding a new table column) 2. database change is not
compatible with older version of application (e.g. renaming an existing table
column)
Graphite is built to handle large amount of data. It can record and show
trends from few hours to few months within a few seconds. Many
organizations like- Facebook etc use Graphite for monitoring. More
information can be found here-
https://graphiteapp.org/
https://github.com/codecentric/spring-boot-admin
These are unique IDs like GUID that can be passed from one service to another
service during an API call. By using a GUID and failure message associated
with it we can find the source of failure as well as the impact of failure.
What are different layers of a single microservice?
Like any typical Java application, a microservice has layered architecture. Most
common layers in any microservice are:
Note
You can also use Docker container to ship and deploy the entire executable
package onto a cloud environment. Docker can also help eliminate "works on
my machine" problem by providing logical separation for the runtime
environment during the development phase. That way you can gain portability
across on premises and cloud environment.
Is it a good practice to deploy multiple microservices
in a single tomcat container (servlet container)?
As stated in 12 factor app, we should deploy each microservice in a separate
process to allow independent scaling. Therefore, it is best to run each
microservice in its own servlet container (Tomcat, Jetty, Undertow, etc.).
Define port as 0 in config file will make your application take a random port.
The actual server port can be captured inside a variable using
${local.server.port}
In real cloud environment, Eureka client discovery can be used to locate the host
and port number dynamically, thereby eliminating the need for knowing the host
and port in advance.
Spring Boot provides us with options to use Tomcat, Jetty or Undertow servlet
container with just few lines of code in build.gradle file.
build.gradle.
dependencies {
compile('org.springframework.boot:spring-boot-starter-web') {
exclude module: "spring-boot-starter-tomcat"
}
compile("org.springframework.boot:spring-boot-starter-
undertow")
//...other dependencies
}
In above example we have excluded the default tomcat and opted for
undertow as the embedded servlet container for Spring Boot Application.
What are Cloud Native applications?
Cloud Native Applications (NCA) is a style of application development that
encourages easy adoption of best practices in the area of continuous delivery and
distributed software development. These applications are designed specifically
for a cloud computing architecture (AWS, Azure, CloudFoundary, etc).
DevOps, continuous delivery, microservices and containers are the key concepts
in developing cloud native applications.
Spring Boot, Spring Cloud, Docker, Jenkins, Git are few tools that can help you
write Cloud Native Application without much effort.
Microservices
DevOps
Continuous Delivery
Containers
Tip
References.
https://pivotal.io/cloud-native
What is Spring Boot?
Spring Boot makes it easy to create stand-alone, production grade Spring
based applications that you can "just run" with an opinionated view of the
Spring platform and third-party libraries so you can get started with
minimum fuss.
2. Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
You can create a Spring Boot starter project by selecting the required
dependencies for you project using online tool hosted at https://start.spring.io/
The main java class for Spring Boot application will look something like the
following:
SampleApplication.
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@Controller
@EnableAutoConfiguration
public class SampleController {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World!";
}
You can directly run this class, without deploying it to a servlet container.
Development Distributed/versioned
Spring Cloud Config Server
Patterns configuration management
Resiliency
Client side load balancing Spring Cloud Netflix Ribbon
Patterns
Logging
Log Correlation Spring Cloud Sleuth
Patterns
Spring Cloud makes it really easy to develop, deploy and operate JVM
applications for the Cloud.
Different release trains in Spring Cloud at the time of writing this handbook are
(newest to oldest) - Finchley, Edgware, Dalston and Camden. Spring Cloud is
always used in conjunction with Spring Boot.
A bare minimum build.gradle for any Spring Cloud project will look like:
build.gradle.
buildscript {
ext {
springBootVersion = '1.5.12.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-
plugin:${springBootVersion}")
}
}
dependencyManagement {
imports {
mavenBom ':spring-cloud-dependencies:Edgware.SR3'
}
}
dependencies {
compile ':spring-cloud-starter-config'
compile ':spring-cloud-starter-eureka'
}
Reference.
http://projects.spring.io/spring-cloud/
What is difference between application.yml and
bootstrap.yml?
application.yml
You can store all the external properties for you application in this file. Common
properties that are available in any Spring Boot project can be found at:
https://docs.spring.io/spring-boot/docs/current/reference/html/common-
application-properties.html You can customize these properties as per your
application needs. Sample file is shown below:
/src/main/resources/application.yml.
spring:
application:
name: foobar
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost/test
server:
port: 9000
Reference.
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-
external-config.html
bootstrap.yml
1. When used with Spring Cloud Config server, you shall specify the
application-name and config git location using below properties
/src/main/resources/bootstrap.yml.
spring.application.name: <application-name>
spring.cloud.config.server.git.uri: <git-uri-config>
2. When used with microservices (other than cloud config server), we need to
specify the application name and location of config server using below
properties
/src/main/resources/bootstrap.yml.
spring.application.name: <application-name>
spring.cloud.config.uri: <http://localhost:8888>
3. This properties file can contain other configuration relevant to Spring Cloud
environment for e.g. eureka server location, encryption/decryption related
properties.
Tip
Upon startup, Spring Cloud makes an HTTP(S) call to the Spring Cloud
Config Server with the name of the application and retrieves back that
application’s configuration.
build.gradle.
compile('org.springframework.boot:spring-boot-starter')
Tip
Spring Cloud solves this problem by providing few ready made solutions for this
challenge. There are mainly two options available for the service discovery -
Netflix Eureka Server and Consul. Lets discuss both of these briefly:
1. It provides service-registry.
Eureka server provides a basic dashboard for monitoring various instances and
their health in service registry. The ui is written in freemarker and provided out
of the box without any extra configuration. Screenshot for Eureka Server looks
like the following.
It contains list all services that are registered with Eureka Server. Each server has
information like zone, host, port and protocol.
Consul Server
It is a REST based tool for dynamic service registry. It can be used for
registering a new service, locating a service and health checkup of a service.
You have a option to choose any one of the above in your spring cloud based
distributed application. In this book, we will focus more on the Netflix Eureka
Server option.
How does Eureka Server work?
There are two main components in Eureka project: eureka-server and eureka-
client.
Eureka Server
The central server (one per zone) that acts as a service registry. All
microservices register with this eureka server during app bootstrap.
Eureka Client
There is usually one eureka server cluster per region (us, asia, europe, australia)
which knows only about instances in its region. Services register with Eureka
and then send heartbeats to renew their leases every 30 seconds. If the service
can not renew their lease for few times, it is taken out of server registry in about
90 seconds. The registration information and the renewals are replicated to all
the eureka nodes in the cluster. The clients from any zone can look up the
registry information (happens every 30 seconds) to locate their services (which
could be in any zone) and make remote calls.
Eureka clients are built to handle the failure of one or more Eureka servers.
Since Eureka clients have the registry cache information in them, they can
operate reasonably well, even when all of the eureka servers go down.
https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance
How to externalize configuration in a distributed
system?
Spring Cloud Config provides server and client side support for externalized
configuration in a distributed system. With a config server we have a central
place to manage external properties for applications across all environments. The
default implementation of the config-server storage uses git so it easily
supports labelled versions of configuration environments.
Config Server Architecture
config-server
config-dev
config-qa
config-prod
/src/main/resources/bootstrap.yml.
spring.cloud.config.uri: http://localhost:8888
If you are using Spring Cloud Netflix and Eureka Service Discovery then you
can have Config Server register with the Discovery Service and let all clients get
access to config server via discovery service.
Discovery-first approach
/src/main/resources/bootstrap.yml.
spring:
cloud:
config:
discovery:
enabled: true
This property should be provided by all microservices so that they can take
advantage of discovery first approach.
The benefit of this approach is that now config-server can change its host/port
without other microservices knowing about it, since each microservice can get
the configuration via eureka service now. The downside of this approach is that
an extra network round trip is required to locate the service registration at app
startup.
How to halt a Spring Boot based microservice at
startup if it can not connect to Config Server during
bootstrap?
If you want to halt the service when it is not able to locate the config-server
during bootstrap, then you need to configure the following property in
microservice’s bootstrap.yml:
/src/main/resources/bootstrap.yml.
spring:
cloud:
config:
fail-fast: true
Using this configuration will make microservice startup fail with an exception
when config-server is not reachable during bootstrap.
We can enable retry mechanism where microservice will retry 6 times before
throwing an exception. We just need to add spring-retry and spring-boot-
starter-aop to the classpath to enable this feature.
build.gradle.
...
dependencies {
compile('org.springframework.boot:spring-boot-starter-aop')
compile('org.springframework.retry:spring-retry')
...
}
How to refresh configuration changes on the fly in
Spring Cloud environment?
Using config-server, its possible to refresh the configuration on the fly. The
configuration changes will only be picked by Beans that are declared with
@RefreshScope annotation.
The following code illustrates the same. The property message is defined in the
config-server and changes to this property can be made at runtime without
restarting the microservices.
package hello;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class ConfigClientApplication {
@RefreshScope
@RestController
class MessageRestController {
@Value("${message:Hello World}")
private String message;
@RequestMapping("/message")
String getMessage() {
return this.message;
}
}
@RefreshScope makes it possible to dynamically reload the configuration
for this bean.
How to achieve client side load balancing in Spring
Microservices using Spring Cloud?
Naive approach would be to get list of instances of a microservice using
discovery client and then randomly pickup one instance and make the call. But
that’s not easy because that service instance may be down at the moment and we
will have to retry the call again on next available instance.
Ribbon does that all internally. Ribbon will do at-least the following-
1. A central concept in Ribbon is that of the named client. more about named
clients.
4. Its easy to use Hystrix with Ribbon and use circuit breaker for managing
fault tolerance.
6. Keeping statistics of servers and avoid servers with high latency or frequent
failures.
Under the hood, Ribbon Load Balancer uses the following 3 components-
We shall always use Ribbon, a client side load balancing library to distribute the
load among various instance of a microservice deployment.
build.gradle.
compile('org.springframework.cloud:spring-cloud-starter-ribbon')
/src/main/resources/application.yml.
ribbon:
ConnectTimeout: 60000
ReadTimeout: 60000
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: true
http:
client:
enabled: true
Now whatever calls you make using this RestTemplate, will be load balanced
using default round-robin fashion.
How to use both LoadBalanced as well as normal
RestTemplate object in the single microservice?
We can create two different beans with different qualifiers, one with load
balanced configuration another without ribbon load balancer.
@Bean(name = "simpleRestTemplate")
public RestTemplate simpleRestTemplate(RestTemplateBuilder restTemplateBuil
RestTemplate restTemplate = restTemplateBuilder.build();
return restTemplate;
}
application.yml.
stores:
ribbon:
listOfServers: example.com,google.com,microsoft.com
We can also explicitly disable the use of Eureka in Ribbon using the following
configuration.
application.yml.
ribbon:
eureka:
enabled: false
In the above code, api calls to stores will be load-balanced among three servers -
example.com,google.com,microsoft.com
How will you use ribbon load balancer
programmatically?
You can use Ribbon load-balancer client to fetch appropriate instance of a given
microservice. Following example code demonstrate the same:
import java.net.URI;
@Component
public class RibbonLoadBalancer implements ApplicationRunner {
@Autowired
private LoadBalancerClient loadBalancer;
@Override
public void run(ApplicationArguments args) throws Exception {
doStuff();
}
}
@EnableDiscoveryClient
@EnableEurekaClient
@EnableEurekaClient is a convenience annotation for clients to enable Eureka
discovery configuration (specifically). This annotation is very much specific to
Eureka based clients.
Tip
When using Eureka Server, you can use any of these convenient annotation
to enable discovery configuration.
How to make microservices zone aware so as to prefer
same zone services for inter-service communication
using Spring Cloud?
If you have deployed Eureka clients to multiple zones than you may prefer that
those clients leverage services within the same zone before trying services in
another zone. To do this you need to configure your Eureka clients correctly.
First, you need to make sure you have Eureka servers deployed to each zone and
that they are peers of each other. See the section on zones and regions for more
information.
Next you need to tell Eureka which zone your service is in. You can do this
using the metadataMap property. For example if service 1 is deployed to both
zone 1 and zone 2 you would need to set the following Eureka properties in
service 1
Service 1 in Zone 1.
eureka.instance.metadataMap.zone: <zone1>
eureka.client.preferSameZoneEureka: true
Service 1 in Zone 2.
eureka.instance.metadataMap.zone: <zone2>
eureka.client.preferSameZoneEureka: true
How to list all instances of a single microservice in
Spring Cloud environment?
You can use
org.springframework.cloud.client.discovery.DiscoveryClient which
provides a simple API for discovering microservices registered in service-
registry.
In the above code, discoveryClient will scan the service-registry for given
server-key i.e. STORES and fetch all the instances registered with this key.
DiscoveryClient may cache the results for performance reasons.
What is API Gateway?
API Gateway is a special class of microservices that meets the need of a single
client application (such as android app, web app, angular JS app, iPhone app,
etc) and provide it with single entry point to the backend resources
(microservices), providing cross cutting concerns to them such as security,
monitoring/metrics & resiliency.
API Gateway
zuul.routes:
customer-service:
path: /customer/**
serviceId: customer-service
product-service:
path: /product/**
serviceId: product-service
4. Path Rewriting
application.properties.
zuul.ignored-patterns: /*/health, /*/sensitive-endpoint
How to protect Sensitive Security Tokens from leaking
into downstream system?
We can define what headers are sensitive at the API Gateway configuration.
/src/main/resources/application.yml.
zuul.ignoreSecurityHeaders: false
zuul.sensitiveHeaders: Q
Here we are instructing zuul to relay token to the downstream systems. We can
also configure the sensitive headers that should not be relayed.
How to retry failed requests at some other available
instance using Client Side Load Balancer?
Spring Cloud Netflix provides two main mechanisms for making a HTTP
requests using load balanced client (Ribbon) - RestTemplate and Feign. There is
always a chance that a network call may fail due to any reason, and we may
want to retry that request automatically on the next available server.
To enable this feature in Spring Cloud Netflix, we just need to include Spring
Retry library on the classpath. When Spring Retry is present, the load balanced
RestTemplate, Feign and Zuul will automatically retry any failed requests.
build.gradle.
compile('org.springframework.retry:spring-retry')
/src/main/resources/application.yml.
client.ribbon.MaxAutoRetries: 3
client.ribbon.MaxAutoRetriesNextServer: 1
client.ribbon.OkToRetryOnAllOperations: true
Incase of Zuul Reverse Proxy, we can turn off retry functionality by setting
/src/main/resources/application.yml.
zuul.retryable: false
/src/main/resources/application.yml.
zuul:
routes:
<routename>.retryable: false
This can lead to cascading failures in the calling service due to threads being
blocked in the hung remote calls. Circuit breaker is a piece of software that is
used to solve this problem. The basic idea is very simple - wrap a potentially
failing remote call in a circuit breaker object that will monitor for
failures/timeouts. Once the failures reach a certain threshold, the circuit breaker
trips, and all further calls to the circuit breaker return with an error, without the
protected call being made at all. This mechanism can protect the cascading
effects of a single component failure in the system and provide a option to
gracefully downgrade the functionality. A typical use of circuit breaker in
microservices architecture looks like the following diagram-
https://martinfowler.com/bliki/CircuitBreaker.html
What are Open, Closed and Half-Open states of
Circuit Breaker?
Circuit Breaker wraps the original remote calls inside it and if any of these calls
fails, the failure is counted. When the service dependency is healthy and no
issues are detected, the circuit breaker is in Closed State. All invocations are
passed through to the remote service.
If the failure count exceeds a specified threshold within a specified time period,
the circuit trips into the Open State. In the Open State, calls always fail
immediately without even invoking the actual remote call. The following factors
are considered for tripping the circuit to Open State -
1. Traffic volume.
2. Request volume.
3. Error percentage.
4. Hosts reporting
5. Latency percentiles.
All these metrics can be aggregated using another Netflix OSS project called
Turbine. Hystrix dashboard can be used to visualize these aggregated metrics,
providing excellent visibility into the overall health of the distributed system.
Tip
https://medium.com/netflix-techblog/fault-tolerance-in-a-high-volume-
distributed-system-91ab4faae74a
What are main features of Hystrix library?
Hystrix library makes our distributed system resilient (adaptable & quick to
recover) to failures. It provides three main features:
Real-time operations
Using real-time metrics, you can remain alerted, make decisions, affect
changes and see results.
Concurrency
https://github.com/Netflix/Hystrix/
https://github.com/Netflix/Hystrix/wiki#principles
https://github.com/Netflix/Hystrix/wiki/How-it-Works
How to use Hystrix for fallback execution?
Hystrix can be used to specify the fallback method for execution in case actual
method call fails. This can be useful for graceful degradation of functionality
incase of failure in remote invocation.
...
}
fallback method should have same signature (return type) as that of original
method. This method provides a graceful fallback behavior while circuit is
in open or half-open state.
When not to use Hystrix fallback on a particular
microservice?
You do not need to wrap each microservice call within hystrix, for example
1. The api’s that will never be invoked from another microservice shall not be
wrapped in hystrix command.
import java.net.URI;
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancer;
@Autowired
private RestTemplate restTemplate;
Reference.
https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-
javanica#error-propagation
What is Strangulation Pattern in microservices
architecture?
Strangulation is used to slowly decommission an older system and migrate the
functionality to a newer version of microservices.
Normally one endpoint is Strangled at a time, slowly replacing all of them with
the newer implementation. Zuul Proxy (API Gateway) is a useful tool for this
because we can use it too handle all traffic from clients of the old endpoints, but
redirect only selected requests to the new ones.
/src/main/resources/application.yml.
zuul:
routes:
first:
path: /first/**
url: http://first.example.com
legacy:
path: /**
url: http://legacy.example.com
Paths in /first/** have been extracted into a new service with an external
URL http://first.example.com
legacy app is mapped to handle all request that do not match any other
patterns (/first/**).
This configuration is for API Gateway (zuul reverse proxy), and we are
strangling selected endpoints /first/ from the legacy app hosted at
http://legacy.example.com slowly to newly created microservice with external
URL http://first.example.com
https://projects.spring.io/spring-cloud/spring-
cloud.html#_strangulation_patterns_and_local_forwards
What is Circuit Breaker?
Circuit breaker is used to gracefully degrade the functionality when one of the
service/method call fails, without cascading the effect that can bring down the
entire system.
Wrap a protected (mostly remote) call inside a circuit breaker object, which
monitors for failures. Once the failures reach a certain threshold, trip the circuit
and immediately return the error (or fallback response) without even making a
actual remote call, thus giving failing service time to recover.
How it helps?.
If many of the caller threads are being timeout on a failed resource, we may
quickly exhaust all our threads and the caller application will also get impacted
due to this. Such failure can cascade and bring down the entire system. Circuit
breaker does not allow this to happen by tripping off the circuits that are
potentially failing. We achieve this fault-tolerance at the expense of degraded
functionality.
What is difference between using a Circuit Breaker
and a naive approach where we try/catch a remote
method call and protect for failures?
Lets say we want to handle service to service failure gracefully without using
Circuit Breaker pattern. The naive approach would be to wrap the inter service
REST call in try catch clause. But Circuit Breaker does lot more that try catch
can not accomplish -
1. Circuit Breaker does not even try calls once failure threshold is reached,
doing so reduces the number of network calls. Also, number of threads
consumed in making faulty calls are freed up.
So instead of wrapping service to service calls with try/catch clause, we must use
circuit breaker pattern to make our system resilient to failures.
What is Request Collapsing feature in Hystrix?
We can front a HystrixCommand with a request collapser (HystrixCollapser is
the abstract parent) with which we can collapse multiple requests into a single
back-end dependency call. Using request collapser reduces the number of
threads and network connections needed to perform concurrent
HystrixCommand executions, that too in an automated manner without forcing
developers to coordinate the manual batching of requests.
More Information.
https://github.com/Netflix/Hystrix/wiki/How-it-Works#RequestCollapsing
What is difference between Circuit Breaker and
Hystrix?
Circuit Breaker is a fault tolerance design pattern, while Netflix’s Hystrix library
provides an implementation for the circuit breaker pattern. We can easily apply
circuit breakers to potentially-failing method calls (in JVM or over the network)
using the Netflix Hystrix fault tolerance library.
Where exactly should I use Circuit Breaker Pattern?
At all places on server side where we are making a service to service call, for
example 1. API Gateway 2. Aggregator Services 3. Web Front that calls multiple
microservices to render a single page
All those places where remote calls can potentially fail are good candidate for
using Circuit Breaker Pattern.
Lets say, you are calling a REST endpoint directly from a mobile client, and
there is no inter-service calls involved in this case, except at API Gateway. So
there is no need for the circuit breaker except at API gateway level. Android
client should be designed to gracefully handle service failures in this case.
What is bulkhead design pattern?
A ship’s hull is divided into different watertight compartments (called as
bulkheads) so that if the hull is compromised, the failure is limited to that
bulkhead as opposed to taking the entire ship down.
By partitioning our system for fault tolerance, we can confine failures (timeouts,
OOME, etc.) to one area and prevent the entire system from failing due to
cascading effects.
Consider combining bulkheads with retry, circuit breaker, and throttling patterns
to provide more sophisticated fault handling. Both circuit breaker as well as
bulkhead design pattern falls under the category of Software Resiliency Design
Patterns, they bring resiliency to the distributed system.
How does Hystrix implements Bulkhead Design
Pattern?
The bulkhead implementation in Hystrix limits the number of concurrent calls to
a component/service. This way, the number of resources (typically threads) that
is waiting for a reply from the component/service is limited.
Lets assume we have a fictitious web application as shown in figure below. The
WebFront communicates with 3 different components using remote network
calls (REST over HTTP).
Order Service
Now lets say due to some problem in Product Review Service, all requests to
this service start to hang (or timeout), eventually causing all request handling
threads in WebFront Application to hang on waiting for an answer from Reviews
Service. This would make the entire WebFront Application non-responsive. The
resulting behavior of the WebFront Application would be same if request volume
is high and Reviews Service is taking time to respond to each request.
Hystrix' has two different approaches to the bulkhead, thread isolation and
semaphore isolation.
The advantage of the thread pool approach is that requests that are passed to C
can be timed out, something that is not possible when using semaphores.
Thread Isolation
The standard approach is to hand over all requests to component C to a separate
thread pool with a fixed number of threads and no (or a small) request queue.
Semaphore Isolation
The other approach is to have all callers acquire a permit (with 0 timeout) before
requests to C. If a permit can’t be acquired from the semaphore, calls to C are
not passed through.
In microservices architecture, what are smart
endpoints and dumb pipes?
Martin Fowler introduced concept of "smart endpoints & dumb pipes" while
describing microservices architecture.
To give context, one of the main characteristic of a unix based system is to build
small small utilities and connect them using pipes. For example, very popular
way of finding all java processes in linux system is
Here two commands are separated by pipe, the pipe’s job is to forward the
output of first command as an input to second command, nothing more. Its like a
dumb pipe which has no business logic except the routing of data from one
utility to another.
Microservices team should follow the principles and protocols that world wide
web & unix is built on.
Reference.
https://martinfowler.com/articles/microservices.html
What is difference between Semaphore and
ThreadPool based configuration in Hystrix?
The default behavior of hystrix in spring cloud environment is to use Thread
Isolation strategy for wrapping network calls. Using semaphore isolation is very
easy when we want to propagate security context (AccessToken) to the
downstream server.
Thread Isolation
The standard approach is to hand over all requests to component C to a separate
thread pool with a fixed number of threads and no (or a small) request queue.
Semaphore Isolation
The other approach is to have all callers acquire a permit (with 0 timeout) before
requests to C. If a permit can’t be acquired from the semaphore, calls to C are
not passed through.
How to handle versioning of microservices?
There are different ways to handle the versioning of your REST api to allow
older consumers still consume the older endpoints. The ideal practice is that any
non backward compatible change in a given REST endpoint shall lead to a new
versioned endpoint.
Versioned URL.
https://<host>:<port>/api/v1/... https://<host>:<port>/api/v2/...
As a API developer you must ensure that only backward compatible changes are
accommodated in a single version of URL. Consumer-Driven-Tests can help
identify potential issues with API upgrades at an early stage.
What is difference between partitioning microservices
based on technical capabilities vs business
capabilities? Which one is better?
An enterprise level application should be partitioned into multiple microservices
based on its business capabilities as laid down in Domain Driven Design
(Bounded context principle).
If we want to run multiple instances of single app on same server, then we need
to assign a different port at runtime. To solve this problem we need to choose
random available port at app startup. Since the app will register itself with
eureka service registry, other apps can still discover this service through service
registry.
Calling the custom method to set the random available port within range
[5000-5500] and update the server.port property as well.
Tip
Always use Eureka Registry to fetch the service details e.g. host, port and
protocol. Never use hardcoded host, port while communicating with one
microservice from another. So you never need to know in advance what
port and host a particular service has been deployed.
How will you run certain business logic at the app
startup?
Often there is a need to execute certain business logic at servlet container startup
for e.g. fetching feeds from a remote service, sending a notification about app
startup by email, etc.
Spring boot provides two main mechanisms for wiring app startup code. This
code will be executed once spring context has been loaded and servlet container
is ready to serve requests.
1. Using CommandLineRunner
2. Using ApplicationRunner
For example, in the below code we have created two startup runners that execute
in a sequence.
@Order(1)
@Component
class StartupExecutorOne implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("StartupExecutorOne invoked");
}
}
@Order(2)
@Component
class StartupExecutorTwo implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("StartupExecutorTwo invoked");
}
}
Program output.
Important
On the other hand ApplicationRunner code will only run once the entire
spring context have been initialized.
How to correctly implement a reporting microservice
in a distributed system?
Implementing a reporting service requires collecting data from multiple
microservices, hence its a unique problem in distributed systems. Preserving
Bounded Context is very important aspect in microservices architecture, and
specially in reporting kind of service.
Operational and the reporting needs are often different - with different
requirements from schema, and different access patterns. In a complex system,
it’s always a good idea to separate reporting needs into a separate microservice
having its own database. This microservice should take copy of the essential
operational data and persist it in a different schema that is most appropriate for
reporting needs.
Each microservice can refactor its own database without affecting the
reporting database.
Queries run against the reporting database does not add load to operational
database.
You may have multiple reporting databases for different reporting needs.
You can store derived data in the database, making it easier to write reports
that use the derived data without having to introduce a separate set of
derivation logic.
Let’s first see what are the wrong ways to implement a reporting solution within
microservices architecture.
Pulling data directly from the database seems to be fastest option, as there is no
other layer involved in this. But it leads to significant interdependence between
individual microservice and the reporting service, making it hard to
accommodate any refactoring in microservice. So this approach is efficient but
tightly coupled.
Lets now head towards the right approach for implementing Reporting Service
Model.
The first two models were pull models where reporting microservice pulls data
from individual microservice and that’s a anti-pattern. Ideal way should be a
push model where each microservice push data to reporting microservice.
So a better design would be to use Event Driven Approach, where each service
will emit an event (OrderCreated, UserRegistered, etc.) and reporting service
will listen to those events and create reporting. The following diagram illustrates
a better design for correctly implementing a reporting service in any distributed
system.
https://martinfowler.com/bliki/ReportingDatabase.html
What is Event Sourcing and CQRS? When should it
be used? Should be use it for the entire system?
Event Sourcing should only be used for the specific scenarios, not everywhere in
the system, otherwise it will complicate the implementation of the system.
Shipping Tracker microservice could be an ideal candidate for the event sourcing
technique.
How to send business errors from a RESTful
microservice to client application?
In RESTful applications, often is not enough to just send the HTTP error codes
for representing the business errors. HTTP error codes can tell about the kind of
failure but will not be able to point to business error codes. The solution to this is
to wrap our service response inside a custom object that can reveal more
information about the failure reason, error codes for machine and developers.
The below class is an example showing how we can wrap additional information
about the business exception such as errorCode, userMessage,
developerMessage, etc. inside a service response.
T data;
boolean success;
int errorCode;
String moreInfo;
String userMessage;
String developerMessage;
12254 here is the business error code that api client would know how to
deal with.
Is it a good idea to share common database across
multiple microservices?
In microservices architecture, each microservice shall own its private data which
can only be accessed by outside world through owning service. If we start
sharing microservice’s private datastore with other services, then we will violate
principle of Bounded Context.
1. Database server per microservice - Each microservice will have its own
database server instance. This approach has overhead of maintaining
database instance and its replication/backup, hence its rarely used in
practical environment.
3. Private Table per microservice - Each microservice owns a set of tables that
must only be accessed by that service. Its a logical separation of data. This
approach is mostly used for hosted database as service solution (Amazon
RDS).
Note
If we are using database as a service (like AWS dynamoDB), then you shall
prefer private table-per-service approach, where each microservice owns a
set of tables that must only be accessible by that service. It is mostly a
logical separation of data. In this way we can have a single DynamoDB
instance for entire fleet of microservices.
How will you make sure that the email is only sent if
the database transaction does not fail?
Spring provides two main mechanisms to handle this situation -
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
Using TransactionSynchronizationManager.
@Transactional
public void create(String firstName, String lastName, String
User user = new User();
user.setEmail(email);
user.setFirstName(firstName);
user.setLastName(lastName);
userRepository.save(user);
TransactionSynchronizationManager.registerSynchronization
public void afterCommit() {
//do stuff right after commit is successful
//TODO: Send welcome email to user.
}
});
}
But the above mentioned code does not guarantee the atomicity of two
operations i.e. if the transaction is successful but email send fails then spring will
not rollback the transaction.
How will you atomically update the database and
publish an event to message broker from single
transaction?
Practical Problem.
Caution
One way to achieve atomicity in this case is to publish events using a multi-step
process involving local transactions.
How will you propagate security context of user when
one microservice calls another microservice on behalf
of user?
There are multiple ways the security context can be passed over service to
service call. If Oauth2 is configure for security, then OAuth2 Token Relay can be
used to propagate the user’s accessToken to the next server. If we are using Basic
Auth, then authorization header can be passed to the downstream system.
application.yml.
proxy:
auth:
routes:
customers: oauth2
stores: passthru
recommendations: none
In this example the "customers" service gets an OAuth2 token relay, the "stores"
service gets a passthrough (the authorization header is just passed downstream),
and the "recommendations" service has its authorization header removed. The
default behaviour is to do a token relay if there is a token available, and passthru
otherwise.
What is Token Relay in Spring Security?
A Token Relay happens when an OAuth2 consumer acts as a client and forwards
the incoming OAuth2 Token to the outgoing resource request. The Consumer
can be a pure client (like an SSO application) or a Resource Server.
How to Enable Token Relay?
If your app is a user facing OAuth2 client (i.e. has declared @EnableOAuth2Sso
or @EnableOAuth2Client) then Spring Boot will automatically create a
OAuth2ClientContext and OAuth2ProtectedResourceDetails in request scope.
You can create your own OAuth2RestOperations using these two beans, as
shown in below code. And then the context will always forward the access token
downstream, also refreshing the access token automatically if it expires. (These
are features of Spring Security and Spring Boot.)
Important
JWT uses cryptography to validate the access token validity and its claims. JWT
tokens are signed by authentication server that has private key, all resource
servers can validate the signed JWT token using their public key. So JWT is
essentially a decentralized security mechanism.
JWT is like Currency, where you need not to go to RBI whenever someone
hands over 100 Rs Note to you, every citizen can check the validity of a
currency note using security features provided by RBI. Thats the power of
cryptography and distributed computing.
If the damage is system wide, then its better to change the private key used in
authentication service to generate the Access and RefreshToken. Thus any token
that had been issued before data breach will become invalid and resource server
will deny access to resource. The only side effect of this approach will be that all
the users be logged out of the system and they need to login again to access
protected resources.
If the damage is local, say a single person’s access/refresh token has been stolen,
then we shall store a device id or some other identifier for each user client
device. Now instead of blocking the JWT, we can block the user’s particular
device that had been stolen.
Shall Authentication and Authorization be one
service?
Not necessarily. Authentication and authorization are two different things.
Authentication needs to know user identity (username and credentials),
authorization service’s responsibility is to issue tokens. So you can create two
different services - one for authentication and another for authorization. But
authorization service will need to talk to authentication service before issuing
tokens so REST communication will be required between the two.
What is API Key security?
API Key is simple form of security for modern web APIs. Its simple because all
we have to do is to set the API Key in Authorization header or the URI itself to
get authenticated. There are downsides to using API Key based security.
3. Its not easy to add custom data to API Key, like we can do in JWT
AccessToken.
4. You need to keep them secret, because these keys does not have expiry. If
you share it, you need to deactivate the existing one and generate a new
one.
What are best practices for microservices
architecture?
Microservices Architecture can become cumbersome & unmanageable if not
done properly. There are best practices that help design a resilient & highly
scalable system. The most important ones are -
Best Practices in Microservices Architecture
Partition correctly
Get to know the domain of your business, thats very very important. Only then
you will be able to define the bounded context and partition your microservice
correctly based on business capabilities.
DevOps culture
Failures are inevitable in distributed systems, so we must design our system for
handling failures gracefully. failures can be of different types and must be dealt
accordingly, for example -
1. Failure could be transient due to inherent brittle nature of the network, and
the next retry may succeed. Such failures must be protected using retry
operations.
2. Failure may be due to a hung service which can have cascading effects on
the calling service. Such failures must be protected using Circuit Breaker
Patterns. A fallback mechanism can be used to provide degraded
functionality in this case.
3. A single component may fail and affect the health of entire system,
bulkhead pattern must be used to prevent entire system from failing.
Since networks are brittle, we should always design our services to accept
repeated calls without any side effects. We can add some unique identifier to
each request so that service can ignore the duplicate request sent over the
network due to network failure/retry logic.
Reference.
https://blogs.oracle.com/developers/getting-started-with-microservices-part-three
Shall we share common domain models or DTOs
across microservices?
Ideally you should not. Creating and sharing unified DTO or Models violates the
principle of Bounded Context. These shared libraries introduces dependencies
between microservices taking away their autonomous behaviour. Moreover these
unified models becomes too complex in any real enterprise application that
hardly any single team can understand it.
For example, if we have a Customer model that will be used in Payment Service,
Shipment Service and Notification Service then we shall create three different
copies of Customer model with attributes appropriate for each individual service.
Notification service needs to know mobile and email for sending alerts
about order to customer.
Caution
Warning
Reference.
https://martinfowler.com/bliki/BoundedContext.html
https://martinfowler.com/tags/domain%20driven%20design.html
There are two open source options available for hosting your own private maven
repository - Artifactory OSS and Nexus. You can use anyone of these in your
own project.
Privately hosted maven repository for Common Library
Once repository is setup, you can use maven-publish to publish shared library
artifacts using below code in gradle project.
group = 'com.shunya.commons'
version = '1.0.0'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
credentials {
username 'admin'
password 'password'
}
url "http://localhost:8081/artifactory/libs-release-
local"
}
}
}
Now this uploaded artifact can be used by all other gradle projects using the
below code:
build.gradle - top level.
allprojects {
repositories {
maven { url "http://localhost:8081/artifactory/libs-
release-local" }
}
}
That’s it. This way each microservice can choose which version of common
library to use. It gives flexibility to rollback to previous version if something
goes wrong.
What is continuous delivery?
Continuous delivery is a software engineering practice in which agile teams
produce software in continuous low-risk short cycles, ensuring that software can
be reliably released at any time. This makes it possible to continuously adapt
software inline with user feedback and changes in business requirements.
If you are using Jenkins, AWS, and CodeDeploy, then the pipeline might look
like below:
https://en.wikipedia.org/wiki/Continuous_delivery
How will you improve the performance of distributed
system?
Introduce Caching at different layers
Techniques like Caching (client side caching, server side caching, proxy
caching) can help improving the query performance of microservices.
Asynchronous Integration can also help bringing performance and resiliency to
the system. If hardware is under your control, then prefer to use optical fibre
communication between microsevrices intranet.
We need to correctly configure the number of threads for the underlying servlet
container. Critical point to consider here is that hystrix when used as semaphore,
will share threads with servlet container. Default value for hystix is 10 threads.
We can configure number of worker threads in spring boot application using
container specific properties defined in application.yml
application.yml.
server:
port: 9090
jetty:
acceptors: 2 (default = 1 + noCores/16)
selectors: 8 (default = noCores)
undertow:
io-threads: 4 (default = noCores/2)
worker-threads: 40 (default = 10)
tomcat:
max-threads: 40
Every time a socket connects, the acceptor threads accepts the connection and
assign the socket to a selector, chosen in a round-robin fashion. The higher the
connection open/close rate, higher the number of acceptor threads you need. For
many many connections, very very active, you want a high number of selectors.
For many many connections, but not very active, fewer selectors are typically
needed.
For 5k clients for normal HTML pages you can easily go by with 1 selector.
https://docs.spring.io/spring-boot/docs/current/reference/html/common-
application-properties.html
HTTP2 Protocol
2. Gateway Cache - central API gateway can cache the query results as per
business needs and provide an improved performance. This way we can
achieve caching for multiple services at one place. Distributed caching
software like Redis or Memcache can be used in this case.
3. Client Side Caching - We can set cache-headers in http response and allow
clients to cache the results for pre-defined time. This will drastically reduce
load on servers since client will not make repeated calls to same resource.
Servers can inform the clients when information is changed, thereby any
changes in the query result can also be handled. E-Tags can be used for
client side load balancing. If the end client is a microservice itself, then
Spring Cache support can be used to cache the results locally.
Which protocol is generally used for client to service
and inter-service communication?
client to service communication
inter-service communication
Performance is increased because the caller will just fire and forget the
request, it does not need to wait for the response from other service thereby
consuming lesser resources (threads and network traffic)
Another powerful advantage of messaging that is not available if you are using
REST communication is the capability to broadcast a message to multiple
services in one go a.k.a publish and subscribe messaging. Publish and Subscribe
usually involves Fanout or Topic Exchange and Subscribers. In event driven
programming, producer just broadcast the message to a topic without knowing
about the consumers and the way message will be consumed by consumers.
Tip
// https://mvnrepository.com/artifact/io.springfox/springfox-
swagger2
compile group: 'io.springfox', name: 'springfox-swagger2',
version: '2.8.0'
compile group: 'io.springfox', name: 'springfox-swagger-ui',
version: '2.8.0'
SwaggerConfig.java.
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.*;
import springfox.documentation.builders.*;
import springfox.documentation.service.*;
@Configuration
@EnableSwagger2
@EnableAutoConfiguration
public class SwaggerConfig {
@Bean
public Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("Product Service")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("hello"))
.paths(PathSelectors.any())
.build();
}
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/"
/*registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
}
https://docs.spring.io/spring-boot/docs/current/reference/html/common-
application-properties.html
You can pick the one that are needed in your project and customize them
according to your business/technical needs.
Security in Microservices
Why Basic Authentication is not suitable in
Microservices Context?
Basic Authentication is natively supported by almost all servers and clients, even
Spring security has very good support for it and its configured out of the box.
But it is not a good fit for Microservices due to many reasons, including -
https://docs.spring.io/spring-
security/site/docs/4.0.4.RELEASE/apidocs/org/springframework/security/crypto/bcrypt/B
5. If we use Basic Auth for a mobile application client, then we might have to
store user’s credentials on the device to allow remember me feature. This is
quite risky as anyone getting access to device may steal the plain
credentials.
Tip
3. Distributed and Stateless. You can validate the token’s authenticity yourself.
6. Lightweight
7. A client application, often web application, acts on behalf of user, but with
user’s approval.
Tip
1. Resource Owner - The person or the application that owns the data to be
shared. When resource owner is a person, it is called as an end-user.
But if you are integrating with Google or Facebook, then this type of grant is not
feasible, because end user will never trust your android app to enter Google’s
credentials. Authorization Code grant is better alternative to such scenarios.
Sample Response.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiI4NTkyM2RkZ
"expires_in": 2628000,
"jti": "9114270c-ea4e-4814-a5b2-3f3e99dc4233",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiI4NTkyM2Rk
"scope": "read",
"token_type": "bearer",
"uid": "85923dde-2423-4ba3-8ca9-a833f6b80d83"
}
When shall I use Authorization Code grant?
It should be used in applications where clientId and clientSecret can be kept
securely i.e. webapps. In this flow the oauth 2.0 client is redirected back to
authorization server’s url for authentication purpose.
This grant type is used when you want to integrate with Google/Facebook on
your server side webapp.
When shall I use client credentials?
Client credentials should be used for inter service communication that is not on
behalf of users i.e. scheduled batch jobs, reporting jobs etc. Unlike Basic Auth,
OAuth 2.0 protocol distinguishes between User (Resource Owner) and Machines
(Client) based on Resource Owner Credentials and Client Credentials. Thus if
the end consumer of your web services is not a human, then client credentials
should be used.
curl service-account-1:service-account-1-
secret@localhost:8080/auth/oauth/token -d grant_type=client_credentials
OAuth2 and Microservices
OAuth2 Use in Microservices Context
3. Browser Clients (single page apps - angular JS, etc) uses Authorization
Code Grant or implicit Grant.
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact
and self-contained way for securely transmitting information between parties as
a JSON object. This information can be verified and trusted because it is
digitally signed. JWTs can be signed using a secret (with the HMAC algorithm)
or a public/private key pair using RSA.
Sources
https://tools.ietf.org/html/rfc7519
https://jwt.io/introduction/
2. JWT contains all the required data about the user, so there is no need to
query the database more than once (once during JWT generation).
Authentication
Information Exchange
JWT can be signed, using public/private key pairs, you can be sure that the
senders are who they say they are. Hence JWT is a good way of sharing
information between two parties. Example usecase could be -
Header
Header contains algorithm information e.g. HS256 and type e.g. JWT
Header Part.
{
"alg": "HS256",
"typ": "JWT"
}
Claim
claim part has expiry, issuer, user_id, scope, roles, client_id etc. It is
encoded as a JSON object. You can add custom attributes to the claim. This
is the information that you want to exchange with the third party.
Claim Part.
{
"uid": "2ce35360-ef8e-4f69-a8d7-b5d1aec78759",
"user_name": "[email protected]",
"scope": ["read"],
"exp": 1520017228,
"authorities": ["ROLE_USER","ROLE_ADMIN"],
"jti": "5b42ca29-8b61-4a3a-8502-53c21e85a117",
"client_id": "acme-app"
}
Signature
Signature Part.
HMACSHA256(base64(header) + "." + base64(payload), "secret")
Tip
JWT Refresh token is used to acquire new Access Token. Token refresh is
handled by the following API endpoint: /api/auth/token. Refresh token should
be used when existing accessToken has expired its validity.
Request.
curl <client-id>:<client-secret>@localhost:9999/uaa/oauth/token -d
grant_type=refresh_token -d refresh_token=$REFRESH_TOKEN
Response.
{
"access_token":"$ACCESS_TOKEN",
"token_type":"bearer",
"refresh_token":"$REFRESH_TOKEN",
"expires_in":86399,
"scope":"openid",
"userId":1,
"authorities":[ROLE_USER],
"jti":"cd4ec2ad-ac88-4dd7-b937-18dd22e9410e"
}
Android Apps can use RestTemplate to renew their accessToken. The returned
accessToken can be saved for subsequent calls to protected resources.
public OAuthTokenDto renewToken(final OAuthTokenDto existingToken) {
String requestUrl = authBaseUrl + "oauth/token";
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "refresh_token");
map.add("refresh_token", existingToken.getRefresh_token());
map.add("scope", "read");
HttpAuthentication authHeader = new HttpBasicAuthentication(CLIENT_ID,
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON
headers.setAuthorization(authHeader);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map
RestTemplate restTemplate = new RestTemplate();
final ResponseEntity<OAuthTokenDto> responseEntity =
restTemplate.exchange(requestUrl, HttpMethod.POST, entity, OAut
return responseEntity.getBody();
}
How to call the protected resource using
AccessToken?
Protected resources from microservices can be accessed by passing
Authorization Bearer token in request headers, using curl the request looks like
below-
curl -H "Authorization: Bearer <AccessToken>" -v
localhost:9090/user/info
JSON Response.
{
"key": "value"
...
}
And then use the above created Headers in RestTemplate, like shown below-
RestTemplate Call.
HttpEntity<Object> httpEntity = new HttpEntity<>(ApplicationContext.
ResponseEntity<String> responseEntity =
restTemplate.exchange(url, HttpMethod.GET, httpEntity, String
Downlaod POSTMAN
Can a refreshToken be never expiring? How to make
refreshToken life long valid?
We can set the validity for refreshToken to a negative value, zero or
Integer.MAX_VALUE to make it never expiring.
@Bean
public AuthorizationServerTokenServices tokenServices() throws Exception
DefaultTokenServices tokenServices = new DefaultTokenServices
tokenServices.setTokenStore(tokenStore);
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(clientDetailsService);
tokenServices.setRefreshTokenValiditySeconds(Integer.MAX_VALUE
return tokenServices;
}
Caution
AWS Cognito only allows 3650 days as the maximum interval for refresh token
expiry. The default expiry is set to 30 days, though. AccessToken expiry is set to
a fixed value of 1 hour which can not be changed.
Generate AccessToken for Client Credentials.
Client Credentials can be used by clients (client applications or microservices,
that are not human) for inter service communication.
Token Response.
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWFkI
"expires_in": 43199,
"jti": "da93ce67-a97a-4a66-8778-417518c4aa08",
"scope": "read write",
"token_type": "bearer"
}
Now this token can be used for making protected endpoint call on resource
server.
On the other hand, Client Credentials workflow is always meant for machine to
machine communication that is not on behalf of resource owner (human). Since
client will always have access to its own credentials, so it can always present the
credentials to oauth server and obtain a new access token after expiry.
How to implement the Logout functionality using
JWT?
In stateless security its impossible to invalidate the JWT accesstoken before they
expire. So its not easy to implement logout funcitonality without side effects.
There are some alternatives that we can implement logout in browser and
android/ios apps-
1. We can just throw away the access and refresh token from client’s local
storage upon logout.
3. We can implement stateful logout service that remembers either the JWT or
a part of claim added in JWT.
First two approached used togather are easier to implement and are generally
recommended.
Security in inter-service communication
There could be two usecases for inter service communication -
Token Relay
In first case, we shall propagate the security context of user from one service to
another.
This approach shall be used when a client (often a microservice) want to relay
token to downstream service in order to access a protected resource on behalf of
user.
@Autowired
private OAuth2RestTemplate oAuth2RestTemplate;
In second case, we shall use Client Credentials issued by OAuth2 workflow for
securing service to service communication. Here user’s security context is not
propogated to the downstream server, instead the client’s credentials are used to
secure the communication.
Client Crendtials Sequence Diagram
This approach shall mostly be used for scheduled jobs where we are not making
remote service calls on behalf of end user.
//Client Credentials based oAuth2 RestTemplate for Service Client Inter Mic
@Bean(name = "oauthRestTemplate")
public RestTemplate oAuthRestTemplate() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredential
resourceDetails.setId("<Id>");
resourceDetails.setClientId("<clientId>");
resourceDetails.setClientSecret("<clientsecret>");
// resourceDetails.setAccessTokenUri("<BaseUrl>/uaa/oauth/token");
resourceDetails.setScope(Collections.singletonList("openid"));
return new OAuth2RestTemplate(resourceDetails, new DefaultOAuth2ClientC
}
This restTemplate can now be used for internal service communication, for
example -
@Service
public class ProfileImageSyncService {
private static final Logger logger = LoggerFactory.getLogger(ProfileImageSy
@Autowired
@Qualifier("oauthRestTemplate")
private RestTemplate restTemplate;
Warning
Access token must be passed using Authorization Header in Http Request, like
shown in below example -
Http Header.
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authe
if (authentication.getPrincipal() instanceof CustomUserDetails) {
final CustomUserDetails user = (CustomUserDetails) authentication.
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("firstName", user.getFirstName());
additionalInfo.put("lastName", user.getLastName());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation
return accessToken;
} else {
return accessToken;
}
}
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new JwtTokenEnhancer();
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setAccessTokenValiditySeconds(ONE_DAY * 3);
tokenServices.setTokenStore(tokenStore());
tokenServices.setTokenEnhancer(tokenEnhancer());
return tokenServices;
}
...
}
3. Always use HTTPS. Basic auth over plain HTTP could prove to be
disasterous.
How to enable spring security at service layer?
First of all we need to define a configuration that allows Spring Security at
method level using annotations.
@Override
protected MethodSecurityExpressionHandler createExpressionHandler
return new OAuth2MethodSecurityExpressionHandler();
}
}
import com.websystique.springsecurity.model.User;
List<User> findAllUsers();
@PreAuthorize("hasRole('ADMIN')")
void updateUser(User user);
Only a user with ROLE_ADMIN or ROLE_DBA can access this service layer
method.
Testing Spring Boot based
Microservices
Testing is as important as the Production Code itself.
The Outline
JUnit
TestNG
Hemcrest
Rest-assured
Mockito
Wiremock
Hoverfly
JSONassert
An assertion library for JSON.
Pact
Selenium
Gradle
IntelliJ IDEA
Using spring-boot-starter-test
build.gradle.
testCompile('org.springframework.boot:spring-boot-starter-test')
This starter will import two spring boot test modules spring-boot-test &
spring-boot-test-autoconfigure as well as Junit, AssertJ, Hamcrest,
Mockito, JSONassert, Spring Test, Spring Boot Test and a number of other
useful libraries.
What is Mike Cohn’s Test Pyramid?
Mike Cohn has provided a model called Test Pyramid that describes the kind of
automated tests required for software development. There are four levels in Test
Pyramid.
Mike Cohn Test Pyramid
1. First layer of Pyramid is Unit Tests. These are the result of Test Driven
Development (TDD).
2. Second layer is Service tests. These tests are for testing the service directly
under different inputs. These tests also verify the interface provided by a
service against the client expectation.
3. Third layer is End-to-End tests that includes UI or API testing. These tests
run against the entire system including UI, Front-end or third party clients.
The purpose of these tests is to verify the actual production code.
1. We shall write tests with different granularity. Unit tests alone are not
sufficient for any healthy system.
2. The more high level you get, fewer the tests you should have. The number
of Unit tests should be highest in any application.
Testing Strategies
Testing Strategies in Microservices Architecture
References, https://martinfowler.com/articles/microservice-testing
Mock vs Stub?
By writing mock, you discover the objects collaboration relationship by
verifying that expectation are met, while stub only simulate the object’s
behavior. A Mock is just testing behaviour, making sure certain methods are
called.
Mock.
Mock is part of our unit tests and we explicitly setup the expectations on mocked
object. These expectations may vary from test to test. A mock is not setup in a
predetermined way so you have code that does it in your test. Mocks in a way
are determined at runtime since the code that sets the expectations has to run
before they do anything. Mock is also dummy implementation but its
implementation done dynamic way by using mocking frameworks like Mockito.
For mocking every language provides some kind of framework. In Java we have
Mockito framework available for creating mocks.
Stub.
The biggest distinction is that a stub you have already written with
predetermined behavior. So you would have a class that implements the
dependency (abstract class or interface most likely) you are faking for testing
purposes and the methods would just be stubbed out with set responses. They
would not do anything fancy and you would have already written the stubbed
code for it outside of your test. Stub is dummy implementation done by user in
static way mean i.e in Stub writing the implementation code, normally this is
done in Junit framework without mocking framework.
https://martinfowler.com/articles/mocksArentStubs.html
https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html
Unit Testing
Unit tests are foundation of your test suite. The responsibility of unit test is to
ensure that a certain unit (could be controller, service or utility class) works as
intended. All the external dependencies are replaced with mocks in class under
test. The following figure depcits the behavior of a unit test.
Unit Testing focus on component isolation
Few points that you should keep in mind while writing Unit Tests for Spring
Boot based application:
Service Layer and Utility classes are the ideal candidates for Unit Tests
Unit Tests should focus on a single component under test, they shall mock
every dependency that is injected into the component.
Controller layer, DAO layer that does not add business logic is bad
candidate for Unit Tests. They are more suited for Integration and End to
End tests.
Tip
@Mock
private PersonRepository personRepo;
@Before
public void setUp() throws Exception {
initMocks(this);
subject = new ExampleController(personRepo);
}
@Test
public void shouldReturnFullNameOfAPerson() throws Exception {
Person peter = new Person("Peter", "Pan");
given(personRepo.findByLastName("Pan"))
.willReturn(Optional.of(peter));
@Test
public void shouldTellIfPersonIsUnknown() throws Exception {
given(personRepo.findByLastName(anyString()))
.willReturn(Optional.empty());
Integration Test checks that your application works well with a real
database and other external dependencies
Integration Tests should be written for all places where our code interacts with
database, filesystem, or network etc. Based on this we can categorize integration
tests into different categories.
1. HTTP Integration Tests - tests that real REST call over HTTP hits your
application successfully.
2. Database Integration Test - tests that you applications integrates with a real
database successfully.
Consumer driven contract tests are similar to Integration tests, except that they
are shared among two teams. These tests ensure that both parties involved in an
interface between provider and consumer service adhere to the shared contract.
These tests will help both teams when they break the contract at very early stage.
Pact is an open source library to define producer and consumer side of CDC test
using a language of your choice. Contract tests always include both sides of an
interface — the consumer and the provider. Both parties need to write and run
automated tests to ensure that their changes don’t break the interface contract.
<<< .Consumer-Driven Contract Tests image::cdc-test.png[]
build.gradle.
testCompile('au.com.dius:pact-jvm-consumer-junit_2.12:3.5.12')
testCompile('au.com.dius:pact-jvm-provider-spring_2.12:3.5.12')
@Autowired
private GeoIpService subject;
@Rule
public PactProviderRuleMk2 weatherProvider = new PactProviderRuleMk2
("geoip_provider", "localhost", 9099, this);
@Pact(consumer="sample_microservice")
public RequestResponsePact createPact(PactDslWithProvider builder)
return builder
.given("geoip location data")
.uponReceiving("a request for a geo ip location data"
.path("/json/10.10.0.1")
.method("GET")
.willRespondWith()
.status(200)
.body(FileLoader.read("classpath:GeoIpApiResponse.json"
.toPact();
}
@Test
@PactVerification("geoip_provider")
public void shouldFetchGeoIpInformation() throws Exception {
Optional<GeoIPDto> geoIpResponse = subject.getGeoIp("10.10.0.1"
assertThat(geoIpResponse.isPresent(), is(true));
assertThat(geoIpResponse.get().getCity(), is("Mohali"));
}
}
We can take this Pact JSON file and hand it over to the Team providing this
interface.
End to End Tests
End to end testcase covers all the layers of system. These tests call our services
through user interface. End-to-end testing treats the system as black box and
testing is done on the public endpoints only (REST endpoints & GUI). System is
generally deployed in a production like environment (have a database,
communicates over REST/HTTP).
End to End Test covering entire system
We can use Selenium and Web Driver Protocol to run our end-to-end tests. First
of all, we need to add selenium dependency in our build.gradle file
build.gradle.
//testCompile('org.seleniumhq.selenium:selenium-firefox-
driver:3.9.3')
testCompile('org.seleniumhq.selenium:selenium-chrome-
driver:3.9.1')
testCompile('org.seleniumhq.selenium:selenium-remote-
driver:3.9.1')
testCompile('org.seleniumhq.selenium:selenium-api:3.9.1')
Now we can create a test that opens chrome browser and calls our endpoint.
Note that this test will only run on your system if you have Chrome/Firefox
installed on the system you run this test on (your local machine, your CI server).
import io.github.bonigarcia.wdm.ChromeDriverManager;
import org.hamcrest.CoreMatchers;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
public class HelloE2ESeleniumTest {
private WebDriver driver;
@LocalServerPort
private int port;
@BeforeClass
public static void setUpClass() throws Exception {
ChromeDriverManager.getInstance().setup();
}
@Before
public void setUp() throws Exception {
driver = new ChromeDriver();
}
@After
public void tearDown(){
driver.close();
}
@Test
public void helloPageHasTextHelloWorld() {
driver.navigate().to(String.format("http://localhost:%s/", port));
This test is quite straight forward, it spins the entire spring application on a
random port using @SpringBootTest and navigate to the / root of our website.
Then we check if application prints hello world or not.
Best Practices in Testing
Automated Tests are as important as the production code. Do not write tests
just for the sake of formality, create a proper design for testcases and do it
properly with proper attention.
Have a minimum set of End to End Tests, do not completely eradicate end
to end test altogether just to meet tight project timelines.
Create different TestSuites for different needs - one for regression, another
for smoke testing, may be another for Build verification and testing.
Non-determinism in tests make them useless, teams will soon start ignoring the
entire testsuite when few tests start fail randomly. It most often occurs in
Integration Tests due to various reasons, including -
3. Lack of Isolation e.g. a test can create some data in database table which
causes test pollution with subsequent tests. Properly isolated tests can run in
any sequence. Some ideas are:
Run tests in database transaction that is rolled back after the test
execution.
Start full running server for tests on random ports to avoid any port
conflict.
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-
features-testing.html#boot-features-testing-spring-boot-applications-
working-with-random-ports
More on this.
https://martinfowler.com/articles/nonDeterminism.html
Both MockMvc and TestRestTenplate helps in writing Interation tests for Rest
Layer in Spring based microservices. MockMvc does not start the container to
check the cotnroller behvior, while TestRestTemplate starts an embedded
container on a random or predefined port. Both kind of tests load the spring
context, so you are free to choose anyone of these mechanisms.
@RunWith(SpringRunner.class)
@DataJpaTest(showSql = true)
@ActiveProfiles("test")
public class ProductRepositoryTests {
@Autowired
private TestEntityManager entityManager;
@Autowired
private ProductRepository productRepository;
@After
public void tearDown() throws Exception {
productRepository.deleteAll();
}
@Test
public void testFindByLastName() {
Product product = new Product("first");
entityManager.persist(product);
assertThat(findBName).extracting(Product::getName).containsOnly
}
}
Cleaning up resources after the test execution bring isolation to tests, thus
avoids the non-determinism in Integration tests.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
public void greetingShouldReturnDefaultMessage() throws Exception
assertThat(this.restTemplate.getForObject("http://localhost:"
String.class)).contains("Hello World");
}
}
This test will start the entire Spring Application Context using embedded servlet
container at a random port. TestRestTemplate will try to access rest endpoint,
thereby verifying the code end-to-end.
Integration and end-to-end tests can cover the security aspects of restful
microservices. For example, in below integration test we can check that only
authorized user can access the protected resource.
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class ApplicationTests {
@LocalServerPort
private int port;
@Test
public void homePageProtected() {
ResponseEntity<String> response = template.getForEntity("http://localho
+ port + "/user", String.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode
String auth = response.getHeaders().getFirst("WWW-Authenticate"
assertTrue("Wrong header: " + auth, auth.startsWith("Bearer realm=
}
}
Asychnrous Communication
Synchronous Communication
No, only the controller under test and its dependencies should be loaded in
integration test of that controller. Dependencies should either be mocked or
stubbed unless you are planning for database integration testing.
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = { MyController.class }, secure = false)
@ActiveProfiles({ "test" })
public class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
MyService myService;
@Test
public void testBaseReq() throws Exception {
Testing dummyData = new Testing();
dummyData.setData("testing");
when(myService.get(anyInt())).thenReturn(dummyData);
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status
}
}
No, unit tests should be very light weight. They should only focus on the single
component and mock everything that this component is dependent on. Ideally
services with mocked dependencies are best candidate for Unit tests on server
side. Controller layer that is most dependent on Spring Context, should be tested
using Integration tests.
https://martinfowler.com/bliki/TestDouble.html