Getty Images

Tip

8 microservices best practices to remember

From integrating domain-driven design to securing APIs, explore a range of microservices best practices for building a resilient and scalable application.

Microservices break applications into small services connected by documented APIs for maximum development efficiency and maintainability -- if architects structure the design process properly.

To successfully harness these architectural strengths and mitigate the complexity, latency and security issues that can arise from the deployment of distributed services, follow these microservices best practices.

1. Design from the top

Effective microservices implementation integrates domain-driven design (DDD) by aligning an application's business or technical domains with broad functional elements. For example, when viewing an application as an input-process-output combination, software architects can decompose each domain into functional units or microservices. During this process, they define the functionality of each microservice and the exchange of data when called. Before adding a microservice to an application, ensure that an existing defined service cannot logically be adapted to perform the appropriate function.

2. Never get out of context

In a stateful operation, the output depends on the context in which it occurs. For example, a transaction, such as a withdrawal from a bank account, must generate different results based on the state -- in this case, the balance -- of the account. Many functions, like editing and validating information, can be stateless. Microservices that support inherently stateless functions do not need to store contextual data because any copy of the microservice can fulfill the process needs. Microservices that require state knowledge must obtain it from the source, which can be achieved through methods such as API calls or database requests. Understanding the state requirements of each domain and microservice and managing that state are critical to the development of a resilient and scalable microservices application. Those familiar with the concept of immutable APIs and data objects will find its principles helpful here.

Diagram detailing an example of domain-driven design in which an application's subdomains pair with corresponding microservices.
In this example of domain-driven design, an application's functional subdomains pair with corresponding microservices.

3. Don't overdecompose

The Single Responsibility Principle maintains that an individual microservice ideally encompasses the smallest, fully complete set of functionality. For example, if logical steps one through three are always invoked together and in the same order, keep them within a single microservice. If the unit of functionality is broken down further, it will add overhead and latency without capturing any additional benefits in development or operation. Overdecomposing can not only make it harder to understand application logic, but it can also make deployment and orchestration more complex. Mapping a microservice application's workflow helps ensure that overall latency meets quality of experience requirements.

4. Host microservices in containers

Container hosting facilitates the deployment and redeployment of microservices, particularly when using a container orchestrator tool like Kubernetes. Containers are portable and carry parameterization for configuration, and the orchestration tool will ensure that library and database requirements are met. Additionally, containers use less server resources than virtual machines and are less expensive to host in the cloud. Container usage can also optimize API connections and reduce latency.

5. Secure exposed APIs

In most container microservice deployments, APIs receive private IP addresses suitable for intra-application use. If an organization requires broader access to the API or uses a microservice across multiple applications, it might be necessary to expose an API to the public address space of the company VPN or internet.

All exposed APIs are vulnerable to attack, so specific steps must be taken to enhance API security, which could impact the design strategy. Use encryption to protect data and access to the exposed API and add security penetration testing to the development pipeline. To further limit the breadth of exposure, it might be helpful to partition applications into groups, permit microservice sharing within those groups and then separate the groups into clusters or structure them in a way that ensures each has its own private IP address space. Firewalls can also be useful to prevent intergroup access.

6. Choose your database strategy carefully

Without proper configuration, databases can create a single thread element in a microservices application, impacting both parallelism and scalability. Assigning a database per service can help resolve this problem, but deploying an application in the cloud can also increase the cost of data storage.

Teams should typically deploy database storage of state information on a per-microservice basis. However, databases accessed and updated by multiple microservices might require special multiphase commit procedures to ensure updates propagate correctly. Where update locks are needed, assess the performance impact on the application and consider reevaluating the overall design to minimize possible adverse effects.

Diagram detailing a basic microservices architecture, with each microservice connected to its database.
A database-per-service approach assigns each distributed service a dedicated database to help achieve loose coupling and independent scaling.

7. Support scaling and resiliency with asynchronous APIs

In a synchronous model, an API call suspends execution of its task until it receives a result from the function it called, and in doing so, potentially inhibits scaling, load balancing and redeployment. As a result, asynchronous APIs are often a better option for microservices applications and other inherently asynchronous work and event distribution design patterns such as pub/sub messaging or a service mesh.

To recognize pending executions of microservices, asynchronous APIs typically require code adaptation that involves the use of context/state control. Try to select a single strategy and tool to support asynchronous API use because consistency facilitates maintenance.

8. Pipeline GitOps and DevOps

Developers can maximize the maintainability benefits of microservices by integrating deployment with the development pipeline and optimizing both to work effectively with microservice components. It's easiest to do this by adopting CI/CD or rapid deployment practices and tools. Version control is critical in microservices environments because some changes might require coordination among multiple microservices where API structures or content definitions change. This is particularly true if applications share microservices -- all the dependencies associated with a change might not be obvious. This kind of cross-application dependency isn't often an issue in nonmicroservice development, so it can be easy to miss. But in situations where applications share microservices, it's also possible to experience drift. Drift can create multiple versions of what should be the same microservice, with each version specific to a particular application.

Tom Nolle is founder and principal analyst at Andover Intel, a consulting and analysis firm that looks at evolving technologies and applications first from the perspective of the buyer and the buyer's needs. By background, Nolle is a programmer, software architect, and manager of software and network products. He has provided consulting services and technology analysis for decades.

Next Steps

Best practices for managing data in microservices

Dig Deeper on Enterprise architecture management