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.
  • 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.