An incremental method for changing tightly coupled Python systems using characterization tests, seams and small reversible transformations instead of a rewrite.

Production principle

Capture behaviour at stable boundaries, introduce one seam at a time and separate structural change from behavioural change.

Degraded
def process_order(order):
    db = connect(PROD_URL)
    customer = db.fetch(order["customer_id"])
    requests.post(CARRIER_URL, json=order)
    smtp.send(customer.email, "shipped")
    db.commit()
Repaired
def process_order(
    command: ProcessOrder,
    uow: UnitOfWork,
    carrier: CarrierGateway,
    notifications: Notifications,
):
    with uow:
        shipment = build_shipment(command, uow.customers)
        uow.shipments.add(shipment)
        uow.outbox.add(ShipmentRequested(shipment.id))
        uow.commit()
01

Characterize before improving

A characterization test records what the system does today, including awkward behaviour that callers may rely on. Begin at a stable external boundary such as an HTTP contract, command entry point or database outcome.

The first goal is not ideal coverage. It is a tripwire around the change you are about to make. Name surprising behaviour explicitly so the team can later decide whether to preserve or intentionally change it.

02

Create a seam

A seam is a place where behaviour can be substituted without editing the code that uses it. Parameters, small interfaces and extracted functions can separate time, network, storage and configuration from business logic.

Introduce the seam in a behaviour-preserving commit. Only then move logic or change policy. Keeping structural and behavioural changes separate makes review and rollback much safer.

03

Replace by capability, not layer

Large rewrites delay feedback and require old and new systems to reach parity before value appears. Select one business capability, route it through the new boundary and measure it in production.

A strangler approach needs explicit ownership of shared data and rollback routing. Delete old code only after traffic, tests and operational evidence show that the replacement owns the capability completely.

Review checklist

Evidence to take into review

  • Current observable behaviour is captured before modification.
  • External effects have explicit seams.
  • Structural and behavioural changes use separate commits.
  • Each migration slice is deployable and reversible.
  • Old paths are removed only after production evidence confirms replacement.
Continue the inspection

Explore all engineering notes.

Use PRODUCTION-7 to connect this concern with the other dimensions of a trustworthy backend.

View all articles Get the checklist