This project simulates a mortgage processing pipeline commonly seen in regulated financial institutions (e.g. Fannie Mae / Freddie Mac style environments).
Mortgage workflows are long‑running, event‑driven, and failure‑prone by nature:
- Multiple systems participate (origination, underwriting, payment, notification)
- Strong requirements on auditability, consistency, and replayability
- Failures must be recoverable, not hidden
This system is intentionally designed as an event‑first backend, prioritizing correctness, traceability, and operational clarity over synchronous performance.
The system models a simplified mortgage lifecycle using asynchronous events:
- Mortgage / Order creation
- Payment initiation and processing
- Success / failure events
- Downstream notification handling
All state transitions are driven by immutable domain events.
-
Order Service
- Owns mortgage/order state
- Persists source‑of‑truth records in PostgreSQL
- Emits domain events
-
Payment Service
- Consumes order events
- Simulates payment processing
- Emits success / failure events
-
Notification Service
- Subscribes to payment outcome events
- Represents downstream integrations (email, audit, reporting)
-
Event Broker
- Kafka‑style topic abstraction
- Decouples producers and consumers
All events are wrapped in a standardized envelope:
eventId(UUID)eventTypeversionoccurredAtpayload
This enables:
- Idempotent processing
- Replay support
- Backward‑compatible evolution
Financial systems must assume duplicate delivery.
Each consumer:
- Tracks processed
eventIds - Ensures side effects are applied at most once
This protects against:
- Broker retries
- Consumer restarts
- Manual replays
The system is designed around:
- At‑least‑once delivery
- Idempotent consumers
This trade‑off is intentional:
- Avoids the complexity of distributed transactions
- Matches common industry practice in regulated systems
To guarantee consistency between database writes and event publication, the system is designed to support the Outbox Pattern:
- Domain state change and outbox insert occur in the same DB transaction
- A background publisher forwards events to the broker
This prevents:
- Lost events
- Partial commits
Failures are treated as first‑class states, not exceptions.
Payment processing may fail due to:
- Timeouts
- Validation errors
- Downstream unavailability
Failures result in explicit domain events:
PAYMENT_FAILED
State transitions remain traceable and auditable.
Consumers are designed to:
- Retry transient failures
- Apply exponential backoff
Retries are bounded to avoid cascading failures.
Events that repeatedly fail processing are routed to a DLQ for:
- Manual inspection
- Reprocessing
- Reconciliation
This aligns with operational practices in financial institutions.
The system is designed with production observability in mind:
-
Structured logging per service
-
Event‑level traceability via
eventId -
Metrics suitable for:
- Throughput
- Failure rate
- Retry counts
This enables:
- Incident investigation
- Regulatory audits
- Operational dashboards
Because all domain changes are event‑driven:
- Historical events can be replayed
- New consumers can be introduced safely
- State can be rebuilt if necessary
Replay is a feature, not an afterthought.
- Mortgage workflows are long‑running
- Strong consistency across services is impractical
- Eventual consistency is acceptable and expected
- Tight coupling increases blast radius
- Failure propagation becomes harder to control
- Prioritizes correctness over cleverness
- Mirrors real‑world financial systems
This project is designed to demonstrate:
- Event‑driven backend design
- Financial‑grade reliability thinking
- Contractor‑ready system architecture
It intentionally avoids UI and non‑essential features to focus on core backend responsibilities.
- Persistent idempotency store
- Schema registry for event evolution
- Stronger audit log integration
- Deployment via AWS (RDS, MSK, ECS)
Author: Boxin Yang