This document discusses best practices for building microservices architectures. It begins by noting that there is no single right way to implement microservices and that each domain needs to be considered individually. It then provides guidelines for microservice design, such as having each service perform a single well-defined function. The document also distinguishes between building a platform of collaborating services versus a distributed services layer with more independent services. It offers recommendations for infrastructure, configuration management, documentation, and other considerations for successful microservices implementations.
2. A Simple Truth
There is no right way to “do” microservices.
• Microservices represent a problem domain
that scales development, architecture,
operations, and infrastructure
• Thinking that one-size-fits-all is naïve
• Need to consider your domain and how it should
all fit together
3. Guidelines for Microservices
• Should perform a single function in the scope
of the domain
– Business/Platform/Operation function
• Should not emphasize LoC in the “micro”
mindset
– “Micro” should speak to a service’s function, not
its size
• Should encapsulate its domain
– Domain modeling, business logic, API
4. What Are You Building?
• Understand how your distributed
infrastructure fits together
• Are you building a set of services that
collaborate toward a common goal?
– Then you’re building a “platform”
• Are you building services that act in a one-off
or composable manner?
– Then you’re building a “distributed service layer”
5. Building A Platform
• A Platform is composed of many different
microservices that collaborate in terms of a
broader “system”
• Microservices perform a very specific function
in the overall processing
• A Gateway Layer should be employed to
service consumers
7. Platform Architecture
• Consumers never speak directly to the
underlying microservices
• Consumers talk to the Gateway layer as
though they are talking to a monolith
• Microservices never talk amongst themselves
– They are clean vertical slices in the architecture
• Gateway layer is responsible for consuming
and aggregating data from microservices
8. Platform Architecture
• In a platform, the data model is coupled because of
the overall collaborative nature of the system
• It is OK to share a data model between microservice and
Gateway layers through a client library
• Since the Gateway will be the only consumer of a backend
microservice, coupling via a client library is OK
• Gateway can gracefully degrade service or data
offerings when a backend service is not available
9. Distributed Service Layer
Microservices
• Distributed Service Layer microservices are
those services that do not cooperate within a
strict “system”
• They are able to be composed by many
consumers for their purposes
• Consumers are responsible for understanding
the service’s data structures and domains
11. Distributed Service Layer
Microservices
• Interconnectivity means the dependency
graph can be difficult to figure out
• Failure in the overall infrastructure can be
difficult to realize and trace
• May be difficult to push data structure and
model changes without breaking many
consumers
• Can quickly turn in “spaghetti infrastructure”
14. Distributed Service Layer
Microservices Architecture
• Define two tiers of distributed service layer
types: data consumers/aggregators & data
producers
• Having a logical boundary between services
that talk to other services and those that do
not makes the infrastructure manageable
• Data producers should not inter-communicate
at their level
16. Distributed Service Layer
Microservices Architecture
• Microservices should not share a domain
model
– The complexity of a distributed service layer can
make dependency management very difficult
• Microservices should focus on documentation
as the contract
• Consumer service tier should be resilient to
failure
17. Distributed Service Layer
Microservices Architecture
• Considerations:
– What about when one or more “consuming”
service tier microservices want to collaborate?
– Dependency graph can still be messy; versioning
of APIs and data models should be strictly
adhered to
– Documentation is hard to get right. How can we
make service structures and APIs more
discoverable?
18. Documentation
• It’s important to document a distributed
service layer microservice in a “human
consumable” way
– Explaining the domain and what a service is doing
is more important than showing the endpoints
– Showing the endpoints is still important
• Platform architectures should expose
documentation through the Gateway layer
19. Documentation
• Discoverability:
– Documentation can be difficult to maintain and
manage, especially with an evolving API
– Statically documenting the API is OK, making it
programmatically discoverable is better
– Documentation + making the API discoverable
gives great coverage over what a service is doing
and how to interface with it
20. Discoverability
• HATEOAS
– Informs of what relationships exist within the data
object that is returned
– Informs of how to access the data of some
relationship
– Can be a great strategy for gracefully degrading
behavior in a platform
25. Discoverability
• HATEOAS
– When the review service goes offline, we can
inform consumers by removing it from the
_links block in the data object
– This means that we only need to document the
model and intention of the service
– Consumers will only need to know the
relationship within the system to know
26. Discoverability
• JSON-LD
– Provides a “linked data” structure for consumers
– Allows you to provide a type for your API
endpoints, and can be programmatically
interpreted in a number of ways
– Worth investigating: http://www.hydra-cg.com/
27. Project Structure
• Many questions in a microservice
architecture:
– Repo per microservice?
– Module per microservice?
– Every microservice in its own process space?
28. Project Structure
• In services that do not collaborate, they should
not share a project structure
• It may make sense for platform microservices to
exist as a module in a singular project
• Following a repo-per- approach for distributed
service layer microservices keeps the concerns
nicely isolated
– “What about when we need to share common things
(constants, configs, etc)?” Publish a library; it’s easier
than ever with things like jfrog* and jcenter*
* http://bintray.com
31. Project Structure
• Principles:
– Generate a self-contained, lightweight artifact
– Should be runnable and environment agnostic
(within the build)
– Runnable JARs or a standalone distribution is the
best way to go
32. Project Structure
• Principles:
– Generate a self-contained, lightweight artifact
– Should be runnable and environment agnostic
(within the build)
– Runnable JARs or a standalone distribution is the
best way to go
33. Project Structure
apply plugin: 'groovy’
apply plugin: 'application'
mainClassName = "com.tld.microservice.Main"
repositories {
jcenter()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.3’
...
}
How do we run this thing
34. Project Structure
apply plugin: 'groovy’
apply plugin: 'application'
mainClassName = "com.tld.microservice.Main"
repositories {
jcenter()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.3’
...
}
How do we build this thing
35. Project Structure
apply plugin: 'groovy’
apply plugin: 'application’
...
• Gradle Task
./gradlew installDist
Creates a distribution of the application, which pulls down all the
dependencies, structures them in a directory, and produces shell scripts that
can start the app in a standalone way.
Can be found in {projectDir}/build/install/{projectName}
37. Project Structure
./gradlew shadowJar
• Produces a “fat jar” with all the dependencies contained
• Integrates with the application plugin to make the fat jar
runnable
• Excellent solution for building lightweight deployables
• Can be run anywhere Java is available
38. Infrastructure
• Managing a microservice infrastructure is
difficult
• Big benefit to microservices/distributed
systems is the ability to scale a service
according to its specific requirements
• Means that all microservices will run on their
own instances/containers
39. Infrastructure
• Infrastruct principles for microservices:
– Follow immutable infrastructure paradigms
– Make the unit of deployment for a microservice
portable
– Structure microservices such that configuration is
derived from the runtime environment
40. Infrastructure
• Immutable infrastructure:
– Once a server is provisioned, it cannot be changed
– Any detail about provisioning a server for a
microservice should come from the microservice’s
build
– Initialization, service starting, base configuration
should be performed only once during provisioning,
never adjusted after that
– Any modifications to the server’s runtime should be in
change control under the microservice project’s
purview
41. Infrastructure
• Portable deployables:
– Building just a JAR is fine, but it doesn’t inform the
server as to how to run that JAR
– Unit of deployment should specify all of the
dependencies required to run your application
• “My microservice depends on Java”, for example
– Unit of deployment should self-contain any
configuration steps, including things like
ansible/chef/puppet runs (beyond any base-image
configuration)
42. Infrastructure
• Portable deployables:
– Package your project as an os-package
– Gradle plugin available from Netflix (Nebula) to
pull your project into a .deb or .rpm file
• Provides a DSL for specifying pre-install/post-install
steps
• Uses Gradle CopySpec to install files into the resulting
os package
45. buildscript {
repositories { jcenter() }
dependencies {
classpath 'com.netflix.nebula:gradle-ospackage-plugin:2.0.2’
}
}
... [snip] ...
mainClassName = "com.tld.products.Main"
ospackage {
packageName = "products"
release '3'
into "/opt/products"
from "${project.buildDir}/install/${project.applicationName}"
from("osfiles") {
into "/"
}
}
buildDeb {
dependsOn installDist
requires(“openjdk-7-jre”)
preInstall file('scripts/preInstall.sh')
}
Puts everything in the project’s
“osfiles” directory into the root of the
server’s filesystem (config, start scripts, other?)
46. buildscript {
repositories { jcenter() }
dependencies {
classpath 'com.netflix.nebula:gradle-ospackage-plugin:2.0.2’
}
}
... [snip] ...
mainClassName = "com.tld.products.Main"
ospackage {
packageName = "products"
release '3'
into "/opt/products"
from "${project.buildDir}/install/${project.applicationName}"
from("osfiles") {
into "/"
}
}
buildDeb {
dependsOn installDist
requires(“openjdk-7-jre”)
preInstall file('scripts/preInstall.sh')
}
Informs the server as to any dependencies
that your project might have
47. buildscript {
repositories { jcenter() }
dependencies {
classpath 'com.netflix.nebula:gradle-ospackage-plugin:2.0.2’
}
}
... [snip] ...
mainClassName = "com.tld.products.Main"
ospackage {
packageName = "products"
release '3'
into "/opt/products"
from "${project.buildDir}/install/${project.applicationName}"
from("osfiles") {
into "/"
}
}
buildDeb {
dependsOn installDist
requires(“openjdk-7-jre”)
preInstall file('scripts/preInstall.sh')
}
Will execute the script before your project
is installed on the server. Allows you to provide
Any additional tuning.
48. Infrastructure
./gradlew buildDeb
• Produces the artifact into the
{project}/build/distributions/{projectName}_{vers
ion}-3.deb file
• Can be installed on any server/container and all dependencies are
resolved at provisioning
• “osfiles” can include anything, but common use-cases are startup
scripts, any system-level configuration needed by the microservice
• Pre/Post install can include things like Ansible/Chef/Puppet runs
50. Infrastructure
• Docker:
– An “OK” option for microservices
– Can be difficult to manage
– Cloud providers out there taking this on
• CloudFoundry (cloudfoundry.com)
• Morpheus (gomorpheus.com)
• Project Atomic (projectatomic.io)
– Gradle Plugin available for packaging your
microservice as a Docker container
• https://github.com/bmuschko/gradle-docker-plugin
51. Microservice Configuration
Management
• When you get into a distributed architecture,
you need faculties to help manage
configuration
• Builds/projects should be agnostic to the
environment they are deployed in
• Environment configuration should be derived
from the environment
54. Microservice Configuration
Management
• Rules of thumb for configuration
management:
– Place config server in an internal DNS zone
– For robust architectures, have a config server per
environment
– Have microservices re-poll the configuration
server for any real-time changes
– During server deployment, export the
environment key (as part of User Data, for
example)
55. Microservice Configuration
Management
• Spring Cloud OSS helps provide integration
with configuration management servers for
pulling properties dynamically
– Gives you the ability to read from Cloud CM
– Provides management endpoints for updating
microservice configurations dynamically
– https://github.com/spring-cloud/spring-cloud-config#spring-cloud-config-client
– http://cloud.spring.io/spring-cloud-consul/spring-cloud-consul.html
56. Additional Considerations
• Metrics Reporting
– Use statsd and things will be very portable
• Service Discovery
– Eureka, Consul, Load Balancing
• Log publishing
– Should write out application logs to a common
location (S3, for example) for inspection