For Deal Engine, I’d make the ADR much more specific than “migrate the monolith to microservices.” An ADR should capture one architectural decision.
Given their domain and the migration context, a strong example would be extracting Refund Processing from the Scala monolith. It naturally lets you discuss migration strategy, GDS/external-provider failures, retries, idempotency, observability, and rollback.
Here’s a realistic ADR you could use for interview preparation:
ADR-0024: Extract Refund Processing from the Scala Monolith into a Dedicated Service
Status
Proposed
Date
2026-08-30
Context
Deal Engine currently handles refund processing as part of the existing Scala monolith.
Refund processing has become a good candidate for extraction because it has a relatively clear business boundary and interacts with external systems such as GDS and airline APIs.
The current implementation creates several challenges:
- Refund logic is coupled to the deployment lifecycle of the monolith.
- Failures or latency in external GDS/airline systems can consume resources inside the monolith.
- Retry and recovery logic is difficult to evolve independently.
- Refund operations may take significantly longer than normal request/response operations.
- External systems may successfully process a refund even when Deal Engine does not receive the response.
- Duplicate requests or worker retries must not result in duplicate refunds.
- Observability of the complete refund lifecycle is difficult when processing is embedded inside the larger application.
Migrating the entire monolith at once would introduce significant technical and operational risk.
We therefore need a migration approach that allows functionality to be extracted incrementally while the existing platform continues operating.
Decision
We will extract refund processing from the Scala monolith into a dedicated Refund Service.
The migration will follow an incremental Strangler Fig approach rather than rewriting the existing platform.
The initial architecture will be:
Client / Internal System
│
▼
API Gateway
│
▼
Scala Monolith
│
│ refund request
▼
Refund Service
│
├── Refund Database
│
└── GDS / Airline APIs
During the first migration phase, the monolith will remain the public entry point for refund requests.
The monolith will delegate eligible refund operations to the Refund Service.
The Refund Service will own:
- refund orchestration;
- refund state transitions;
- idempotency;
- interaction with GDS and airline APIs;
- retry policies;
- timeout handling;
- reconciliation;
- refund-specific persistence;
- refund-specific observability.
The service will maintain an explicit refund state machine such as:
REQUESTED
│
▼
PROCESSING
│
├───────────────┐
▼ ▼
SUCCEEDED FAILED
│
│
└──── UNKNOWN
│
▼
RECONCILIATION
A refund request must contain an idempotency key or stable refund identifier.
Repeated processing of the same refund identifier must not create another external refund.
The first version will preserve synchronous communication between the monolith and Refund Service where an immediate response is available.
Long-running operations, retries, and reconciliation may later be moved to asynchronous processing through a queue.
This allows the service boundary to be established before introducing additional messaging infrastructure.
Alternatives Considered
Rewrite the Entire Monolith as Microservices
Pros
- Clean architecture without transitional components.
- Opportunity to redesign all service boundaries.
- Removes legacy architecture in one migration.
Cons
- Very large migration scope.
- Long period before business value is delivered.
- Difficult to validate behavioral equivalence.
- High rollback risk.
- Existing business rules may be poorly documented.
Why rejected
A full rewrite introduces unnecessary risk. Incremental extraction allows individual business capabilities to be migrated and validated independently.
Extract a Simpler CRUD Capability First
For example, configuration or reference-data management.
Pros
- Easier first migration.
- Lower operational risk.
- Useful for validating deployment and infrastructure.
Cons
- Does not exercise the difficult architectural problems Deal Engine needs to solve.
- Provides limited evidence for handling concurrency, retries, external-system failures, and long-running workflows.
Why rejected
Refund processing provides a more meaningful vertical slice through the architecture and validates patterns required by future service extractions.
Keep Refund Processing Inside the Monolith
Pros
- No migration effort.
- No additional service infrastructure.
- Existing transactional model remains unchanged.
Cons
- Refund processing remains coupled to the monolith.
- External-system latency continues affecting monolith resources.
- Independent scaling and deployment are impossible.
- Recovery and reconciliation logic remain mixed with unrelated functionality.
Why rejected
This preserves the architectural constraints that the migration is intended to remove.
Consequences
Positive
- Refund processing can evolve independently from the monolith.
- GDS and airline integration failures are isolated behind a clear service boundary.
- Refund-specific retry and recovery policies become easier to implement.
- Independent deployment becomes possible.
- Independent scaling becomes possible.
- Observability can be built around the complete refund lifecycle.
- The migration pattern can later be reused for other capabilities.
The architecture can evolve incrementally:
MONOLITH
│
│
┌────────┴────────┐
│ │
Existing Logic Refund Service
│
▼
GDS / Airline
followed eventually by:
API Gateway
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Monolith Refund Service Other
Services
Negative
- The platform temporarily operates as a distributed system plus a monolith.
- Network failures must now be considered between the monolith and Refund Service.
- Distributed tracing becomes necessary.
- Data ownership boundaries must be clearly defined.
- Some business transactions can no longer rely on a single database transaction.
- Operational complexity increases.
Risks
Duplicate refund
A worker may retry after an external refund succeeded but before the response was persisted.
Mitigation: idempotency keys, persistent refund state, and reconciliation with the external provider.
Unknown external state
The GDS may timeout after receiving the request.
The system must not assume:
TIMEOUT == FAILED
Instead:
TIMEOUT
↓
UNKNOWN
↓
QUERY / RECONCILE
↓
SUCCEEDED | FAILED
Data ownership
Both the monolith and Refund Service could accidentally become authoritative for refund state.
Mitigation: once a refund is migrated, the Refund Service becomes the source of truth for refund execution state.
Migration regressions
Existing refund behavior may contain undocumented business rules.
Mitigation: migrate incrementally, compare old/new behavior, use feature flags, and initially route only selected refund traffic through the new service.
Rollout Strategy
The migration will be gradual:
Phase 1
Refund Service deployed
↓
No production traffic
Phase 2
↓
Shadow / test traffic
↓
Compare behaviour
Phase 3
↓
Small % production traffic
↓
Monitor
Phase 4
↓
Increase traffic
Phase 5
↓
100% refund processing
Phase 6
↓
Remove refund logic
from monolith
A feature flag will control whether refund requests are processed by the existing monolith implementation or delegated to the Refund Service.
Rollback therefore consists of routing new refund requests back to the monolith while investigating the service.
Success Criteria
The extraction will be considered successful when:
- refund behavior remains functionally equivalent;
- duplicate refunds are prevented;
- external timeout scenarios are recoverable;
- migrated traffic can be rolled back safely;
- refund processing is independently deployable;
- refund lifecycle metrics and traces are available;
- the monolith no longer owns refund execution logic.
Related Decisions
- ADR-0025: Refund Service Data Ownership
- ADR-0026: Idempotency Strategy for Refund Processing
- ADR-0027: Asynchronous Refund Processing
- ADR-0028: Distributed Tracing with OpenTelemetry
- ADR-0029: Service-to-GDS Retry and Reconciliation Strategy
Notice something particularly useful here: we deliberately did not decide everything in ADR-0024.
For example:
ADR-0024
Extract Refund Service
│
├── ADR-0025
│ Who owns refund data?
│
├── ADR-0026
│ How do we guarantee idempotency?
│
├── SPIKE
│ Can GDS refund status be queried reliably?
│ ↓
│ ADR-0029
│ Reconciliation strategy
│
└── SPIKE
SQS vs Kafka vs synchronous worker?
↓
ADR-0027
Async processing architecture
That is much closer to how I would expect a Principal Engineer to approach the migration: the ADR isn’t “the architecture document.” It records one consequential decision, while Spikes provide evidence for decisions where uncertainty remains.