Defining Effective Microservice Boundaries - A Practical Approach To Avoiding The Most Common Mistakes
Most microservices failures stem from poor boundaries. Fix those, and your teams will ship faster, suffer less and succeed more.
Join the DZone community and get the full member experience.
Join For FreeHave you found yourself staring at an entire whiteboard filled with boxes and arrows, pondering whether this would become the next awesome microservices architecture or the worst distributed monolith that ever existed? Same here, and more often than I would like to admit.
Last month, I was talking to one of my cofounder friends, and he mentioned, “We have 47 services!” with pride. Then two weeks later, I was going through their docs and found out that to deploy a simple feature, I need to make changes in six of their services. What I thought was their “microservices” architecture turned out to be a monolith split into pieces, with distribution complexity but no benefits whatsoever.
Perhaps the most critically important and the most underappreciated step in this architectural style is correct partitioning of microservices. Doing so increases modular independent deployability, isolation of faults, and swiftness in team operations. Mess it up, and welcome to a distributed system that is a thousand times harder to maintain than the monolith you wanted to remove.
The Anti-patterns: How Boundaries Fail – Consider the Case
The Distributed Monolith: Death by a Thousand Cuts
An application made up of multiple services that are interdependent is an example of a pattern I encounter frequently, known as “distributed monolith.” This occurs when the complexity of distribution exists, but not the advantages.
Here are some indicators that your distributed Monolith is operating below peak efficiency:
- One modification prompts and requires multiple adaptations across different services.
- Disabling dependency across services results in a breakage.
- Cross-team coordination complexity for release planning.
A team I recently interacted with had to cross-coordinate across eight services for deployment just to add a field in their user’s profile. That is neither a microservices nor a service; that is just an unnecessarily intricate web of self-ensuing torture.
The Shared Database Trap
Again, “We need to use the same data!” falls under an alarm that can lead to this trap. Having many services access the same database tables leads to direct hidden coupling that eliminates every siloed advantage your architecture stands for.
I saw a retail company suffering through Black Friday as a consequence of four hours of downtime when their order service’s inventory service changed a database schema that their order service relied on.
Nanoservice Growth: Over-Indulging on the Good Stuff
This can also go in the opposite direction. Sometimes I refer to it as “nanoservice madness.” You create an endless number of services and it turns your architecture into something resembling spaghetti.
One of the gaming companies I consulted for was creating individual microservices for user achievements, user added preferences, user friends, and even user authentication and profile. Each of these services had their own deployment pipeline, database, and even an on-call rotation. The operational overhead was too much for their small team.
A Defined Scenario: Redesigning an E-Commerce Boundary
Let me show you an actual scenario from last year. I was consulting for an e-commerce business that had a typical case of a “distributed monolith.” Their initial architecture was something along the lines of this:
# Original architecture with poor boundaries
services:
product-service:
responsibilities:
- Management of product catalogs
- Inventory management
- Rules associated with pricing
- Discount calculations
database: shared_product_db
dependencies:
- user-service
- order-service
order-service:
responsibilities:
- Management and creation of orders
- Processing of payments
- Coordination of shipping
database: shared_order_db
dependencies:
- product-service
- user-service
user-service:
responsibilities:
- User profiles
- Authentication
- Authorization
- User preferences
database: shared_user_db
dependencies:
- product-service
It was obvious what the problems were. Services did have an appropriate amount of responsibilities but were overloaded with circular dependencies and too much knowledge of each other. Changes required coordinating at minimum three separate teams which is a disaster waiting to happen.
Their business professionals were with us for a week. By the end of day one, the sticky notes had taken over the walls. The product team was in a heated debate with the inventory folks over who “owned” the concept of a product being “in stock.” It was chaotic, but by the end of the week, we had much clearer boundaries.
The end result is as follows:
services:
catalog-service:
responsibilities:
- Product information
- Categorization
- Search
database: catalog_db
dependencies: []
inventory-service:
responsibilities:
- Stock tracking
- Reservations
database: inventory_db
dependencies: []
pricing-service:
responsibilities:
- Base prices
- Discounts
- Promotions
database: pricing_db
dependencies: []
order-service:
responsibilities:
- Order creation
- Tracking
- History
database: order_db
dependencies:
- catalog-service
- inventory-service
- pricing-service (all async)
payment-service:
responsibilities:
- Payment processing
- Refunds
database: payment_db
dependencies: []
user-profile-service:
responsibilities:
- Profile management
- Preferences
database: user_profile_db
dependencies: []
auth-service:
responsibilities:
- Authentication
- Authorization
database: auth_db
dependencies: []
I understand your initial thoughts, “You went from 3 services to 7? That is increasing complexity, not decreasing it,” right? The thing is, every service now has one, dedicated responsibility. The dependencies are reduced and mostly asynchronous. Each service is fully in control of its data.
The outcome was drastic. The average time to implement new features decreased by 60%, while deployment frequency went up by 300%. Their Black Friday sale was the real test for us six months later. Each service scaled on its load patterns rather than overstocking resources like the previous year. While the catalog service required 20 instances, payment only needed five. In the middle of the night, their CTO texted me a beer emoji, the universal sign of a successful launch.
A Practical Strategy Finding The Right Boundaries
Start With Domain-Driven Design (But Make It Work)
As much as Domain-Driven Design (DDD) purists would like to disagree, you don’t need to be a purist to benefit from DDD’s tools for exploring boundaries.
Start with Event Storming. This is a workshop approach where you gather domain experts and developers to construct business process models using sticky notes representing domain events, commands, and aggregates. This type of collaboration often exposes boundaries that are already a feature in your domain.
The “Two Pizza Team” Rule Still Works
Amazon continues to enforce their famous rule that states a team should be small enough to be fed by two pizzas. The team should be able to fit into a single meeting room alongside to microservices. If a service grows so complicated that it takes more than 5-8 engineers to maintain it, that's often an indication it should be split.
But the inverse is also true, if you have 20 services and only 5 engineers, there is an increasing likelihood you’ve become too fine grained.
The Cognitive Load Test Introduction 2025
An interesting approach I adopted in 2025 is what I like to refer to as 'the cognitive load test' for boundary determination, which seems to be very effective. It’s very straightforward: does any new team member manage to understand the goals, duties, and functions of the service within a day? If not, your service might have too many operations or is fragmented.
Actionable Insights for 2025 and Further
The Strangler Fig Pattern: Expand Your Horizons
When remodeling an existing system, don’t sweat the boundaries on the first attempt. Implement the Strangler Fig pattern which replaces parts of monolithic architecture gradually with well-structured microservices (named after a vine that gradually overtakes its host tree).
A healthcare client of mine tried to create the perfect microservices architecture for 18 months without writing a single line of code. Their design became completely obsolete after many changes within the business requirements during the tangled time-consuming process.
The Newest Pattern: Boundary Observability
A trend that I've started noticing in 2025 is something I'm calling “boundary-testing observability”—monitoring cross service dependencies and consistency data, essentially. ServiceMesh and BoundaryGuard are tools that will notify you when services are getting too talkative or when data redundancy is posing a consistency threat.
Concluding Remarks: Boundaries Are a Journey, Not a Destination
After assisting countless businesses with adopting microservices, my domain understanding boundaries have shifted as business needs change. This learning will always remain agile, and boundless.
If there’s a strong “value” in doing so, initiate with coarse-grained services and progress from there. Boundaries and borders are subjective. There is a fine line that dictates whether data should be shared or duplicated, so be reasonable. Most importantly, pay attention to the problems and pain your teams face, there is a strong chance that it will give clues to boundary issues.
As my mentor used to say, “the best microservices architecture isn’t the one that looks prettiest on a whiteboard—it’s the one that lets your teams ship features quickly and safely without wanting to quit every other Tuesday.”
Opinions expressed by DZone contributors are their own.
Comments