Skip to content

CONCEPT Cited by 1 source

Shim layer

Definition

A shim layer is a thin proxy library interposed between application code and one or more underlying implementations, exposing a single unified API and dispatching each call to the chosen backend.

In a dual-stack migration — the canonical motivating case on this wiki — the shim sits between application code and two copies of the same library (e.g. legacy and upstream) that are statically linked together in the same binary, with a runtime flag (flavor) choosing which backend handles each call. See concepts/runtime-flavor-dispatch.

Why a shim

A shim decouples two concerns:

  1. Stability for consumers — existing call sites at higher layers continue to target a stable API (often the library's own namespace, e.g. webrtc::), with no code changes required when the underlying implementation is swapped or forked.
  2. Flexibility for the migration — the shim can dispatch to any of N backends based on runtime configuration, enabling A/B testing, gradual rollout, easy rollback, or multi-backend coexistence (e.g. for hardware-specific variants).

Where to shim matters

The layer at which you interpose has order-of-magnitude cost consequences. From Meta's WebRTC case study:

"This approach — shimming at the lowest possible layer — avoids a significant binary size regression that duplicating the higher-layer call orchestration library would have caused. Duplication would have resulted in an uncompressed size increase of approximately 38 MB, whereas our solution added only about 5 MB — an 87% reduction." (sources/2026-04-09-meta-escaping-the-fork-webrtc-modernization)

Shimming too high duplicates orchestration code; shimming too low doesn't capture enough surface area to support the migration. The right layer is just below the library-under-migration but above its consumer — in WebRTC's case, between webrtc::* and the call-orchestration library.

Components of a well-formed shim

Meta's WebRTC shim illustrates the canonical shape:

  • Unified API — a single version-agnostic API every caller uses (e.g. webrtc_shim::Foo).
  • Runtime flavor dispatch — a global enum or config flag set at app startup decides between webrtc_legacy::* and webrtc_latest::* per-call.
  • Directional adapters — proxy objects that implement the unified API and forward to the underlying flavor, or vice-versa (exposing internal backend classes outward, or injecting custom components inward).
  • Directional converters — utility functions that translate structs/enums between the shim type system and the backend type system.
  • Template-based shared logic — the common code path lives in generic templates; per-flavor behavior lives in template specializations, so divergence cost is paid only where the flavors actually differ.
  • Backward compatibility header — usually via bulk using declarations so existing call sites don't need to be rewritten.

What the shim doesn't fix

A shim does not remove the need to resolve ODR violations when two copies of the library are statically linked. That's solved separately via symbol renamespacing (rewriting webrtc::webrtc_latest:: / webrtc_legacy:: across the code) and by merging/suffixing non-namespaced globals, macros, and free variables. The shim sits on top of that renamespacing work.

Seen in

Last updated · 319 distilled / 1,201 read