# Defining Microservices: A Comprehensive Guide to Best Practices
Written on
Understanding Microservices Architecture
When your team embarks on the journey of implementing microservices, several pivotal questions often arise: How should you delineate your microservices? What boundaries should you establish for them? How many microservices are appropriate for your application or organization?
In the past, our team managed a robust monolithic application that served its purpose admirably for over ten years. However, as it became a hindrance to our advancement, we made the decision to decompose the application into smaller modules. Transitioning to microservices was a logical step as we modernized both our application and its underlying platform. Yet, we needed to carefully consider the number of microservices we intended to develop.
Creating too few microservices can lead to applications that still function like monolithic systems, while an excess can complicate maintenance. Below are key considerations our team evaluated during our transition to a microservices architecture, which may prove beneficial for refining your own design.
High Cohesion with Loose Coupling
Services should maintain high cohesion by focusing on a single functional purpose. Each service should only address one specific business need and avoid mixing multiple functionalities. For instance, while a customer is necessary to create an order, the service responsible for order creation should not also handle customer creation. Instead, the latter should be an independent microservice, allowing the order creation service to receive the customer ID as input after the customer is established.
Ensuring high cohesion while keeping services loosely coupled is crucial. Services should avoid direct dependencies on external code or databases whenever possible. In our order creation example, the order service shouldn't directly invoke the customer creation service; rather, it should handle customer IDs independently. This design prevents the order service from failing if the customer service is temporarily unavailable, allowing it to function with existing customer IDs without being affected by changes in the customer service.
Minimizing Database Interaction
To keep services narrowly focused, they should interact with as few database tables as necessary. The primary consideration is to engage with tables that contain transactional data, while not factoring in static lookup tables. Ideally, each service should rely on just one functional table interaction, although existing database structures from a monolithic application may complicate this goal. It’s essential not to force a one-table constraint if it doesn't align with your current database design.
Adapting to Varying Change Speeds
If functionalities exhibit different rates of change, they should be developed as independent services. For example, if functionality A frequently undergoes updates while functionality B remains stable, it’s wise to deploy them as separate microservices. This separation allows each service to maintain its own build, deployment, and operational cycles, reducing the risk of one service inadvertently affecting the other.
Independent Lifecycle Management
Features that can function with their own independent lifecycle—encompassing coding, building, testing, and deployment—should be considered as standalone microservices. Even if two features change at similar frequencies, bundling them into a single microservice can hinder independent development and deployment. This independence empowers teams to introduce new features and enhancements without waiting for others to finish their tasks.
Scaling Requirements
Different functionalities may have distinct scaling requirements. Some services may need to scale rapidly due to high demand at certain times, while others maintain a steady load throughout the year. For instance, a product viewing service may experience spikes during holiday sales, while warranty inquiries might peak after purchases. These differing demands suggest that such features should be developed and scaled as independent microservices.
Transaction Speed Variation
When functionalities demand different transaction speeds, they should be implemented as separate microservices, ideally with distinct data stores. A service with slow performance can monopolize database resources, degrading the performance of faster services if they share the same pool. By isolating slower services with their own build and deployment processes, you can prevent performance bottlenecks.
Business Group Ownership
If your application serves multiple business units, it’s critical to segregate functionalities based on ownership. This division helps avoid conflicting requirements and potential defects that may arise from shared codebases. By creating distinct services for different business groups, you mitigate the risk of unintended impacts during code changes.
Additional Considerations
Keep the following points in mind when determining microservice boundaries:
- Avoid creating overly small (nano) services, as they can generate unnecessary complexity. If two functions consistently call each other, consider consolidating them into a single service.
- Ensure that no duplicate information is disseminated across services. Each piece of data should have a single source, with other services utilizing identifiers to access necessary details.
- Leverage the experience of your team. Skilled developers can more accurately define microservice boundaries, so having at least one expert can significantly aid in this process.
- Reassess your database structure. A single database for all microservices can become a bottleneck, so adopting a micro DB architecture may be beneficial for optimizing service independence.
Final Thoughts
The considerations outlined here focus on accurately defining microservice boundaries. While there is no one-size-fits-all approach, it's essential to create services that meet the outlined criteria. Don't feel pressured to achieve the perfect number or size of services from the outset; requirements will evolve over time, and your understanding of both functional and non-functional needs will deepen.
Instead of striving for perfection at the beginning, aim to launch a functional system in production. Learning from real-world performance will enable you to refine your service architecture over time.
I welcome your insights and experiences with microservices. Please share your thoughts in the comments section!
Thank you for reading this article. You may also find interest in the following resources:
This video offers insights on designing microservice architectures effectively.
In this video, microservices architecture is explained in a way that is accessible to everyone, making it a valuable resource for understanding the concepts discussed.