A Guide to Excellence in Microservices: 12 Essential Practices

Mehmed Ali Çalışkan
Hexaworks-Papers
Published in
6 min readOct 5, 2023

While there are no strict rules governing a microservice, adhering to the following 12 best practices in a microservice architecture will ensure that our software is reliable, sustainable, and scalable.

1. Designing Microservices with Standalone Capability

A microservice should be nearly autonomous. While microservices collectively contribute to the complexity of a larger system, designing each one as if it could be marketed as a standalone product encourages independence and loose coupling. Therefore, during the design phase, pose the question: Could my microservice function and be acquired independently by others? A resounding ‘yes’ ensures your microservice will seamlessly integrate into more intricate systems.

2. Microservice Scope: Limiting to a Single Domain or Subdomain

Adhering to the Single Responsibility Principle (SRP) from SOLID design principles is crucial in microservice architecture. Each microservice should be dedicated to one specific function or a closely related set of functions. For instance, if an online shop requires sending both email notifications and SMS messages, it’s advisable to separate these into distinct microservices unless they share extensive overlapping features. While SRP often aligns with domain boundaries in microservices, especially for those familiar with Domain Driven Design, it’s always appropriate to further narrow a microservice’s scope to specific segments of a domain if they necessitate independent operation.

3. Individualized Databases: Ensuring Independence for Each Microservice

Adopting an individual database for every microservice is more than just a best practice — it’s almost a foundational principle of microservice architecture. Ideally, this database should predominantly revolve around a single main table. If multiple central tables emerge within the database, it’s wise to revisit the Single Responsibility Principle discussed earlier. Separate databases not only reinforce the independence of microservices as highlighted in the first topic but also enhance system scalability. This becomes especially pivotal when handling vast data traffic where the database could potentially become a bottleneck.

4. Minimizing Inter-Microservice Communication: Prioritize Less Chatty Designs

While microservices inherently need to collaborate, it’s essential to minimize direct communication between them. Although they must be capable of interfacing, designs should lean towards reduced inter-service dialogue. Peer-to-peer communication can often introduce dependencies, but utilizing middlewares, such as message brokers, can ensure communication remains independent of individual service availability. While it’s challenging to eliminate all inter-service conversations, you can significantly reduce them by employing a central message broker and shifting inter-service interactions to client requests. If data integration between two microservices becomes necessary, consider first merging this data at the client level through separate requests to each service.

5. Embracing Statelessness in Microservices: Outsourcing the State

Microservice code should inherently be stateless, with state data residing either in a database or in-memory storage systems like Redis. States at the code level should exist solely within a single request-response cycle. If a task requires data from multiple events for completion, avoid designing your microservice to aggregate states within the application memory. Instead, the architecture should feature distinct tasks operating autonomously, utilizing states stored in databases or systems like Redis from each preceding operation.

6. Multi-Instance Capability: Ensuring Non-Interference Across Instances

Microservices should be architected to operate as multiple, concurrent instances without mutual disruption. Tasks, whether activated via REST/gRPC APIs or asynchronous event listeners, should be evenly distributed and load-balanced across these instances. While maintaining statelessness at the code level is essential for seamless multi-instance operation, internal microservice design must always account for the possibility of simultaneous instances running within the same environment.

7. Containerization: Enhancing Microservice Scalability and Portability

While not strictly mandatory, containerizing your microservices greatly augments their scalability and ease of management. Deploying microservices on traditional servers without containerization reduces their portability. Adopting containerization not only enhances scalability but also facilitates better integration with other containerized services, such as database pods.

8. Narrow Focus: Microservices Without the Burden of the Bigger Picture

A microservice operates within its defined parameters, drawing on specific concepts from the larger system but remaining indifferent to the overarching landscape. For instance, an order service concerns itself solely with whether the ‘isPaymentDone’ field is set to true or false, without needing insight into the broader intricacies of the payment process. This compartmentalized approach ensures that a microservice’s internal architecture is tailored exclusively to its domain’s business logic, without the distraction of the system at large.

9. Broadcasting Changes: Microservices and Public Notifications

A microservice ought to emit events upon any alteration in its data state. Employing a message broker is a prevalent method to achieve this. The microservice remains agnostic about the consumers of this information; its primary responsibility is to announce any state changes, minor or major, via a universal message broker. Instead of being tasked with notifying specific services of distinct changes, the microservice ensures that its state change events are accessible to any service subscribed to the relevant channels of the message broker.

10. Reactive Behaviors: Microservices Responding to External Events

While each microservice is architected for independence, this autonomy doesn’t preclude it from responding to state changes in other services. In line with event-driven design principles, a microservice should be equipped to undertake specific actions based on operations occurring in another microservice. Properly defined event channel names and payload signatures are imperative, serving as communication contracts throughout the complex system. Should integration challenges arise due to differing publish and consume signatures, an intermediary transposer service can bridge the gap between the publisher and consumer services. Occasionally, opting for transposer services is strategic, ensuring that one service’s architecture remains decoupled from others during the design phase.

11. The Merits of RESTful Interfaces in Microservices

Microservices can leverage various technologies for their APIs, such as gRPC or REST. Some might operate exclusively on event-driven triggers or scheduled tasks without offering a traditional API. However, even if a microservice primarily utilizes gRPC or is event-driven, featuring a REST API typically enhances its accessibility. This is because devops teams can conveniently inspect service states using tools like Postman or even straightforward browser requests. As a result, it’s a widespread practice to equip microservices with a REST API.

12. The Preference for Asynchrony in Microservice Operations

While microservice tasks can be initiated by synchronous requests demanding immediate responses, incorporating asynchronous communication often presents notable advantages. Asynchronous calls are readily embraced in inter-service communication through event-driven design, though their implementation between the service and frontend client can be more challenging. Ideally, a microservice should offer both traditional synchronous APIs and alternative asynchronous APIs to frontend clients. Implementing proper socket communication with browser or mobile clients can effectively facilitate this dual approach, paving the way for versatile asynchronous interactions.

--

--