Microservices divide a large program into a number of smaller, independent services. Unlike a monolithic application which implements all features in a single code base with a database for all data. Today we will insight into a base principals of building microservice applications called ‘Twelve-factor design’.
What is microservice application?
Microservices are the current industry trends. However, it’s important to ensure that there is a good reason to select this architecture.
The primary reason is to enable teams to work independently and delivered through to production at their own cadence, this supports scaling the organization, adding more teams increases speed. There’s also the additional benefit of being able to scale the microservices independently based on their requirements.
The key to architecting microservice application is recognizing service boundaries:
Decompose applications by feature to minimize dependencies | Organize services by architectural later | Isolate services that provide shared functionality |
Store service | Web | Authentication service |
Booking card service | Mobile | Reporting service |
Products service | Data access | Etc. |
Etc. | Etc. |
Architecturally, an application designed as a monolith or around microservices should be composed of modular components with clearly defined boundaries. With a monolith, all the components are packaged at deployment time and deployed together. With microservices, the individual components are deployable.
To achieve independence on services, each service should have its own data store. This lets the best data store solution for that service to be selected and also keeps the services independent. We do not want to introduce coupling between services through a data store. A properly designed microservice architecture can help achieve the following goals:
- define strong contracts between the various microservices,
- allow for independent deployment cycles, including rollback,
- facilitate concurrent AB release testing on subsystems,
- minimize test automation and quality assurance overhead,
- improve clarity of logging and monitoring,
- provide fine-grained cost accounting,
- increase overall application scalability and reliability through scaling smaller units.
However, the advantages must be balanced with the challenges this architectural style introduces. Some of these challenges include:
- it can be difficult to define clear boundaries between services to support independent development and deployment,
- increased complexity of infrastructure with distributed services having more points of failure,
- the increase latency introduced by network services and the need to build a resilience to handle possible failures and delays.
- Due to the networking involved, there is a need to provide security for service-to-service communication, which increases complexity of infrastructure.
- Strong requirement to manage and version service interfaces with independent, deployable services that need to maintain backward compatibility increases.
- Decomposing applications into microservices is one of the biggest technical challenges of application design. Here, techniques like domain driven design are extremely useful in identifying logical functional groupings.
Pros of microservice architectures | Cons of microservice architectures |
Easier to develop and maintain | Increased complexity when communicating between services |
Reduced risk when deploying new versions | Increased latency across service boundaries |
Services scale independently to optimize use of infrastructure | Concerns about securing inter-service traffic |
Faster to innovate and add new features | Multiple deployments |
Can use different languages and frameworks for different services | Need to ensure that you don’t break clients as versions change |
Choose the runtime appropriate to each service | Must maintain backward compatibility with clients as the microservice evolves |
Where to start?
The first step is to decompose the application by feature or functional groupings to minimize dependencies. Consider, for example, an online retail application, logical functional groupings could be product management, reviews, accounts, and orders. These groupings then for many applications which expose an API.
Each of these mini-applications will be implemented by potentially multiple microservices internally. Internally, these microservices are then organized by architectural layer and each should be independently deployable and scalable.
Any analysis will also identify shared services such as authentication, which are then isolated and deployed separately from the mini-applications. When you are designing microservices, services that do not maintain state but obtain their state from the environment or stateless services are easier to manage. That is, they are easy to scale, to administer, and to migrate to new versions because of their lack of state.
However, it is generally not possible to avoid using stateful services at some point in a microservice-based application. It is therefore important to understand the implications of having stateful services on the architecture of the system. These include introducing significant challenges in the ability to scale and upgrade the services. Being aware of how state will be managed is important in the very early stages of microservice application design.
Let me introduce some suggestions and best practices on how this can be achieved.
The twelve-factor app
The twelve-factor app is a set of best practices for building web or software-as-a-service applications. 12-factor design helps you decouple components of the application, so that each component can be deployed to the cloud, using continuous deployment and scale up or down seamlessly. The design principles also help maximize portability to different environments. Because the factors are independent of any programming language or software stack, 12-factor design can be applied to a wide variety of applications. Let’s take a look at these best practices.
The first factor is codebase.
The codebase should be tracked in a version control, such as Git. Cloud Source Repositories provide fully featured, private repositories.
The second factor is dependencies.
There are two main considerations when it comes to dependencies for twelve-factor apps: dependency declaration and dependency isolation. Dependencies should be declared explicitly and stored in version control. Dependency tracking is performed by language-specific tools such as Maven for Java and Pip for Python. An app and it’s dependencies can be isolated by packing them into a container. Container Registry can be used to store the images and provide fine-grained access control.
The third factor is configuration.
Every application has a configuration for different environments, like test, production, and development. This configuration should be kept external to the code and is usually kept in environment variables for deployment flexibility.
The fourth factor is backing services.
Every backing service, such as database, cache, or message service, should be accessed via URLs and set by configuration. The backing services act as abstractions for the underlying resource. The aim is to be able to swap one backing service for a different implementation, easily.
The fifth factor is build, release, run.
The software deployment process should be broken into three distinct stages: build, release, and run. Each stage should result in an artifact that is uniquely identifiable. Build will create a deployment package from the source code. Every deployment package should be linked to a specific release that’s the result of combining a runtime environment configuration with a build. This allows for easy rollbacks and a visible audit trail of the history of every production deployment. The run stage then simply executes the application.
The sixth factor is processes.
Applications run as one or more stateless processes. If state is required, the technique discussed earlier in this module for state management should be used. For instance, each service should have its own datastore and caches using, for example, Redis, to cache and share common data between services used.
The seventh factor is port binding.
Services should be exposed using a port number. The applications bundle the web server as part of the application and do not require a separate server like Apache. In cloud, such apps can be deployed on platform services such as VM, Kubernetes, Azure Web App or Google App Engine, or serverless technologies like AWS Fargate or Cloud Run.
The eighth factor is concurrency.
The application should be able to scale out by starting new processes and scale back in as needed to meet demand and load.
The ninth factor is disposability.
Applications should be written to be more reliable than the underlying infrastructure they run on. This means they should be able to handle temporary failures in the underlying infrastructure and gracefully shut down and restart quickly. Applications should also be able to scale up and down quickly, acquiring and releasing resources as needed.
The tenth factor is dev/production parity.
The aim should be to have the same environments used in development and test/staging as are used in production. Infrastructure as code and Docker containers make this easier. Environments can be rapidly and consistently provisioned and configured via environment variables. Any cloud provides several tools that can be used to build workflows and keep the environments consistent. These tools include Cloud Source Repositories, Cloud Storage, Container Registry, and Terraform. Terraform uses the underlying APIs of each cloud service to deploy your resources.
The eleventh factor is logs.
Logs provide an awareness of the health of your apps. It’s important to decouple the collection, processing, and analysis of logs from the core logic of your apps. Logging should be to the standard output and aggregating into a single source. This is particularly useful when your apps require dynamic scaling and are running on public clouds, because it eliminates the overhead of managing the storage location of the logs and the aggregation from distributed (and often ephemeral) VMs or containers.
The twelfth factor is admin processes.
These are usually one-off processes that should be decoupled from the application. This should be automated and repeatable, not manual, processes. Depending on your deployment, there are many options for this, including Linux cron jobs, Kubernetes cluster jobs, Windows background tasks or any scheduler.
Hope you like the post. For more SRE content please subscribe to our newsletter, follow us on Twitter and LinkedIn.
Save your privacy, bean ethical!