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.
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.
01
reconcile first
row counts, checksums, field compare - then run it again
reversible02
one writer
one holds authority, the other follows a transactional outbox
reversible03
shadow reads
same query both sides, compared off the hot path, never served
reversible04
move by key
by tenant or account, in slices - back is a config change
reversible05
keep it warm
the old path runs until a month end and a peak day have passed
reversible06
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.
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