Skip to content

PATTERN Cited by 1 source

Dual-architecture CI builds

Pattern

For a multi-quarter mobile framework migration (React Native's old → new architecture is the canonical example), build and test the app under both the current and the target framework architecture on every PR. Block merges that break either build. This converts the migration from a continuous operations problem ("keep the new-arch branch green while everyone else breaks it") into a gating problem ("PRs that break either architecture can't merge").

The canonical wiki instance is Shopify's 2025 new-architecture migration for Shopify Mobile + Shopify POS, using their internal TopHat platform to generate dual builds on every PR (see sources/2025-09-12-shopify-migrating-to-react-natives-new-architecture).

Forces

  • Weekly release cadence. Shopify ships weekly to merchants throughout migration; feature work can't pause.
  • Hundreds of engineers writing parallel feature code — without CI enforcement, someone will inadvertently break the target architecture in a way that's not caught until someone else tries to build it.
  • No remote feature flag gates the migration. The target architecture is a native binary property, so rollback means shipping a new binary; you can't "just flip it off" after merge.
  • Local dual-arch builds are slow. Developers skip the non-primary architecture when testing locally. CI is the only place the enforcement reliably happens.

Solution shape

  1. Per-PR dual build. Every PR triggers two builds — one under the old architecture, one under the new — that both must pass before merge.
  2. Make the dual build discoverable. Shopify's TopHat exposed both builds for easy download/test — "generated builds for both architectures on every PR, enabling easy testing without local rebuilds". This removes friction from testing the non-primary architecture, not just verifying it compiles.
  3. Pair with feature-flag dual implementation — see patterns/feature-flagged-dual-implementation. For modules that can't satisfy both architectures with a single implementation, the source tree has two implementations selected by feature flag, and both CI builds exercise the appropriate implementation.
  4. Track the dual-arch cost explicitly. The temporary code paths should be marked with deprecation warnings (Shopify explicitly did this) so the post-migration cleanup has an automatic todo list.
  5. Kill the old-architecture build after shipping the migration. Not before — the safety net is needed until production stability is confirmed.

Costs

  • 2× CI compute / wall-clock time per PR for the dual build.
  • Forked module code that has to be maintained in parallel for the migration window (Shopify: "We also added deprecation warnings to temporary code paths for post-migration cleanup").
  • Review burden — reviewers need to understand both architectures during the migration window.

Why "dual" beats "long-lived feature branch"

The alternative shape — a long-lived feature branch for the new architecture that the team periodically rebases — fails for Shopify-scale apps:

  • Merge conflicts compound. Every change merged to main creates a rebase burden on the feature branch.
  • The feature branch diverges. Without PR-level enforcement, the new-architecture branch develops its own bugs, its own style drift, and its own review backlog.
  • Production parity is impossible to verify. You can't run the feature branch in production alongside main, so every "is the new architecture ready?" question becomes a subjective judgment rather than a measurable state.

Dual-architecture CI on a single branch sidesteps all three problems.

Last updated · 470 distilled / 1,213 read