
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Implementing Database per Service: Patterns and Pitfalls is the defining architectural challenge when moving from monoliths to distributed systems. While isolating data stores prevents tight coupling and enables independent scaling, it introduces significant complexity regarding distributed transactions and cross-service queries. Before you provision separate RDS instances or MongoDB clusters, review this guide on RDS versus self-managed database trade-offs to understand the operational baseline.
How do you enforce data isolation in Database per Service?
The core tenet of Database per Service: Patterns and Pitfalls is strict ownership. A service must never expose its internal schema directly, nor should it allow other services to query its tables. In practice, this means creating separate logical databases or schemas even if you share physical infrastructure initially. For teams using PostgreSQL, creating distinct schemas within a single cluster can reduce costs while maintaining logical boundaries, but you must revoke all cross-schema privileges.
Practical Schema Isolation Steps
- Create a dedicated database user for each service with permissions restricted to its specific schema.
- Revoke
PUBLICaccess and explicit cross-schema grants during provisioning. - Use Infrastructure as Code to manage these permissions declaratively; manual GRANT statements drift quickly. See Terraform for practical IaC to automate this securely.
- Implement network-level segmentation where possible, especially in multi-tenant environments or regulated sectors like fintech in Nepal.
-- Example: Strict isolation for Order Service in PostgreSQL
CREATE SCHEMA orders_schema;
CREATE ROLE order_svc_user WITH LOGIN PASSWORD 'secure_pass';
-- Grant usage ONLY on the specific schema
GRANT USAGE ON SCHEMA orders_schema TO order_svc_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA orders_schema TO order_svc_user;
-- Explicitly prevent access to other schemas
REVOKE ALL ON SCHEMA public FROM order_svc_user;
REVOKE ALL ON SCHEMA users_schema FROM order_svc_user; This level of isolation feels restrictive at first, but it prevents the "distributed monolith" anti-pattern where services are coupled through hidden database dependencies. When an audit requires proving data segregation for SOC 2 compliance, these explicit permission boundaries serve as primary evidence.
How do you handle distributed transactions without 2PC?
Without shared ACID transactions, maintaining consistency across services is the most common failure point in Database per Service: Patterns and Pitfalls. Two-Phase Commit (2PC) is generally avoided in modern cloud-native architectures due to blocking behavior and poor availability. Instead, the Saga pattern provides eventual consistency through compensating transactions.
Choreography vs Orchestration
Choreography relies on events: Service A publishes OrderCreated, Service B reacts with PaymentProcessed. This decouples services but makes debugging difficult as flows grow. Orchestration uses a central coordinator (like AWS Step Functions or Temporal) to direct each step. For most production systems beyond three services, orchestration is safer because the state machine is explicit and observable.
A common mistake is implementing Sagas without idempotency. Network retries will happen. Every command handler and compensation logic must safely handle duplicate requests. Store processed message IDs in a deduplication table or use Redis to track completion status before executing business logic.
How do you query data across multiple microservices efficiently?
You cannot JOIN across databases. When your UI needs order details plus customer names, you have two primary options in Database per Service: Patterns and Pitfalls: API Composition or CQRS.
| Criteria | API Composition | CQRS (Read Models) |
|---|---|---|
| Complexity | Low initial setup | High (event pipelines, sync lag) |
| Latency | Higher (multiple round trips) | Low (single optimized query) |
| Data Freshness | Always consistent | Eventually consistent |
| Best For | Admin panels, low-traffic views | High-read dashboards, search |
| Failure Mode | Partial results if one service fails | Stale data during outages |
Implementing API Composition Safely
If choosing API composition, implement it in a dedicated gateway or BFF (Backend for Frontend) layer, not in the frontend browser. Use parallel async calls and set aggressive timeouts. Always define fallback behavior: if the Recommendation Service is down, return an empty list rather than failing the entire product page. For Laravel teams building BFFs, understanding performance optimization techniques is critical to avoid making the composer itself a bottleneck.
When to Adopt CQRS
CQRS adds significant operational overhead. Only adopt it when read latency directly impacts revenue or user experience. The read model should be denormalized specifically for the query it serves. If you find yourself building generic read models that mirror your write models, you are adding complexity without benefit. Start simple with API composition; migrate to CQRS only after profiling proves it necessary.
What are the operational pitfalls of polyglot persistence?
Choosing the right database for each service sounds ideal but creates massive operational tax. Managing PostgreSQL, MongoDB, Redis, and Elasticsearch simultaneously requires diverse expertise. Backup strategies, monitoring dashboards, and security patches differ for each. In smaller teams or startups in Nepal, this fragmentation often slows delivery more than the architectural benefits accelerate it.
Mitigating Polyglot Overhead
- Standardize on one primary store: Use PostgreSQL as the default for 80% of services. Only introduce specialized stores when you hit specific limitations.
- Use managed services: Offload patching, backups, and replication to cloud providers. The premium cost is usually lower than hiring a dedicated DBA.
- Unified observability: Ensure all datastores export metrics to the same Prometheus/Grafana stack. You cannot troubleshoot what you cannot see in context.
- Automate provisioning: Never create databases manually. Use Terraform modules that enforce encryption, backup policies, and tagging standards consistently.
In my experience helping Nepali companies scale, teams that resisted polyglot persistence until they absolutely needed it shipped features 3x faster than those who adopted exotic databases prematurely. Operational simplicity is a feature.
Conclusion
Mastering Database per Service: Patterns and Pitfalls requires accepting that distributed data is fundamentally harder than centralized data. Start with strict isolation and simple API composition. Introduce Sagas and CQRS only when consistency or performance demands justify the complexity. Resist polyglot persistence until benchmarks prove necessity. If your team lacks bandwidth to manage this complexity, consider whether a modular monolith better serves your current stage. For tailored architecture reviews or migration planning, reach out to discuss your specific infrastructure challenges.