# Microservice Architecture
Is a way of designing an application such that the application's different functional areas are separated into multiple smaller applications called microservices. This basically corresponds to [[Scale Cube#Y-Axis Scaling]]. It's the opposite of a monolithic architecture.
It is often compared to the Service Oriented Architecture (SOA), and it is indeed similar to some SOA definitions. However, SOA means different things to different people and some definitions describe something significantly different, as they focus on ways to integrate monolithic applications using a service bus.
Each microservice:
- Is loosely coupled from other microservices and cohesive
- Is independently deployed
- Is independently developed (with its own stack including independent databases and APIs)
- Has an explicit [[Published Interface]] that rarely changes
## Benefits
- The stack of each microservice can be tailored to that individual service. What's good for one microservice might not be good for all of them. Think [[Polyglot Programming]].
- At the same time this doesn't necessarily have to be done but it is an option
- It allows for each service's data to more closely resemble the real world. See [[Bounded Context]].
- Easier onboarding if teams are split correctly (see section on [[Microservice Architecture#Team Management|Team Management]] below). As each microservice is independent, new team members can become productive more quickly due to the reduced [[Cognitive Load]].
- Maximizes productivity of each individual team by decreasing its size which improves communication (see section on [[Microservice Architecture#Team Management|Team Management]] below).
- Ability to take advantage of future frameworks, methodologies and libraries that are not yet developed/known to you due to the lower cost of rewriting smaller independent components. Put another way, stack changes are inexpensive/possible.
- Allows for independent [[Application Scaling|scaling]] of individual microservices (using different [[Scale Cube]] methods). Again, what's good for one might not be good for all of them. This makes it less expensive to deal with bottlenecks as you don't have to scale the whole application.
- A change in one service does not require deploying the whole application
- Increased resiliency. If a single service goes down it won't bring down the whole application.
- Easier to keep good module structure i.e. easier to constrain changes that affect a single module within that module
- Improves likelihood of component reuse as complete microservices could be reused for a new customer (customer in this case is the customer of the software development company).
## Drawbacks
- Increased complexity
- Due to service/process cross-communication which is more expensive than in-process calls. This greater cost leads to coarser-grained APIs which are often more awkward to use.
- Allocation of responsibilities between modules is harder to change
- Due to the need to deal with partial failure (cannot assume the whole system is running)
- Integration testing is more difficult
- Implementing higher-level services requires careful coordination with multiple teams. However, it could be argued that this is not really a drawback as in a Monolithic Architecture you have to deal with one big team instead. Businesses tend to keep that one team in one place tho, microservice teams could be split geographically, making communication difficult (Is this argument still relevant in a post-corona world? One team in one place no longer applies when people work remotely from home.).
- Increased deployment complexity as more components must be deployed and introduced to their peers.
- Increased hardware cost. Each service runs in its own process which consumes more memory.
- Increased initial costs due to a more complex architecture.
- Decreased initial evolution speed as codebase is split across different stacks/deployments. In a monolithic architecture founding team members are usually familiar with the whole codebase at the beginning, making it easy for them to make quick changes. This goes away tho with time.
- Can be tricky to properly decompose the application into components such that the benefits are maximized.
- Poorly delineated microservices might require a lot of requests that span multiple microservices, which might make it difficult to satisfy data [[Transaction Atomicity|atomicity]], as we're dealing with multiple databases.
- Natural progression leads to [[Command Query Responsibility Segregation|CQRS]], which has its own drawbacks.
## Example
Say you have a typical shopping cart application. You would split it into microservices as such:
- Account Service — Accounts Database | Accounts API
- Inventory Service — Inventory Database | Inventory API
- Shipping Service — Shipping Database | Shipping API
- Mobile API facade Service, which combines the three APIs above
- Mobile App (which might be considered as a microservice as it has its own stack)
- Web site back end (potentially with its own API facade if necessary)
## Guidance for Microservice Design
- Keep microservices loosely coupled from other microservices and cohesive. You should keep things that change at the same time in the same microservice. Contrary to common belief, microservices do not necessarily need to be small.
- Minimize changes to service interfaces which require changing multiple services at the same time, as that requires coordination between multiple teams.
- Achieve this through high service cohesion and making service interfaces future-proof by having them follow the [[Open-closed Principle]] and [[Postel's Law]].
- Try not to rely on API versioning as it can quickly add loads of complexity. See [[Guidance for Testing Dependent Modules]].
- Each microservice should follow the [[Single Responsibility Principle]] and the Unix philosophy of "do one thing and do it well", and be delineated from other microservices based on business capability i.e. shipping a product is a separate service from inventory management
- Communication should be done using a lightweight messaging system or lightweight RPC. Logic should live in the endpoints and not in communication methods.
- Although [[REST]] is a very popular choice for microservices, I'm questioning this fashion as [[REST]] and Hypermedia as the Engine of Application State are incompatible with the idea of a [[Published Interface]].
- A Common Object Request Broker Architecture (CORBA) such as RPCs and specifically [[gRPC]] seems like a much better fit.
- Prefer coarser interfaces to chatty ones as cross-service API calls are significantly more expensive than in-process ones
- As distributed transactions (i.e. sync method for multiple [[Transactional Database]]-s) are notoriously difficult to implement, prefer designs with [[Eventual Consistency]]. See [[Saga Pattern]].
- As clients cannot rely on peers' availability, design with failure in mind and handle it gracefully
- If you find yourself always changing two services at the same time it's a good sign that the two should be merged
- Think about implementing quick failure detection as well as automatic service restoration. Achieve this through real-time monitoring.
- Think about starting out with a Monolithic Architecture and transitioning to microservices once the monolith becomes a problem
- This adheres to [[YAGNI]]
- Allows you to go to market quickly as the Monolithic Architecture is easier to develop at first
- Gives you time to properly recognize service boundaries
- See [[Advice for Breaking a Monolith into Microservices]]
- Think about disregarding the above advice if you think your application is big enough to require multiple services from the beginning
- As an in-process interface is not a good service interface (coarse vs chatty), so if you start from a coarse interface from the get-go you'll have less issues when separating modules from a monolith
- Because in a monolith it's extremely hard to keep individual parts separated, developers will tend to share objects, reuse code where reuse is not actually a good idea, and make other architectural decisions that will tend to increase coupling and make it harder to separate services later (applies to the above point as well).
- Think about implementing a hybrid of the two approaches listed above with fewer bigger services that you can separate later
## Team Management
Microservice architecture allows splitting the workforce into multiple teams which increases productivity by decreasing communication costs (see [[Two Pizza Rule]]). When designating teams you should [[Prefer Vertical to Horizontal Teams]].
## See Also
- [[Guidance for Testing Dependent Modules]]
- [[Circuit Breaker]]
- [[Advice for Breaking a Monolith into Microservices]]
## Sources
- Chris Richardson. [Microservice Architecture](https://microservices.io/patterns/microservices.html).
- James Lewis and Martin Fowler. [Microservices. A definition of this new architectural term](https://www.martinfowler.com/articles/microservices.html).
- Martin Fowler. [MonolithFirst](https://www.martinfowler.com/bliki/MonolithFirst.html).
- Stefan Tilkov. [Don’t start with a monolith](https://www.martinfowler.com/articles/dont-start-monolith.html).