Microservices are independently owned and deployable capabilities with explicit contracts and data boundaries. Event-driven design communicates facts asynchronously. Both can improve autonomy and scaling, but they introduce network failure, partial success, duplicates, ordering, schema evolution, observability, and operational cost. A modular monolith is often the better starting point.
Method note: Service boundaries, transaction semantics, retries, and event contracts are architecture choices shared by every deployment method. Concrete Portal and automation alternatives for Service Bus, Event Grid, Event Hubs, and compute appear in their service chapters.
Use business capabilities and ownership: document catalog, ingestion, search, answer generation, billing. Do not create a service per database table or technical layer. A service owns its data and exposes contracts; direct cross-service database access destroys that boundary.
Avoid distributed ACID transactions across independent services. Use a saga: local transactions plus messages and compensating actions.
The transactional outbox writes business state and an event record in one local transaction. A publisher sends the outbox event later. Consumers record processed IDs or make writes naturally idempotent.
Retry only transient failures, with exponential backoff, jitter, maximum attempts, and an overall deadline. Respect service throttling headers. Put a circuit breaker around failing dependencies. Use bulkheads so model/search failures do not consume every worker. Move exhausted messages to a dead-letter path with an owned replay process.
Layered SDK, service mesh, queue, and application retries can multiply attempts. Create one retry budget for the end-to-end operation.
Global ordering is expensive and rarely needed. Use partition/session keys to order events for one entity or workflow. Include sequence/version and reject stale updates. Handle late events and replay explicitly.
Use additive compatible changes, default missing optional fields, and version only when necessary. Publish schemas and contract tests. Never change the meaning of an existing event field. Consumers should ignore unknown optional fields.
Choose the smallest set that meets delivery, throughput, replay, and operations requirements.
Propagate trace/correlation/causation IDs through HTTP and message headers. Record service, version, event/message ID, tenant pseudonym, retry count, queue age, dependency latency, and outcome. Link producer/consumer spans rather than pretending asynchronous work is one continuous call.
Verify business invariants and recovery, not just HTTP status.