DEV Community

Cover image for Using the Strangler Pattern to Add AI Around Legacy Systems Without Rewriting the Core
OutworkTech
OutworkTech

Posted on

Using the Strangler Pattern to Add AI Around Legacy Systems Without Rewriting the Core

A legacy system can contain years of business rules, data relationships, and operational behavior that nobody wants to rediscover during a full rewrite. Yet the same system may expose only batch interfaces, rigid APIs, or workflows that make introducing a new AI capability difficult.

The answer is not necessarily to replace the core application. A safer approach is to introduce a new capability at a well-defined boundary, route a limited set of workflows through it, and preserve the existing system as the authority for critical business operations.

This is where the Strangler Fig pattern becomes useful. Martin Fowler describes the pattern as an incremental approach to modernization: introduce new functionality around the existing system, establish seams between components, and gradually move behavior when there is a clear reason to do so. The pattern reduces the need for a single, high-risk replacement event, although it introduces temporary architectural complexity.

Adding AI makes those boundaries even more important. An AI component can interpret requests, classify information, summarize records, or propose actions. It should not automatically inherit the authority to change business state simply because it can generate a plausible action.

Start With a Workflow, Not a Model

Consider a hypothetical order-management application that already handles customer records, inventory, pricing, and order updates. The system works, but support staff spend time interpreting requests and locating the relevant information.

The proposed AI capability could classify incoming requests, retrieve relevant order information, and suggest an action. That does not require replacing the order-management core. Instead, isolate one workflow with a clear input, output, and operational boundary.

For example:

  • The existing application remains responsible for customer identity, order state, inventory, and business rules.
  • An adapter exposes the specific operations the new capability needs.
  • The AI component interprets the request and produces a structured proposal.
  • A deterministic validation layer checks whether the proposed action is permitted.
  • The existing application executes the approved operation.
  • Logs and traces capture the request, decision, validation result, and execution outcome.

The distinction is important: the AI proposes; the application validates and executes. This keeps the initial modernization scope narrow enough to test without turning the model into a second implementation of the core business logic.

Put an Adapter Between AI and the Existing System

A common mistake is connecting an AI component directly to database tables or internal functions because those interfaces are convenient. This can couple model-generated decisions to implementation details that were never designed as public contracts. An adapter provides a more controlled boundary. It translates between the new capability's request format and the existing application's supported operations.

For example, an adapter might expose a conceptual operation such as:
get_order_status(order_id)

The operation returns a defined response containing the order identifier, current status, and relevant timestamps. It does not expose unrestricted database access. For a state-changing operation, the boundary should be narrower still. Rather than accepting an arbitrary instruction, the adapter should accept a validated command with explicit fields, an authenticated caller, and a defined set of permitted operations.

The adapter should handle:

  • Input validation: Reject missing identifiers, invalid values, and unexpected fields before invoking the core.
  • Identity and authorization: Enforce access using the actual caller's permissions, not a permission level inferred from model output.
  • Contract translation: Map new request and response structures to the existing system's established interfaces.
  • Timeouts and errors: Return explicit failure states rather than encouraging the AI component to guess what happened.
  • Audit context: Preserve correlation identifiers and the identity of the actor responsible for the action.

This layer is not merely a compatibility wrapper. It is the place where the new capability is prevented from bypassing established application controls.

Separate AI Output From Business Decisions

Structured output can make an AI integration easier to validate, but valid structure does not prove that an action is correct or authorized. Suppose the AI proposes changing an order's delivery address. A response may conform perfectly to the expected schema while referring to the wrong order or attempting an unauthorized change.

The execution path should therefore have separate validation stages:

  1. - Validate the output structure and required fields.
  2. - Resolve the referenced entity through the existing application.
  3. - Verify the caller's authorization for the requested operation.
  4. - Check business invariants, such as whether the order is eligible for modification.
  5. - Apply risk-based approval rules.
  6. - Execute the command through the established application interface.
  7. - Record the result and return the confirmed state.

These checks should not depend on the model's assurance that it has followed the rules.

A conceptual execution boundary might look like this:

request = receive_user_request()

proposal = ai_component.propose_action(request)

if not schema_is_valid(proposal):
reject("invalid proposal")

if not caller_is_authorized(proposal, caller):
reject("unauthorized")

if not business_rules_allow(proposal):
reject("business rule violation")

if requires_human_approval(proposal):
return request_approval(proposal)

result = legacy_adapter.execute(proposal)

record_execution_result(result)
return result

This is pseudocode, not a drop-in implementation. Each validation function must be implemented against the application's real identity model, business rules, and transaction semantics.

The important property is the separation between generating an action and granting permission to execute it.

Route Traffic Gradually

The Strangler Fig approach depends on choosing a boundary that can be introduced without changing every consumer of the existing system. A routing layer can direct selected workflows to the new capability while keeping other workflows on their existing path. The routing decision should be explicit and observable.

A practical rollout might proceed in stages:

Stage 1: Read-only assistance

Allow the AI capability to classify requests, summarize records, or retrieve information through approved interfaces. Keep state-changing operations disabled.

Stage 2: Shadow evaluation

Run the new path alongside the existing workflow without allowing it to commit changes. Compare its proposed output with the established process, using representative cases and defined acceptance criteria.

Stage 3: Limited execution

Enable one low-risk, reversible action for a controlled set of requests. Use explicit authorization, bounded permissions, and a defined approval policy.

Stage 4: Expand by capability

Add further workflows only after the current boundary has demonstrated acceptable behavior, including failure handling and operational visibility. A feature flag can control exposure, but it is not an authorization mechanism. The adapter must enforce permissions regardless of how traffic reaches it.

The goal is not to move every request to the new path as quickly as possible. It is to learn where the new capability adds value without weakening the existing system's guarantees.

Design the Failure Path Before Enabling Writes

An AI service can become unavailable, exceed a timeout, return an unusable result, or propose an action that the core application rejects. The integration needs a deliberate response to each condition. For read-only assistance, a useful fallback may be to return the existing application's normal response without the AI-generated summary. For a state-changing workflow, fallback may mean returning control to the established manual process rather than attempting an alternative action automatically.

Retries need particular care. If a write request reaches the legacy system but the response is lost, the caller may not know whether the operation succeeded. Repeating the request can create duplicate side effects unless the operation has suitable idempotency or deduplication semantics.

For state-changing operations, consider:

  • A stable operation identifier that survives retries.
  • An idempotency mechanism supported by the receiving application, where appropriate.
  • Explicit distinction between rejected, failed, and unknown execution outcomes.
  • Reconciliation against the authoritative system when the outcome is uncertain.
  • A controlled manual escalation path for operations that cannot be safely resolved automatically.

Do not treat every timeout as proof that the operation failed. The system may have committed the change even though the response never reached the caller. Likewise, a fallback should not silently execute the same business action through a second path while the first path's outcome remains unknown.

Keep the Data Boundary Explicit

Introducing AI around an existing application also creates a data-flow question: which information can leave the core, where is it processed, and what is retained? The adapter should return only the fields needed for the workflow. Sensitive fields should not be exposed simply because they are present in the underlying record.

The integration should also define what happens to prompts, retrieved records, model responses, and execution metadata. Retention, access, and redaction requirements depend on the data and the model provider, so they must be verified for the actual environment.

Observability should capture enough information to reconstruct a failure without indiscriminately recording sensitive payloads. Useful fields include the correlation ID, workflow name, policy outcome, adapter latency, execution status, and escalation reason. A trace should make it possible to distinguish an AI failure from an authorization rejection, a legacy-system error, or a timeout with an uncertain outcome.

Know When the Boundary Is Not Enough

The pattern is not a reason to preserve every part of a legacy system indefinitely. A narrow adapter is useful when the existing application has stable business behavior and a manageable interface can be established. It becomes less useful when the integration requires extensive duplication of business rules, continuous synchronization of conflicting state, or workarounds for behavior that cannot be reliably isolated.

At that point, the modernization decision may need to change. A domain capability may be better extracted, a contract redesigned, or part of the core replaced. The decision should follow the constraints discovered at the boundary rather than an assumption that either replacement or preservation is always correct.

There is also a cost to transitional architecture. Every additional adapter, routing rule, and policy boundary becomes something the team must test, observe, secure, and eventually maintain or remove. The team should define ownership for those components and periodically reassess whether the boundary still serves a purpose.

Make the First Change Small Enough to Reverse

The useful outcome of this approach is not simply that an AI feature has been attached to an older application. It is that the organization can introduce a new capability while retaining control over business state, authorization, failure handling, and operational ownership.

Start with one workflow, one explicit interface, and a limited set of permitted actions. Measure whether the new path behaves correctly, identify where its assumptions fail, and keep the existing path available while those questions remain unresolved. Modernization becomes easier to reason about when the new capability can evolve independently without quietly taking ownership of the system around it.

The core does not need to disappear before the surrounding system can improve. The boundary is where that improvement becomes testable, controllable, and reversible.

Top comments (0)