Senior 10 min · May 23, 2026

Database per Service Pattern: Microservices Data Architecture

Master the database-per-service pattern: saga choreography, outbox pattern, CQRS, eventual consistency, and why shared DBs destroy microservice autonomy.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Each microservice owns its schema; no service may query another service's tables directly
  • Shared databases create deployment coupling, schema lock-in, and prevent independent scaling
  • The Saga pattern (choreography or orchestration) replaces distributed transactions
  • The Outbox pattern guarantees at-least-once event publishing without two-phase commit
  • CQRS separates the write model from optimised read models for query performance
✦ Definition~90s read
What is Database per Service Pattern?

The database-per-service pattern mandates that each microservice has exclusive ownership of its data store. The database technology (PostgreSQL, MongoDB, Redis, Cassandra) is chosen to fit the service's access patterns, not forced to match a corporate standard.

Think of microservices like separate restaurant kitchens.

The schema evolves independently, versioned alongside the service, deployed with the service. Other services that need data from this service must ask for it through the service's API or subscribe to events the service publishes.

This is not merely a physical separation of schemas within one database cluster (though that can be a useful interim step). True database-per-service means separate connection strings, separate credentials, separate lifecycle management. The goal is that any engineer on the orders team can drop and recreate the orders schema, run a major PostgreSQL version upgrade, or migrate to a different database engine entirely — without filing a change request with the inventory team or coordinating a multi-team release window.

The pattern is a direct enabler of the microservices autonomy guarantee. Without it, services are coupled at the data layer regardless of how clean their API contracts look. With it, teams can own their domain end to end: schema, business logic, API contract, and operational runbook.

Plain-English First

Think of microservices like separate restaurant kitchens. A shared database is like all kitchens sharing one pantry — one chef's reorganisation blocks everyone else. Database-per-service gives each kitchen its own locked pantry. Coordinating a multi-course meal across kitchens then requires a maitre d' (saga orchestrator) or pre-agreed signals (choreography events) instead of one chef yelling across a shared space.

The promise of microservices — independent deployability, technology heterogeneity, and fault isolation — evaporates the moment two services share a database. A schema change in the orders table forces a coordinated release with the inventory service. A long-running report query from the analytics service holds locks that stall order writes. A poorly indexed join in billing brings down the entire cluster. These are not theoretical concerns; they are Tuesday morning incidents at companies that adopted microservices topologically (splitting the code) but not architecturally (splitting the data).

The database-per-service pattern is the foundational data rule of microservices. It states that each service is the sole owner and gatekeeper of its persistent state. No other service may read from or write to that database directly. All data exchange happens through well-defined APIs or asynchronous events. This sounds simple, and it is — but the consequences are profound and the implementation challenges are real.

The first challenge engineers hit is cross-service queries. In a monolith, joining orders and customers is a single SQL statement. In a database-per-service world, that join must become a service call, an event-driven denormalisation, or a dedicated read model assembled from both services. Each approach has trade-offs in latency, consistency, and operational complexity.

The second challenge is distributed transactions. Moving money between accounts in a monolith is a single ACID transaction that either commits or rolls back. In a microservices world, the accounts service and the ledger service each own their data. The Saga pattern — implemented via choreography (events) or orchestration (a saga orchestrator) — replaces 2PC with a sequence of local transactions and compensating actions. Understanding when to use each flavour is one of the most important architectural decisions you will make.

The Outbox pattern solves the dual-write problem: how do you atomically update your database and publish an event without a distributed transaction? The answer is to write the event into an outbox table in the same local transaction, then have a relay process publish it asynchronously. This guarantees at-least-once delivery even if the broker is down at commit time.

Finally, CQRS (Command Query Responsibility Segregation) acknowledges that the write model optimised for consistency is rarely the same shape as the read model optimised for query performance. By maintaining separate models — often a normalised write store and a denormalised read store — teams can serve complex dashboards without burdening transactional databases.

Why Shared Databases Break Microservices

The entire value proposition of microservices rests on independent deployability. A team should be able to release their service on a Friday afternoon without coordinating with five other teams. Shared databases make this impossible in practice, even when service APIs are well-defined.

The mechanisms of coupling are insidious. Schema coupling: Service B reads columns from Service A's table. When A wants to rename or remove a column, it must first verify B has been updated — a cross-team coordination dance. Temporal coupling: A's migration holds a table lock. B's queries time out. The order of deployment matters and teams must coordinate release windows. Load coupling: B's report query does a full-table scan on A's orders table at 09:00 every morning, causing write latency spikes for A's customers.

None of these problems are visible in architecture diagrams that show only service-to-service API calls. They only surface in production, usually at the worst possible moment. Database-per-service eliminates all three forms of coupling by making the data boundary as explicit as the API boundary.

The transition from a shared database to database-per-service is rarely a big-bang migration. The strangler fig pattern applies here: identify seams in the existing schema where data belongs clearly to one domain, extract those tables into a service-owned schema, and replace direct DB queries in other services with API calls. The process is iterative and can take months for a large monolith, but each seam extracted reduces the blast radius of future incidents.

The 'Same Cluster, Different Schema' Trap
Separating schemas within a single PostgreSQL cluster is better than a single schema, but it is not true database-per-service. A long-running transaction in one schema can still hold locks that affect others. A cluster upgrade still requires coordinated downtime. Use separate clusters (or at minimum separate RDS instances) for true isolation — especially for services with different SLAs.
Production Insight
Every direct cross-service DB query you find in production is a ticking clock — the only question is which schema change will detonate it.
Key Takeaway
Data ownership is the real boundary in microservices — API boundaries without data boundaries are just a monolith with extra network hops.

Saga Pattern: Choreography vs Orchestration

A saga is a sequence of local transactions, each updating its own service's database and publishing an event or message to trigger the next step. If any step fails, compensating transactions undo the work of preceding steps. This replaces the two-phase commit (2PC) protocol, which requires a distributed transaction coordinator and is incompatible with independently deployed services.

Choreography-based sagas have no central coordinator. Each service listens for events and decides independently what to do next. The order service publishes OrderPlaced. The inventory service listens, reserves stock, and publishes StockReserved. The payment service listens to StockReserved, charges the card, and publishes PaymentProcessed. Each service knows its role in the dance without anyone calling the shots. This approach is simple to implement, scales well, and has no single point of failure. The downside is that the overall flow is implicit — it exists only in the aggregate behaviour of all participants. Debugging a stuck saga means correlating events across multiple service logs, which requires good distributed tracing and a shared correlation ID.

Orchestration-based sagas have a central saga orchestrator (often a separate service or a durable workflow engine like Temporal or Axon). The orchestrator drives the saga: it calls inventory to reserve stock, waits for confirmation, then calls payment, waits, then calls fulfilment. If payment fails, the orchestrator explicitly calls inventory to release the reservation. The flow is centralised and visible — you can query the orchestrator for the current state of any saga instance. The trade-off is a new service to maintain and a potential bottleneck if the orchestrator becomes overloaded.

Choose choreography when: the saga has 2-3 steps, participants are well-defined, and the team values simplicity. Choose orchestration when: the saga has 5+ steps, conditional branching is needed (retry payment 3 times before cancelling), long-running waits are involved (wait for human approval), or business teams need visibility into saga state via a dashboard.

Always Include a Correlation ID
Every event and API call in a saga chain must carry a correlation ID (a UUID generated at saga start). This is the only way to trace a saga across service logs, Kafka consumer groups, and distributed traces. Inject it into MDC at saga start: MDC.put("correlationId", saga.getCorrelationId()).
Production Insight
Choreography sagas look elegant on whiteboards but become debugging nightmares in production without centralised event sourcing or at least a saga tracker table.
Key Takeaway
Choose choreography for simplicity in small sagas; choose orchestration when you need visibility, branching logic, or long-running waits.

Outbox Pattern: Reliable Event Publishing

The dual-write problem is simple to state: your business logic needs to update its database AND publish an event to a message broker. If you do both in sequence, you have a race condition. Update DB, then crash before publishing? Event is lost. Publish event, then crash before committing? Event fires but DB has no record. Two-phase commit between a relational DB and Kafka is theoretically possible but operationally complex and almost never used in practice.

The Outbox pattern solves this with a local transaction guarantee. Instead of publishing directly to Kafka, you insert the event into an outbox table in the SAME database transaction as your business update. If the transaction commits, both the business data and the outbox record are durably saved. If the transaction rolls back, neither survives. The dual-write problem disappears because there is only one write target: your own database.

A separate relay process then reads unpublished outbox rows and publishes them to Kafka, marking them published after acknowledgement. The relay can be implemented as a polling publisher (a scheduled Spring job that queries for unprocessed outbox rows every 100ms) or as CDC (Change Data Capture) using Debezium, which tails the database's write-ahead log and publishes changes to Kafka with near-zero latency and no polling overhead.

Debezium is the production choice for high-throughput systems. It reads the Postgres WAL or MySQL binlog, requires no polling, and adds no write amplification to the application. The outbox rows themselves can be used as the event payload, or the Debezium Outbox Event Router SMT (Single Message Transform) can route events to per-aggregate topics automatically.

Critical detail: the relay guarantees at-least-once delivery, not exactly-once. Consumers must be idempotent. The standard approach is to include the outbox row's UUID as the event ID, and consumers check IF NOT EXISTS (SELECT 1 FROM processed_events WHERE event_id = ?) before applying the event.

Never Delete Outbox Rows Immediately
Keep published outbox rows for at least 24 hours. They are invaluable for debugging event delivery issues — you can check exactly what payload was sent and when. Archive to cold storage after 7 days. Immediate deletion loses your audit trail and makes replay impossible.
Production Insight
Debezium CDC with the Outbox Event Router SMT is the gold standard — zero polling overhead, sub-100ms latency, and it works even if your application is completely down.
Key Takeaway
The Outbox pattern converts a distributed commit into a local commit — one of the most powerful reliability patterns in event-driven microservices.

CQRS: Separating Write and Read Models

CQRS (Command Query Responsibility Segregation) is the recognition that the data model optimised for writing (normalised, consistent, transactional) is almost never the same shape as the data model optimised for reading (denormalised, pre-joined, query-friendly). Most systems use a single model for both, and both suffer as a result: writes deal with complex joins to maintain normalisation, reads deal with expensive joins to reconstruct domain objects.

In a microservices context, CQRS pairs naturally with the database-per-service pattern. The write-side service owns its transactional store and publishes domain events on every state change. One or more read-side projectors subscribe to these events and maintain denormalised read models optimised for specific query patterns. The customer dashboard projector might maintain a customer_order_summary table with pre-computed totals. The inventory report projector might maintain a daily_stock_snapshot table. Each read model is a materialised view, but built from events rather than from a database view.

The key operational insight is that read models are disposable and rebuildable. Because they are derived from an immutable event stream, you can drop a read model, replay the event stream from the beginning, and rebuild it with a new schema. This makes schema evolution on the read side trivial — something that is very expensive on a shared transactional database.

CQRS does introduce eventual consistency between write and read models. A customer who just placed an order may not see it immediately in the orders list if the projector has not yet processed the event. This is usually acceptable with a clear UX pattern: after the write succeeds, show the new item optimistically in the UI while the event propagates. If strict consistency is required for a specific query, serve it from the write-side service directly, accepting the performance cost.

Include Enough Data in Events to Avoid Lookups
When publishing domain events, include denormalised data that projectors will need — customer name, product title, price at time of purchase. This avoids the projector having to make synchronous API calls to enrich the event, which reintroduces coupling and creates a potential point of failure during event replay.
Production Insight
Design events for the readers, not for the writer — include the data consumers will project, not just the minimal change delta.
Key Takeaway
CQRS read models are disposable materialised views built from events — optimise them freely for query patterns without any risk to the write side.

Eventual Consistency: Designing for It, Not Against It

Eventual consistency means that, given no new updates, all replicas of a data item will eventually converge to the same value. In a database-per-service architecture, it is not an optional trade-off — it is the fundamental consistency model for cross-service data. The question is not whether to accept eventual consistency but how to design systems that are correct in its presence.

The first design principle is to make writes return as fast as possible and let the system catch up asynchronously. An order placement writes to the orders database and publishes an event. The inventory update happens asynchronously. The payment charge happens asynchronously. The confirmation email is sent asynchronously. The customer sees immediate feedback ('Your order has been placed') while the downstream effects propagate over the next few hundred milliseconds.

The second principle is to design UIs for optimistic updates. When a customer places an order, show it immediately in their order history with a 'Processing' status. Do not wait for the CQRS read model to catch up. Use a local state update (Redux, Zustand, or React Query optimistic update) that will be reconciled when the next poll or WebSocket push delivers the confirmed state.

The third principle is to design for idempotency everywhere. Events will be delivered at least once. Consumers may process the same event multiple times due to redelivery after a crash. Every event handler must be safe to call multiple times with the same event. The standard pattern is to maintain a processed_events table with the event ID and check it before applying — if already processed, skip and ack the message.

Business processes that require strong consistency (financial ledgers, inventory allocation at checkout) should be modelled as sagas with explicit compensating transactions, or should remain within a single service's database boundary where ACID transactions apply.

Use 202 Accepted for Commands That Trigger Async Sagas
Return HTTP 202 Accepted (not 200 OK) when a command triggers an asynchronous saga. Include an order/saga ID and a polling URL (/api/orders/{id}/status) so clients can track progress. This sets accurate expectations and prevents clients from assuming the operation is complete.
Production Insight
The time window of inconsistency in a well-tuned Kafka-based system is typically 50-200ms — design UX for this, not for hours.
Key Takeaway
Eventual consistency is a feature, not a bug — it enables independent scaling and autonomous deployments, but demands idempotent consumers and optimistic UI patterns.

Migration Strategy: Strangler Fig to Database-per-Service

Migrating a monolith with a shared database to a database-per-service architecture is a multi-month journey. The strangler fig pattern provides the framework: build new service-owned data stores alongside the monolith, gradually route traffic to them, and strangle the shared database one domain at a time.

Phase 1 — Identify seams: Map which tables belong exclusively to which domain. Tables that are truly shared (accessed by many domains) are the hardest — start with the easy wins: tables accessed by only one domain team. Use a database dependency graph tool or simply grep the codebase for table name references.

Phase 2 — Create the shadow service: Stand up the new service with its own database. Implement a dual-write adapter in the monolith: on every write to the shared table, also write to the new service's API (or outbox). Run both databases in sync for 2-4 weeks, comparing output.

Phase 3 — Migrate reads: Switch the consumers of this data (other services, the monolith itself) to call the new service's API instead of querying the table directly. Do this one consumer at a time, running A/B comparisons.

Phase 4 — Cut over writes: Remove the dual-write adapter. The shared table becomes read-only. After a confidence period, drop the table from the shared database.

Pitfalls: Cross-domain transactions that span both old and new data (must be converted to sagas before cutover). Reporting queries that join multiple domains (must become read models before cutover). Long-lived transactions that pin the shared DB (identify and shorten before migration).

Use Feature Flags to Control Migration Traffic
Every phase of the migration should be behind a feature flag so you can roll back instantly without a deployment. Keep the flag granular: per-tenant, per-region, or percentage-based rollout. This lets you validate the new service at 1% traffic before committing to 100%.
Production Insight
The strangler fig migration almost always takes 2x longer than estimated — the last 20% of cross-cutting references are the hardest.
Key Takeaway
Migrate data ownership incrementally, one domain seam at a time, with dual-write and feature flags providing a safe rollback at every step.
● Production incidentPOST-MORTEMseverity: high

The Shared Schema Migration That Took Down Order Processing for 4 Hours

Symptom
Alert fired at 14:02: order creation latency p99 spiked from 80ms to 30s. API gateway started returning 503s. Customer support reported users unable to checkout. Inventory and fulfilment dashboards also went blank.
Assumption
The on-call engineer assumed a network partition between the app tier and the database. They checked RDS CloudWatch — CPU was fine, IOPS normal. They restarted the order service pods. Nothing changed.
Root cause
A database migration job run by the billing team executed ALTER TABLE orders ADD COLUMN tax_amount DECIMAL(10,2) NOT NULL DEFAULT 0. On MySQL 5.7 (the team had not yet migrated to 8.0), this acquired an exclusive metadata lock for the duration of the table rebuild — 23 minutes on a 400M row table. Because the orders table was shared by order, inventory, and fulfilment services, all three were blocked. The billing team had no visibility into which other services depended on the table.
Fix
The DBA killed the ALTER session. Services recovered immediately. The migration was re-run the following week using pt-online-schema-change. The architectural fix took three months: the orders table was split into service-owned schemas with a dedicated read model for billing.
Key lesson
  • Schema coupling is invisible until it is catastrophic.
  • The moment two teams share a table, one team's routine migration becomes the other team's production incident.
  • Database-per-service is not optional for truly independent deployability.
Production debug guideSymptom → root cause → fix5 entries
Symptom · 01
Service B fails when Service A's database is down
Fix
This is data coupling masquerading as a network issue. Trace the call graph — if B is querying A's DB directly (shared connection pool, shared schema, read replica shared), that is the root cause. Introduce an API boundary: B calls A's REST/gRPC endpoint, which can apply circuit breaker logic and return cached or degraded responses. Short term: add a read replica dedicated to B. Long term: migrate B's queries to subscribe to A's events and maintain its own read model.
Symptom · 02
Saga transaction leaves data inconsistent after partial failure
Fix
First, identify the saga step that failed by querying the saga state table (if orchestrated) or correlating event IDs across service logs. Check whether compensating transactions ran. If they did not, the compensating handler likely threw an exception — look for uncaught errors in the dead-letter queue. Replay the compensating event manually after fixing the handler. Add idempotency keys to all compensating actions so replay is safe. Then harden the failure path with retry + DLQ + alerting.
Symptom · 03
Outbox events are not being published after service restart
Fix
The outbox relay (Debezium CDC or polling publisher) likely lost its offset or failed to start. Check the relay process logs first. For Debezium, verify the connector status via the Kafka Connect REST API (GET /connectors/<name>/status). If the connector is in FAILED state, check the offset stored in the connect-offsets Kafka topic. For a polling publisher, verify the processed flag on outbox rows — unprocessed rows should be queued immediately on startup. Add a startup health check that verifies the relay is running before the service accepts traffic.
Symptom · 04
CQRS read model is stale — showing data that was updated 10 minutes ago
Fix
Check the event consumer lag for the read-model updater using kafka-consumer-groups.sh --describe. If lag is growing, the consumer is too slow — profile the read-model update logic (likely missing an index on the projection table). If lag is zero but data is still stale, suspect a bug in the event handler (wrong event type subscribed, filtering too aggressively, or a field mapping error). Enable structured logging in the event handler with the event offset and processed-at timestamp to correlate lag.
Symptom · 05
Cross-service join is too slow — 5s to assemble a customer order summary
Fix
This is a missing read model. The summary should be pre-computed by subscribing to OrderCreated, OrderUpdated, and CustomerProfileUpdated events and writing a denormalised summary row per customer. If you cannot introduce a read model immediately, implement a parallel fetch: call order service and customer service concurrently with CompletableFuture.allOf(), then join in application memory. Measure — if the join data is small (<10k rows), in-memory join is fast enough as a bridge solution.
★ Debug Cheat SheetImmediate actions for database-per-service incidents
Outbox table growing — events not consumed
Immediate action
Check relay process health and Kafka broker connectivity
Commands
curl -s http://kafka-connect:8083/connectors/outbox-connector/status | jq '.connector.state'
SELECT COUNT(*), MAX(created_at) FROM outbox WHERE published = false;
Fix now
Restart the Kafka Connect connector; if broker is unreachable, fix network policy then restart
Saga stuck in PENDING state for >5 minutes+
Immediate action
Identify the blocking step and check its service health
Commands
SELECT id, current_step, updated_at FROM saga_instance WHERE status='PENDING' AND updated_at < NOW() - INTERVAL '5 minutes';
kubectl logs -l app=saga-orchestrator --since=10m | grep -E 'ERROR|saga_id=<id>'
Fix now
Trigger compensating transaction manually via admin endpoint or replay the step event from the DLQ after fixing the downstream service
Read model showing stale data+
Immediate action
Check consumer group lag on the projection topic
Commands
kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group read-model-updater
SELECT MAX(event_offset), MAX(processed_at) FROM projection_checkpoint WHERE projection='order_summary';
Fix now
If lag >0, scale up consumer replicas; if lag=0 but data stale, redeploy with fixed event handler and trigger full projection rebuild
Service startup fails — cannot reach its own database+
Immediate action
Verify connection string and DB pod status in the same namespace
Commands
kubectl get pods -n orders | grep -E 'postgres|orders-db'
kubectl exec -it orders-app-<pod> -- psql $DATABASE_URL -c 'SELECT 1'
Fix now
If DB pod is CrashLoopBackOff, check PVC mount; if connection refused, verify Service/Endpoint object and NetworkPolicy
Saga: Choreography vs Orchestration
DimensionChoreographyOrchestration
Flow visibilityImplicit — exists only in eventsExplicit — saga state queryable
CouplingServices know only their eventsServices coupled to orchestrator API
Failure handlingEach service handles its compensationOrchestrator drives compensation in reverse
DebuggingCorrelate events across service logsQuery saga state table directly
ScalabilityNo central bottleneckOrchestrator can become bottleneck
Best for2-3 step linear flows5+ steps, branching, long waits
ToolingKafka events, Spring @KafkaListenerTemporal, Axon, Spring State Machine
Operational complexityLow (no extra service)High (orchestrator service to operate)

Key takeaways

1
Database-per-service is the foundational data rule of microservices
without it, independent deployability is impossible regardless of API boundary quality
2
The Outbox pattern converts a dual-write problem into a single local transaction, guaranteeing at-least-once event delivery without distributed transactions
3
Choose saga choreography for simple linear flows; choose orchestration when you need visibility, branching, or long-running waits
4
Every event consumer must be idempotent
at-least-once delivery is the guarantee of all practical message brokers
5
CQRS read models are disposable and rebuildable from the event stream
optimise them aggressively for query patterns without affecting the write side
6
Migrate from shared DB using the strangler fig pattern
dual-write → migrate reads → cut over writes → drop old table, with feature flags at every step

Common mistakes to avoid

6 patterns
×

Sharing a database but calling it 'microservices'

Symptom
Teams must coordinate deployments whenever schema changes; one service's slow query causes latency spikes in unrelated services
Fix
Apply the strangler fig pattern to extract service-owned schemas; start with the easiest seams (tables only one team touches)
×

Publishing events directly to Kafka inside a @Transactional method without the Outbox pattern

Symptom
Events occasionally not published (transaction committed but Kafka send failed) or published twice (message sent but DB rolled back on retry)
Fix
Write to an outbox table in the same transaction; use Debezium CDC or a polling relay to publish to Kafka
×

Not including compensating transactions in saga design

Symptom
Saga fails halfway — inventory reserved but payment never charged; order stuck in PENDING state indefinitely
Fix
For every forward step in a saga, define and implement its compensating transaction before the saga goes to production
×

Non-idempotent event consumers

Symptom
Duplicate processing causes double charges, duplicate emails, or incorrect inventory counts after a Kafka partition rebalance
Fix
Add a processed_events table; check event ID before processing; store the check and the side effect in the same local transaction
×

Read models that make synchronous API calls during event handling

Symptom
Read model projector falls behind during dependent service outages; projection lag grows indefinitely
Fix
Enrich events at the source — include denormalised data in the event payload so projectors never need to call other services
×

Using 2PC (XA transactions) across services instead of sagas

Symptom
Distributed transaction coordinator becomes a single point of failure; locks held across services cause cascading timeouts under load
Fix
Replace 2PC with sagas; accept eventual consistency and design compensating transactions for every step
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Why does a shared database violate the principles of microservices?
Q02SENIOR
Explain the Outbox pattern and why it's needed.
Q03SENIOR
When would you choose choreography over orchestration for a saga?
Q04SENIOR
How do you handle a saga that fails halfway through? Walk me through com...
Q05SENIOR
What is CQRS and how does it complement the database-per-service pattern...
Q06SENIOR
How do you ensure eventual consistency does not cause incorrect business...
Q07SENIOR
How would you migrate a monolith with a shared database to database-per-...
Q08SENIOR
What is CDC (Change Data Capture) and why is it preferred over polling f...
Q01 of 08JUNIOR

Why does a shared database violate the principles of microservices?

ANSWER
A shared database creates schema coupling (one team's column rename forces coordination with all teams that query that column), deployment coupling (schema migrations require coordinated release windows), and load coupling (one service's expensive query degrades all others). Independent deployability — the core microservices promise — is impossible when the data layer is shared. True microservices require data ownership: each service controls its schema, connection strings, and database lifecycle.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Can two microservices share the same PostgreSQL cluster but use different schemas?
02
How do I query data across multiple services without a shared database?
03
What happens if a saga compensating transaction also fails?
04
How do I handle schema changes in event payloads?
05
Is the Outbox pattern necessary if I use Kafka transactions?
06
How long should eventual consistency take in a well-designed system?
🔥

That's Microservices Patterns. Mark it forged?

10 min read · try the examples if you haven't

Previous
Event-Driven Architecture with Spring Boot
1 / 3 · Microservices Patterns
Next
Saga Pattern for Distributed Transactions