Microservices#
What are Microservices?#
factors such as easily accessible cloud infrastructure, tools and automation drive toward
microservices * microservices enables to manage complexity at granular level * a single microservice should be manageable by a single developer or small team * each service should run on its own deployment schedule and be able to update independently * each developer/team is responsible for development, testing, deployment and operations of the service(s) they own * individual microservice can be external for customers or only internal, with access to a database, file storage or method of state persistence * in a well-designed system, services must collaborate with each other to provide features and functions of greater application
Microservices Application#
a microservices application, distributed application, is a complex adaptive system
because of its constituent parts * each component should be small, manageable and easy to understand * components are separate processes, with each on physically or virtually separate computer, and communicate via network * application can be in different forms, flexible and suitable for any situations * the services do different jobs, depending on business domain
orchestration: automated management of services
Monolith vs Microservices#
- Monolith
a great option for creating a throw-away prototype, to validate business idea first
all code runs in same process with no barriers
can become a mess as size increases and can be difficult for new developers to
understand - a code change can break the entire application and gives arise to deployment fear - testing can be difficult to scale as it consumes resources as entire application and becomes more expensive to run
- Microservices
with the right tools, microservices prototyping can be as easy as with monolith
before, microservices were only used where value of solution is more than cost of
implementation - as it became cheaper to replace monolithic apps, components of distributed systems reduce to microservices - each service can has dedicated resources - physical infrastructure can be shared between many services to be cost effective - can have fine-grained control, minimize deployment risk and choose own tech stack - error when updating a service cannot break the entire application and easier rollback as small part of the system rather than the whole - faster boot that makes system easier to scale as it can be replicated quickly - easier to test and troubleshoot - each component should be able to thrown away and be rewritten with different tech within weeks - multiple tech stacks can work together in the cluster using shared protocols and communication mechanism - microservices create structure that can be converted to newer tech stacks - more difficult and complex to develop, and has steep learning curve
microservices as an architectural pattern is a tool that gives many ways to manage complexity
- and force for better design
do not over design or try to future proof the architecture
always start with simple design
apply continuous refactoring during development, then a good design will emerge naturally
duplicated code is accepted in microservices, as a trade for higher tolerance
process boundaries make more difficult to share code
# Principles of Microservices
single responsibility principle#
each microservice should be as small and simple as possible
each service should cover only single conceptual area of business
updating or adding a service should be easy
loose coupling#
connections between services should be minimal
do not share information unless necessary
can help when changing the application into new configurations
high cohesion#
all the code in a microservice belong together
all the code solves problem in the service’s responsible area
a service should not solve more than one problem or has larger responsibility area
domain driven design works well for microservices
Development#
start with simplest code and keep it simple as possible
- simple changes are easy to understand, test and integrate
test each service independently as restarting a single service is faster
integration testing can be difficult when application grows larger
mocking: testing technique where dependencies are replaced with simulated alternative
- use tools, techniques, processes and patterns as the application becomes complex
use same versions of systems and tools in development as in production
install less in production environment to reduce security issues
deploy tools in-cluster or use third-party ones based on business requirements
take out as much code as possible to isolate the problem when troubleshooting
it’s better for a microservice not to start than operate on wrong configuration
- use containers to abstract resources required by a microservice
as containers can virtualize OS and hardware, resources can be divided on one computer
among many services * build container images, bootable and immutable snapshots of servers including necessary assets, for easier and faster deployment * containers names should be independent of the underlying technique * if possible, run commands at container startup to cache on host OS
freedom to effect change in the future without knock-on effects is important
control which services should be exposed and which to restrict access
stateless cluster#
cluster can be destroyed and rebuilt without loss of data
can use blue-green deployment for production rollouts
only need to switch DNS record to point to the new version and can be switched back to
the old one if problems occur
- use one database per microservice if possible
sharing databases between services can have architectural and scalability problems
restrict data access only to the underlying code so that changes in structure of data
over time can be hidden * avoid using a database as integration point or interface between microservices
- it’s beneficial to go production while the application is still small
to get user feedback and adapt and build features
building a CD pipeline and deploying is easier
Communication#
communication is the key between microservices
direct messaging (synchronous communication)#
messages are sent and responded directly and immediately between services
used when a service should be invoked immediately to perform a task
also used for triggering a direct action on another microservice
one controller microservice can cause to perform a strict series of behaviors across
multiple microservices, but the controller becomes single point of failure * recipient service can’t ignore or avoid the incoming message * requires tight coupling between microservices * useful to coordinate behaviors in explicit way or well-defined order * can only target single service at a time * not easy to use when a single message should be received by multiple recipients * e.g messages sent with HTTP requests
indirect messaging (asynchronous communication)#
has intermediary between services
both sender and receiver don’t need to know about each other
looser coupling between microservices
the sender doesn’t even know if the other service will receive the message
the receiver cannot send a direct reply
can’t be used when direct response is required
used for important events that don’t need a direct response
can make architecture more difficult
increases security, scalability, extensibility, reliability and performance
no single service that orchestrate others
allows more complex and resilient networks of behaviors
messages won’t be lost even if the microservices fail as the messages aren’t acknowledged
when a service crashes and they will be delivered to another service to be handled * e.g RabbitMQ, which uses Advanced Message Queuing Protocol (AMQP) * single-recipient messaging
one-to-one indirect messaging between services
can have multiple senders and receivers
only single service will receive each individual message
useful when distributing a job that should be handled only by the first one that is
capable of dealing with - will make sure that a job is done only once within application
- multiple-recipient messaging
one-to-many broadcast style, one service sends that message but many others can
receive - mostly used for notifications
can use direct or indirect messaging to load balance handling of a message by one of a
collection of services
RabbitMQ#
- assert queue
multiple microservices can assert a queue
checking and creating the queue if not exist
the queue is created once and shared between all participating services
incoming message must be parsed manually as RabbitMQ doesn’t natively support JSON
it only sees the message as a blob of binary data
can use any efficient binary format for message payload
queue should auto deallocate when microservices disconnect to prevent memory leak
Code Base#
Monorepos#
related changes in different projects can be in the same pull-request
- Nx
npx create-nx-workspace
,yarn create nx-workspace
nx [target] [project]
, targets (serve, build, lint, test, e2e) fromproject.json
types of libraries: Feature, UI, Data-access, Utility
nx g lib feature --directory feature1 --appProject project1 --tags type:feature
,create library in
libs/feature1/feature
,--appProject
make the library routable inside specified application,--tags
are add toproject.json
-nx g component GlobalStyles --project project1-folder1 --export
, create component inlibs/project1/folder1/src/lib/global-styles
,--project
specifies as found inprojects
section ofnx.json
,--export
exports the component inindex.ts
file -nx g app --help
,nx g lib --help
,nx g component --help
-@nrwl/nx/enforce-module-boundaries
define libraries dependencies based on tags, even circular dependency is not allowed, need to setallowCircularSelfDependency: true
-nx graph
, show dependency graph -nx affected:graph
, show affected graph (uses Git history and compare with main branch to determine which projects are affected, specify base with--base
ordeafultBase
innx.json
) -nx affected:build
, build affected apps -nx affected:test
, test affected projects -nx affected:lint
, lint affected projects -nx affected:e2e
, run e2e tests on affected projects - projects at the bottom of the dependency run first ---maxParallel
, specify parallel tasks (3 by default) -nx print-affected --type=app --select=projects
, list affected apps -nx print-affected --type=lib --select=projects
, list affected libraries - listing affected projects can be useful in CI - Nx uses computation caching to run tasks faster, will replay the same output if computation hash is the same (stored innode_modules/.cache/nx
and can specify undertasksRunnerOptions
innx.json
) -nx g @nrwl/express:app api --no-interactive --frontend-project=frontend
, generate express app boiler plate (--frontend-project
add proxy configuration) -nx run-many --target=serve --projects=api,frontend
, start multiple applications -nx g @nrwl/node:lib shared-models --no-interactive
, generate utility library -nx format:check
,nx format:write
, checks and formats files with Prettier