Many engineering organizations attempt to escape monolithic friction by decomposing their entire system into dozens of microservices simultaneously. Without clear domain boundaries, this merely converts in-process function calls into fragile, high-latency HTTP or gRPC network requests with distributed transaction complexity.
Domain-Driven Boundary Identification
SazM approaches decomposition by profiling change frequency and operational contention:
- High Friction / High Velocity: Services that deploy multiple times daily and suffer deployment queue bottlenecks (e.g. Lead Intake, User Onboarding).
- High Contention / High Latency: Services that hold long-running database locks or interface with unstable third-party APIs.
- Stable Core: Core accounting or ledger tables that change rarely and operate reliably within the monolith.
The Anti-Corruption Layer (ACL)
When modern services must read or write data shared with the legacy monolith, an Anti-Corruption Layer translates between domain models:
export function adaptLegacyUserData(legacyRow: Record<string, any>): CanonicalUser {
return {
id: legacyRow.vch_user_uuid || legacyRow.int_user_id.toString(),
email: legacyRow.txt_email_address.trim().toLowerCase(),
tier: legacyRow.int_status === 1 ? 'enterprise' : 'standard',
createdAt: new Date(legacyRow.dt_created).toISOString(),
};
}
By isolating the modern schema from legacy naming idiosyncrasies, the modern system remains clean and verifiable.
Event-Driven Decoupling via Transaction Outbox
Instead of cross-database transactions, publish domain events to an Outbox table:
BEGIN TRANSACTION;
UPDATE orders SET status = 'shipped' WHERE id = ?;
INSERT INTO domain_events (id, topic, payload, created_at)
VALUES (uuid(), 'order.shipped', json_object('order_id', ?), datetime('now'));
COMMIT;
A background worker polls the outbox and streams events to downstream services with guaranteed delivery.