What we build

Legacy modernisation

This work usually starts with a system that still earns money and can no longer carry the load. We re-architect it in place - .NET Framework to .NET Core, monolith to event-driven services, one deployment per client to multi-tenant - and move traffic incrementally instead of in one cutover. A booking engine came off Ruby that way, with 450,000+ historical bookings intact and no downtime.

← All services

Monolith to event-driven services

We decomposed a legacy .NET Framework estate into event-driven microservices on managed Kafka, DynamoDB, and Lambda, and took the AWS bill down about 50% doing it.

Zero-downtime migration

Traffic moves incrementally, never in a single cutover, and history is migrated and reconciled first - 450,000+ bookings, in one case - so nothing is lost in the switch.

Runtime & framework upgrades

.NET Framework to .NET Core, Ruby to .NET - done for the throughput on the transactional paths, not for the version number.

One deployment, many tenants

Per-client instances put a ceiling on growth. We re-architected one into a multi-tenant platform now serving 100+ enterprise clients from a single deployment.

The path

Every step has a way back.

The old path stays up the whole way, one system holds authority at a time, and traffic moves in slices you can reverse with a config change. Decommissioning is a dated decision taken afterwards, not the same evening.

The old path keeps serving through every phase below, which is what makes each of them reversible. 1. reconcile first: row counts, checksums, field compare - then run it again, reversible onto the old path. 2. one writer: one holds authority, the other follows a transactional outbox, reversible onto the old path. 3. shadow reads: same query both sides, compared off the hot path, never served, reversible onto the old path. 4. move by key: by tenant or account, in slices - back is a config change, reversible onto the old path. 5. keep it warm: the old path runs until a month end and a peak day have passed, reversible onto the old path. 6. decommission: dated, afterwards - never the same evening. The old path ends at decommission - the one step with no way back, taken as a dated decision afterwards rather than on the night. The new path follows, then carries the traffic, one slice at a time. The measure of a good cutover is that nobody outside engineering can tell you the date it happened.

old path

serving the whole way. every revert lands back on it.

  1. 01

    reconcile first

    row counts, checksums, field compare - then run it again

    reversible
  2. 02

    one writer

    one holds authority, the other follows a transactional outbox

    reversible
  3. 03

    shadow reads

    same query both sides, compared off the hot path, never served

    reversible
  4. 04

    move by key

    by tenant or account, in slices - back is a config change

    reversible
  5. 05

    keep it warm

    the old path runs until a month end and a peak day have passed

    reversible
  6. 06

    decommission

    dated, afterwards - never the same evening

    old path ends here - a dated decision, afterwards

new path

follows, then carries. one slice at a time.

The measure of a good cutover is that nobody outside engineering can tell you the date it happened.

What you get

Yours to keep, and to run.

  • A migration plan with a cutover path and a rollback for every step
  • Historical data migrated and reconciled before traffic moves
  • CI/CD as code, so a release stops being a coordinated event
  • A cost baseline before and after, with the changes that moved it

Have a project in mind?

Start a project →