Skip to content

PATTERN Cited by 1 source

Error isolation per feature wrapper

When to use

You are composing a response from many independent units (features, widgets, panels) and you want:

  • A failure in one unit to drop only that unit, not the whole response.
  • Explicit opt-in for units that should fail the whole response (e.g. the primary feature on a page).
  • Centralised error telemetry keyed by unit and owner, not scattered per-feature try/except blocks.

The pattern

Every unit is invoked through a decorator that catches exceptions. On catch:

  • If the unit is marked essential, re-raise (the whole response fails).
  • Otherwise, log the exception with unit identity, owner metadata, and request context, and return a neutral value (empty list, None, sentinel) so the composer can continue.
def error_decorator(f: F) -> F:
    @wraps(f)
    def wrapper(self, *args, **kwargs):
        try:
            return f(self, *args, **kwargs)
        except Exception as e:
            if self._is_essential_provider:
                raise
            log_error(exception=e, context=self._context)
        return []
    return cast(F, wrapper)

class ErrorHandlingExecutionContext:
    def __init__(self, wrapped_element: ProviderBase) -> None:
        self._wrapped_element: ProviderBase = wrapped_element
        self._context: Context = self._wrapped_element.context
        self._is_essential_provider: bool = (
            self._wrapped_element.IS_ESSENTIAL_PROVIDER
        )

    @error_decorator
    def final_result_presenter(self) -> List:
        ...

Verbatim from the 2025-07-08 post: "each FeatureProvider is wrapped in an error-handling wrapper during the CHAOS build process. If an exception occurs, the individual feature is dropped, and the rest of the view remains unaffected. Unless developers choose to mark the feature as 'essential,' meaning its failure will affect the entire view."

The essential-feature opt-out

Most features on a page are additive — a user missing the "recently viewed" feature still has a usable screen. A few features are load-bearing — no search results on a search page is worse than a 500 error, because the user sees a UI with its heart ripped out.

The decorator defaults to drop-on-failure; developers explicitly set IS_ESSENTIAL_PROVIDER = True on providers whose absence would render the view nonsensical. This inverts the usual default of any exception fails the request and makes the safer-for-most-features path the easy one.

Telemetry is load-bearing

Verbatim (2025-07-08): "we record details such as the feature name, ownership info, exception specifics, and additional request context. This logging facilitates the monitoring of issues, the generation of alerts, and the automatic notification of the responsible team when problems reach a specified threshold."

Without telemetry this pattern silently hides bugs. The design compensates by:

  • Tagging every log with feature name + owner — routes alerts to the team that can fix it.
  • Including request context — allows reproduction.
  • Threshold-based alerting — a single dropped feature on one request is fine; the same feature dropping on 10% of requests is a page-quality incident.

Trade-offs

  • Silent degradation. Users may see a page that's missing a feature and not know; the system doesn't tell them. Acceptable because the feature is additive, but it does mean UX-facing monitoring must catch what the error logs catch.
  • Essential-flag discipline. If every feature is marked essential, you lose the isolation; if none are, broken primary features render empty pages. Teams must decide per feature.
  • Wrapper depth. The decorator must wrap the right methods. Wrapping only the final result_presenter can miss exceptions in load_data or resolve; the full lifecycle needs wrapped stages (see patterns/feature-provider-lifecycle).
  • State left behind. A feature that throws in resolve() may leave its state half-initialised. Other features or subsequent lifecycle hooks that read that state need to handle the empty-result case.
  • patterns/feature-provider-lifecycle — the multi-stage contract this wrapper is applied to.
  • concepts/blast-radius — the general principle; this is the request-scoped, feature-granular implementation.
  • Netflix Hystrix / resilience4j circuit breaker — same idea at the network-call layer; this pattern applies it at the request-composition layer.

Seen in

Last updated · 476 distilled / 1,218 read