Engineering Deep Dive
Porting 1,000 Lines of Order Logic Without Guessing
How a strangler cutover from a legacy .NET order pipeline to Bun and Postgres is de-risked by an executable parity harness: same payloads through both implementations, JSON-diffed until they agree.
Updated October 2026 ยท About a 6 minute read
Private employer Details are sanitized; architecture and outcomes are described without proprietary identifiers.
TL;DR
- About 1,000 lines of C# orchestrator logic decide how orders are routed, consolidated, and backordered, and the ERP downstream trusts those decisions.
- Feature flags and careful review both lost: the port is proven by a parity harness that runs the legacy regression payloads through both pipelines and diffs the JSON.
- The port now places orders end to end on the dev stack under 299 tests; the legacy half of the parity comparison and the ERP dispatch leg wait for the cutover window.
Context
B2B order intake at a wholesale operation is moving off a legacy stack, a .NET order pipeline plus PHP-era commerce logic and SQL Server stored procedures, onto a new platform: a Bun and Hono API backed by Postgres, with the ERP keeping ownership of pricing and final confirmation. Today, integrators place orders through an email-driven intake. The target is a real API: programmatic order placement that validates SKUs, ranks 14 warehouses, allocates stock, and returns structured responses the ERP can finalize.
Writing the new pipeline is not the risky part. The risky part is that roughly 1,000 lines of orchestrator logic encode years of fulfillment decisions: how lines get categorized, when shipments consolidate, where backorders go, what a will-call order splits into. The ERP downstream, and the warehouses behind it, trust those decisions. A port that is 98 percent faithful is not a compliment; the remaining 2 percent is misrouted orders.
So the interesting engineering question became: how do you replace a pipeline in one move and know, rather than hope, that the replacement makes the same decisions?
Constraints
- The ERP is the authority, and it has expectations. Pricing, promo tier selection, and final confirmation stay in the ERP. The API owns sourcing decisions and structured line types, which means its output is not free-form: it must be exactly what the downstream system has always been given.
- Two languages, no shared semantics. The reference implementation is C#; the port is JavaScript on Bun. Rounding, null handling, collection ordering, and default values all behave differently. Reading both side by side catches typos, not behavior.
- The floor is being built at the same time. The Postgres schema and the DB2 data-sync that feed the pipeline are themselves in flight, and warehouse quantities must stay authoritative in the legacy DB2 system rather than in any synced copy.
- One pipeline of record. The business wants a single-release swap, not a long coexistence: no dual-path feature flags, no legacy aliases kept alive beside the new API.
Options considered
Option A: a gradual strangler with feature flags. The textbook answer is to run old and new side by side and shift traffic customer by customer. It lost on two grounds. Practically, there is no traffic to shift: the current intake is email, not a comparable API, so there is no request stream to split between two endpoints. Structurally, a dual path means two order pipelines writing into one ERP for months, with every rule change made twice and every discrepancy ambiguous about which side is wrong. The halfway state is the most dangerous state, and flags extend it.
Option B: port carefully and trust review. Translate the orchestrator line by line and rely on code review to catch drift. This is how cross-language ports usually happen, and it is how they usually go wrong: review compares what code says, not what it does, and a thousand lines of branching over inventory states cannot be verified by inspection in any language, let alone across two.
Option C: cut over once, but make agreement executable. Keep the single-release swap, and spend the engineering effort on proof instead of coexistence. The legacy pipeline already had two assets worth their weight: a harness that exercises the full orchestrator directly, and 57 regression tests pinning its behavior. Those become the spec. The new pipeline is not done when it looks right; it is done when both implementations, fed identical payloads, produce outputs with an empty diff.
The design
The port maps the legacy orchestrator's eight steps into the new API's route actions: idempotency handling, customer preferences, promo validation, warehouse ranking, parallel SKU checks, allocation, persistence, and status handling. Warehouse ranking keeps its resilience posture from the legacy design, rating candidates in parallel with a distance-based fallback when the carrier API fails. Identity is token-scoped: the customer is resolved from B2B API credentials, never from a customer number in the request body.
Inventory is deliberately hybrid. Validation rules and reference data sync into Postgres, where the pipeline can query them cheaply, but live quantities are read from the legacy DB2 system at request time. A synced copy of stock levels would be a cache with a staleness window, and a staleness window on inventory is how two orders get promised the same last unit.
The cross-language parity harness. Use "Next step" to walk through it.
The harness is the part that makes the single-release swap defensible. Every payload from the legacy test suite runs through both implementations, outputs are normalized into comparable JSON, and the diff is the review. The 57 legacy tests stop being documentation of the old system and become the acceptance criteria of the new one. Disagreement between two languages stops being a debate between two readings of the code and becomes a field-level diff pointing at exactly where the port diverged.
What happened
The port is built. The legacy orchestrator's eight steps run as one Bun pipeline behind two lanes, the partner-facing order route and the storefront's cart submit, sharing one idempotency store and one 16-code outcome vocabulary, under 299 tests across 28 files. The storefront lane grew the quote-to-submit contract the case study describes: a version-stamped quote, a validate gate, and a submit that places exactly the allocation the buyer reviewed. Browser walks on the dev stack have placed orders end to end, revisited the receipt with zero writes, and caught one bug worth the whole exercise: a submit that outlived the client's timeout while the order was already placed, which the idempotency key now turns into a replay instead of a duplicate.
The parity harness runs the full pipeline against the ERP's test data, with live inventory reads, and shows the port making real decisions: a minimum-quantity rejection, a ranking fallback, a discontinued item that still has stock, a will-call split. What it has not done yet is the comparison it was built for. The legacy side's response was never captured from a session that could reach it, so the diff is still a plan rather than a result, and three behaviours sit flagged for that run: the will-call split on the partner lane, the 409 answered for in-flight conflicts, and a minimum-quantity rejection that leaves the order status untouched. Each is implemented as the specification reads; each needs the legacy run to confirm that the specification read the legacy right.
The cutover itself is deliberately not started. Every order placed in v3 today persists and reaches nobody, because the ERP dispatch leg stays off until the swap, and card authorization at commit fails closed until the payment seam lands. Those steps, the partner credential, the smoke test, and the retirement of the email intake are entries in a go-live register that keeps every cutover step in one place, and a validation phase that walks each buyer journey with a persona and a bug register began in October 2026. The single-release swap remains the accepted risk; what changed since the plan is that the risk is now measured against a working pipeline instead of an intended one.
Next deep dive
From Browser Canvas to Fabrication File
How a self-service gangsheet builder packs customer artwork with 2D bin packing in the browser, then turns submitted layouts into fabrication-ready print files through an asynchronous compositing pipeline.
About a 6 minute read