Composable Execution Layer (CEL)

By Everett Quebral
Picture of the author
Published on
Cover illustration for Composable Execution Layer showing modular logic flowing through shared runtime services

Part of Building Composable Systems. For the full frontend reading path, start with Part II: Composable Frontend Architecture.

A machine bay takes inputs on one side, runs them through portable logic gears, and sends the results to client, server, edge, and worker surfaces.

A customer changes a pickup location. The selector updates, but the order summary still shows the old delivery charge. In the support dashboard, the same change produces a different result because that team implemented the sequence separately.

Both interfaces can look correct in isolation. The problem is the behavior between them: which operation runs, what counts as success, and which state the rest of the interface should trust.

That behavior needs an owner outside the rendering code.

The Composable Execution Layer (CEL) is the name I use for that ownership. It gives frontend workflows explicit inputs, dependencies, results, and failure states. A component asks for an operation; the workflow coordinates it; the component presents the outcome.

Why We Need CEL

CEL provides a clean separation between logic and presentation, enabling teams to:

  • Write business logic once and reuse it across platforms.
  • Decouple UI layers from behavior, making refactors safer.
  • Optimize execution environments (e.g., run logic at the edge or server without rewriting).
  • Empower independent teams to build, test, and deploy autonomously.

Additional Reasons CEL is Critical Today

1. Avoiding Duplication Across Platforms

In multi-platform apps—web, mobile, desktop—developers often rewrite the same logic multiple times. CEL allows you to centralize business logic in one layer that feeds multiple frontends. This promotes consistency and drastically reduces bugs and rework. (leancode.co)

2. Enhancing Maintainability and Collaboration

When business rules are trapped inside UI components, they’re harder to find, maintain, or test. CEL enforces clear boundaries, which helps frontend and backend teams collaborate better without stepping on each other’s code. (medium.com)

3. Improving Testability

By isolating logic in pure functions, CEL makes it easy to write fast, reliable unit tests without setting up UI environments. This boosts confidence during refactors and CI/CD processes.

4. Enabling Scalable Architecture

CEL supports composable, scalable systems by allowing logic modules to be independently owned, deployed, and evolved. This is essential in large teams and micro-frontend architectures.

5. Ensuring Consistent User Experience

A shared CEL ensures business rules behave the same way across web, mobile, kiosk, and edge devices. This consistency improves both the user experience and developer reliability.

Modern applications face a complexity problem:

  • State is fragmented between components, stores, and backend APIs.
  • Logic is duplicated across client-side and server-side rendering paths.
  • Testing requires full framework scaffolding.

CEL addresses this by providing a dedicated execution layer:

  • Write once, run anywhere logic.
  • Unified treatment of user interaction, server-side data fetch, background sync, etc.
  • Support for multiple renderers (React, Web Components, SSR frameworks).

CEL creates a composable, reactive, testable logic tier that is:

  • Platform-agnostic
  • Observable and debuggable
  • Stateless or stateful (via injected contexts)

Architecture Overview

To understand how the Composable Execution Layer integrates within the broader architecture, it's helpful to visualize the separation of responsibilities between the UI, CEL, and underlying domain services.

At its core, CEL acts as the brain of the frontend system—decoupling business rules and logic flows from the rendering layer and ensuring that these rules can be invoked consistently regardless of where the application runs (browser, edge, or server).

Below is a simplified architectural diagram that illustrates how CEL connects to the UI and downstream domain services:

Explanation:

  • UI Renderer invokes actions, effects, or fetch logic via CEL APIs.
  • CEL encapsulates logic in composable units: tasks, effects, workflows.
  • Domain Handlers handle business operations, often in pure or async code.

Historical Context and Prior Art

To fully understand the value of the Composable Execution Layer, it helps to revisit how frontend logic has historically evolved—and where traditional paradigms have fallen short. The CEL was born out of necessity in a landscape where logic has grown increasingly fragmented and bound to presentation frameworks.

The Early Days: Coupling Everything

In the early days of web development, business logic was deeply entangled with the UI. PHP templates, jQuery functions, and even early React apps tied logic directly to buttons and components. This created brittle systems that were hard to reuse, test, or port.

Redux and Elm: Centralization with Limitations

The emergence of state containers like Redux and Elm provided a breath of fresh air. They emphasized centralized, pure logic and predictable state transitions. But the cost was boilerplate-heavy structures, poor async support, and tight coupling to component trees.

React Hooks: The Age of Scoped Behavior

React hooks introduced a new era of composability, giving developers the power to encapsulate behavior in small units. However, hooks are tightly bound to React’s lifecycle and cannot run independently of the UI. This made them difficult to reuse outside rendering contexts (e.g., SSR, background jobs).

CQRS & Command/Event Models

From the world of enterprise architecture, patterns like Command Query Responsibility Segregation (CQRS) and event-sourcing taught us how to model logic independently of UI. These ideas inspired CEL to treat tasks, effects, and side effects as orchestrated flows rather than imperative calls.

Actor Model: Logic as Message-Driven Modules

The Actor Model introduced the idea of independent, stateful units communicating via messages. CEL leverages this to build distributed workflows that respond to user actions or system events, making logic resilient, testable, and extensible.

Serverless & Edge Computing: Portability Becomes Critical

Finally, the explosion of edge computing and serverless architectures introduced a need for logic that can run anywhere. CEL is designed to execute in the browser, on the server, at the edge, or in a background worker—without needing to rewrite logic per context.


Implementation Examples

Below are implementation examples that show how CEL logic can be written once and reused across frameworks. In addition to usage, we also explore testing strategies and error handling.

TypeScript (Task Executor)

type User = { name: string }
type TaskContext = {
  userId: string
  baseUrl: string
  signal?: AbortSignal
}

export async function fetchUser(
  ctx: TaskContext,
  request: typeof fetch = fetch,
): Promise<User> {
  const url = new URL(`/api/user/${encodeURIComponent(ctx.userId)}`, ctx.baseUrl)
  const response = await request(url, { signal: ctx.signal })
  if (!response.ok) throw new Error(`User request failed: ${response.status}`)

  const user: unknown = await response.json()
  if (!user || typeof user !== 'object' ||
      !('name' in user) || typeof user.name !== 'string') {
    throw new Error('Invalid user response')
  }
  return { name: user.name }
}

The task receives its URL base and optional cancellation signal explicitly. A server caller must provide an appropriate base URL and authentication adapter; a relative browser URL alone is not portable. The task checks the HTTP response and validates the data before returning a user. The injected request function gives tests a narrow dependency to replace.

React Usage

import { useEffect, useState } from 'react'
import { fetchUser } from './cel/fetchUser'

export function UserProfile({ userId, baseUrl }) {
  const [state, setState] = useState({ status: 'loading', name: '' })

  useEffect(() => {
    const request = new AbortController()
    let active = true
    setState({ status: 'loading', name: '' })
    fetchUser({ userId, baseUrl, signal: request.signal }).then(
      user => { if (active) setState({ status: 'ready', name: user.name }) },
      () => { if (active) setState({ status: 'error', name: '' }) },
    )
    return () => {
      active = false
      request.abort()
    }
  }, [userId, baseUrl])

  if (state.status === 'loading') return <p role="status">Loading profile…</p>
  if (state.status === 'error') return <p role="alert">Could not load profile.</p>
  return <p>{state.name}</p>
}

The React adapter owns loading and error presentation. Cleanup aborts an obsolete read and prevents its completion from updating a different profile. The task itself does not know about React state. Authentication and request authorization remain responsibilities of the application adapter and service.

Web Component Usage

import { fetchUser } from './cel/fetchUser'

class UserProfile extends HTMLElement {
  static observedAttributes = ['user-id', 'api-base']
  private request?: AbortController

  connectedCallback() { void this.load() }
  attributeChangedCallback() {
    if (this.isConnected) void this.load()
  }
  disconnectedCallback() { this.request?.abort() }

  private async load() {
    this.request?.abort()
    const request = new AbortController()
    this.request = request
    this.textContent = 'Loading profile…'
    try {
      const userId = this.getAttribute('user-id')
      if (!userId) throw new Error('Missing user-id')
      const user = await fetchUser({
        userId,
        baseUrl: this.getAttribute('api-base') || document.baseURI,
        signal: request.signal,
      })
      if (!request.signal.aborted) this.textContent = user.name
    } catch {
      if (!request.signal.aborted) this.textContent = 'Error loading user'
    }
  }
}
customElements.define('user-profile', UserProfile)

The custom element invokes the same task. It reloads when its inputs change and cancels when disconnected. Assigning textContent renders the returned name as text rather than interpreting it as HTML. Each renderer owns its lifecycle while sharing the data contract.

Error Handling and Fault Tolerance

CEL logic is built to handle failures gracefully without locking up the UI. Handle rejected operations explicitly and preserve the distinction between a known failure and an unknown outcome. The example below wraps a read; a timed-out write requires reconciliation before retry because the service may already have accepted it.

async function safeFetchUser(ctx: TaskContext) {
  try {
    const user = await fetchUser(ctx)
    return { user }
  } catch (error) {
    console.error('Fetch failed', error)
    return { error: true }
  }
}

Testing CEL Tasks

Since CEL tasks are decoupled from the renderer, they can be tested in isolation using standard test frameworks like Jest or Vitest.

import { describe, it, expect, vi } from 'vitest'
import { fetchUser } from './cel/fetchUser'

describe('fetchUser', () => {
  it('requests the user through its injected adapter', async () => {
    const request = vi.fn<typeof fetch>().mockResolvedValue(
      new Response(JSON.stringify({ name: 'Alice' }), { status: 200 }),
    )
    const result = await fetchUser({
      userId: '123', baseUrl: 'https://example.test',
    }, request)
    expect(result.name).toBe('Alice')
    expect(String(request.mock.calls[0][0])).toBe('https://example.test/api/user/123')
  })
})

Testing Advantages:

  • No DOM or renderer setup required.
  • Clear input/output contract for every CEL unit.
  • Mocks and fakes are easy to inject via the task context.

Real-World Case Studies

🏢 Spotify – Shared Logic Across Surfaces

Background: Spotify operates across dozens of platforms—mobile (iOS/Android), desktop (Electron), web, smart TVs, and automotive systems. Maintaining consistent business logic across these platforms became a major challenge as teams duplicated authentication, user session handling, and playback behavior.

Problem:

  • Business logic was reimplemented independently on each platform, leading to inconsistencies and fragmented behavior.
  • Fixing bugs on one platform didn't ensure parity elsewhere, causing frustrating regression cycles.
  • UI framework dependencies made shared testing frameworks difficult to implement.

CEL Solution: Spotify introduced portable logic modules abstracted from rendering logic. These units could run independently in headless services, React Native, or embedded web players. By isolating behavior from UI code, teams built once and reused everywhere.

Results:

  • Uniform playback, auth, and user state behavior across platforms.
  • Streamlined debugging and QA across multi-device environments.
  • Easier A/B testing through decoupled feature toggles and CEL hooks.
  • Consistent user experiences.
  • Shared validation, playback, and authentication logic.
  • Easy A/B testing via feature-flagged CEL workflows.

Developer Insight:

“Moving workflows from React hooks to CEL helped us run logic in both SSR and background workers without rewriting it.” “Our validation logic used to live in three places—now we write it once and import it into any UI.”

🏢 Shopify – Server-Edge Hybrid Execution

Background: Shopify powers storefronts for millions of merchants. Their Hydrogen framework enables developers to build storefronts that can render and run logic at the server, edge, or client depending on performance, SEO, or personalization needs.

Problem:

  • Commerce logic (cart validation, pricing, promotions) needed to run consistently across environments.
  • Logic embedded in server-side code was hard to migrate or share with edge rendering.
  • Duplication of behavior across runtime boundaries increased the chance for bugs.

CEL-Inspired Architecture: Shopify adopted a CEL-style orchestration layer for commerce workflows. Each business process—like cart verification or shipping estimation—was composed into platform-independent tasks triggered by lifecycle hooks.

Results:

  • Code parity across client-rendered, edge-personalized, and server-rendered storefronts.
  • Stronger resilience in network-degraded environments (failover from client to edge).
  • Improved delivery velocity as logic could be tested and deployed once, reused everywhere.
  • Reduced logic drift across environments.
  • Instant failover between client and edge in case of network issues.
  • Better developer experience: same logic, tested once, reused everywhere.

Developer Insight:

“CEL gave us a single test surface for client/server workflows. Our mocks are smaller, and tests are faster.”

🏢 Airbnb – Composable Form Engine

Background: Airbnb’s platform relies heavily on forms—from listing creation to guest verification. These forms need to be dynamic, localized, and consistent across platforms while adhering to compliance and usability standards.

Problem:

  • Form logic—field visibility, validation, async workflows—was tightly coupled with UI components, making reuse difficult.
  • Teams building similar flows duplicated effort or introduced regressions.
  • Testing forms across platforms and markets was slow and brittle.

CEL-Driven Refactor: Airbnb developed a schema-first execution engine to separate form logic from UI. Each field referenced CEL tasks to drive behavior like conditional display, remote data fetching, or validation. This allowed for easier testing and previewing of forms without a UI.

Results:

  • Cross-platform consistency in form experience.
  • Localized behavior modules deployed independently of UI code.
  • Modular flows enabled new onboarding experiments and rapid internationalization.
  • UI framework-agnostic form logic (used in React, mobile web, and AMP).
  • Reusable validation chains across markets and languages.
  • Declarative testing of form workflows without a DOM.

Developer Insight:

“Our schema-driven form engine lets us launch new onboarding flows in days, not weeks. CEL helped us isolate logic and visualize flow logic in testing tools.” Airbnb collects large amounts of form data (host info, guest preferences, account setup, etc.). These forms must be fast, reliable, and easy to iterate on.

Problem:

  • Form logic was scattered across React components.
  • Validation and field visibility logic were duplicated across teams.
  • Updating form flows required manual regression testing.

CEL-Driven Refactor: Airbnb built a schema-first execution layer. Each form field described its behavior (visible if, validate when, load if) as CEL tasks, effects, and rules. Execution was decoupled from rendering.

Results:

  • UI framework-agnostic form logic (used in React, mobile web, and AMP).
  • Reusable validation chains across markets and languages.
  • Declarative testing of form workflows without a DOM.

Reusable Patterns and Strategic Benefits of CEL

CEL in Practice: Patterns and Anti-Patterns

✅ Recommended Patterns

  • Isolate logic into pure, testable functions: Pure functions without side effects are easier to test, debug, and reuse across environments.
  • Pass all context via typed objects (e.g., TaskContext): This keeps functions self-documenting and decoupled from global or implicit dependencies.
  • Co-locate related workflows and effects near the domain logic—not UI components: Improves modularity and reduces the coupling between views and business rules.
  • Reuse tasks across renderers (React, Web Components, SSR): Write once, deploy everywhere—ensures uniform behavior regardless of how and where the app renders.
  • Use CEL as the orchestration layer for background jobs, SSR preloads, or service workers: Avoids duplication by centralizing behavior, regardless of runtime context.

🚫 Anti-Patterns to Avoid

  • Calling DOM APIs or window.* inside CEL functions: Doing so breaks platform-agnostic design and makes the logic unusable in SSR, edge, or test environments.
  • Embedding presentation logic inside tasks: Tangles responsibilities, making it difficult to maintain or reuse behavior across projects.
  • Overloading a single task with too many responsibilities: Creates brittle, hard-to-test code. Favor small, composable tasks that do one thing well.
  • Mixing side effects and mutations without encapsulation: Use declarative flow or command chaining to manage complex execution cleanly.
  • Embedding presentation logic inside tasks (violates separation of concerns).
  • Overloading a single task with too many responsibilities—keep them composable.
  • Mixing side effects and mutations without encapsulation (prefer declarative chaining).

CEL Tooling and Developer Experience

To fully leverage CEL in complex applications, teams often benefit from enhanced observability and dev workflows:

  • Debuggable Observers: Instruments can trace CEL tasks, invocations, and emitted events.
  • Playground or Sandbox Runners: Run tasks outside the app context to prototype workflows.
  • Hot Module Reloading (HMR): Tasks can be hot-swapped in dev environments to speed iteration.
  • Visual Task Graphs: Use task registries to visualize dependencies and effects.
  • Test Framework Integrations: CEL logic fits naturally into Jest, Vitest, or Storybook Logic Test Kits.
  • ECN Compatibility: CEL integrates with the Event-Driven Component Network for real-time sync and distributed UI orchestration.

These tools help CEL act as both an engineering abstraction and a platform layer for teams building at scale.

CEL provides a clean separation between logic and presentation, enabling teams to:

  • Write business logic once and reuse it across platforms.
  • Decouple UI layers from behavior, making refactors safer.
  • Optimize execution environments (e.g., run logic at the edge or server without rewriting).
  • Empower independent teams to build, test, and deploy autonomously.

| Benefit | Expanded Explanation | | --------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | | Write Once | Author your business logic once and deploy it across multiple platforms—React, Web Components, or server. | | Isolated & Testable | Logic is separated from rendering concerns, making it easier to test in isolation with unit tests. | | Platform-Agnostic | CEL modules can run in browsers, on the server, or on edge runtimes without modification. | | Reactive & Observable | You can trace interactions through CEL tasks and observe emitted events, improving debugging and visibility. | | Orchestrated | CEL allows you to chain workflows, apply conditions (guards), and retry failures, making execution resilient and modular. | CEL supports sequencing, workflows, guards, and retries. |


Additional Reasons CEL is Critical Today

1. Avoiding Duplication Across Platforms

In multi-platform apps—web, mobile, desktop—developers often rewrite the same logic multiple times. CEL allows you to centralize business logic in one layer that feeds multiple frontends. This promotes consistency and drastically reduces bugs and rework. (leancode.co)

2. Enhancing Maintainability and Collaboration

When business rules are trapped inside UI components, they’re harder to find, maintain, or test. CEL enforces clear boundaries, which helps frontend and backend teams collaborate better without stepping on each other’s code. (medium.com)

3. Improving Testability

By isolating logic in pure functions, CEL makes it easy to write fast, reliable unit tests without setting up UI environments. This boosts confidence during refactors and CI/CD processes.

4. Enabling Scalable Architecture

CEL supports composable, scalable systems by allowing logic modules to be independently owned, deployed, and evolved. This is essential in large teams and micro-frontend architectures.

5. Ensuring Consistent User Experience

A shared CEL ensures business rules behave the same way across web, mobile, kiosk, and edge devices. This consistency improves both the user experience and developer reliability.

Summary

The Composable Execution Layer is not just a technical solution—it's a strategic foundation that empowers modern frontend development. It enables code reuse, simplifies testing, and makes cross-platform consistency attainable. Whether you're building complex multi-surface apps or looking to improve maintainability in your current stack, CEL offers a path to better separation of concerns, faster iteration, and more scalable architectures.

Diagram: CEL in Action


CEL is the logic backbone of the composable frontend. It replaces ad hoc, framework-bound code with a unified, portable execution layer—scaling from local components to edge services.

Stay Tuned

Want to become a Next.js pro?
The best articles, links and news related to web development delivered once a week to your inbox.