This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 43b1d0193ed7 CAMEL-25142: design - late binding of cross-cutting 
features (#27083)
43b1d0193ed7 is described below

commit 43b1d0193ed79f50b848f916fdff3bbd0b8fdba4
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 29 15:06:37 2026 +0200

    CAMEL-25142: design - late binding of cross-cutting features (#27083)
    
    A design document for binding onException, the interceptors, onCompletion 
and route configurations
    late (resolved by the routes from a registry, with a generation check), so 
they keep working when
    routes and route configurations change at runtime and when CamelContext is 
restarted.
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 AGENTS.md                         |   1 +
 design/cross-cutting-binding.adoc | 215 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 216 insertions(+)

diff --git a/AGENTS.md b/AGENTS.md
index e0426a118a34..a18411f21240 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -314,6 +314,7 @@ jump straight to implementation after reading the issue 
description and the curr
    - **Tracing / Telemetry** (OpenTelemetry, spans, context propagation): 
[`design/tracing.adoc`](design/tracing.adoc)
    - **MDC / Logging** (MDC propagation, logging context): 
[`design/mdc.adoc`](design/mdc.adoc)
    - **Headers** (naming conventions, constants, upgrade policy): 
[`design/headers.adoc`](design/headers.adoc)
+   - **Cross-cutting features** (onException, interceptors, onCompletion, 
route configurations and how they bind to routes): 
[`design/cross-cutting-binding.adoc`](design/cross-cutting-binding.adoc)
 5. **Understand the broader context**: If the issue involves a module that 
replaced or deprecated
    another (e.g., `camel-opentelemetry2` replacing `camel-opentelemetry`), 
understand *why* the
    replacement was made and what was intentionally changed vs. accidentally 
omitted.
diff --git a/design/cross-cutting-binding.adoc 
b/design/cross-cutting-binding.adoc
new file mode 100644
index 000000000000..83dd3f4fccd4
--- /dev/null
+++ b/design/cross-cutting-binding.adoc
@@ -0,0 +1,215 @@
+---
+title: Late binding of cross-cutting features
+authors:
+  - "@davsclaus"
+reviewers: []
+approvers: []
+creation-date: 2026-09-29
+last-updated: 2026-09-29
+status: proposed
+see-also:
+  - "CAMEL-25142"
+  - "CAMEL-25140"
+  - "CAMEL-25141"
+  - "CAMEL-18923"
+replaces: []
+superseded-by: []
+---
+
+== Summary
+
+The cross-cutting features of Camel routes (`onException` and the error 
handler, `intercept`, `interceptFrom`,
+`interceptSendToEndpoint` and `onCompletion`) and the route configurations 
that hold them are bound to the routes
+when the routes are created. After that the binding is fixed. This proposal 
describes how they are bound today, and a
+model where they are bound late: the routes resolve what applies to them from 
a registry in the `CamelContext`, so
+changes to routes and route configurations while Camel runs keep working as 
expected.
+
+== Motivation
+
+Camel is increasingly used in a more dynamic way: dev mode reload, routes 
added and removed at runtime, route
+configurations managed by tooling or low-code editors, and restarting a 
`CamelContext` in place (which could make a
+reload on the `CamelContext` level possible, instead of reloading only the 
routes).
+
+The static binding does not handle this well:
+
+- Updating or removing a route configuration at runtime does not update the 
routes that use it
+  (see also CAMEL-18923, where this was explained as "not supported, as they 
can be global and affect running routes").
+- Dev mode reload works because by default it removes all the routes and loads 
all the files again.
+- Some features are lost when the routes are created again, such as on a 
`CamelContext` restart.
+- `interceptSendToEndpoint` bound the endpoint to the processors of one route, 
so removing that route broke the other
+  routes (CAMEL-25140).
+
+Historically these features were implemented as internal processors woven into 
each route, with a lot of logic to make
+them fast. That gives good performance, but makes them complex, and hard to 
change when routes and configurations are
+dynamic.
+
+== Goals
+
+- The cross-cutting features keep working when routes are added, removed, 
reloaded or restarted, and when route
+  configurations are added, updated or removed at runtime.
+- The per-message behaviour stays as today (which exception policy matches, 
`onWhen` predicates, endpoint patterns,
+  and the order of precedence).
+- No noticeable overhead on the hot path.
+- Simpler code than today, where possible.
+
+== Non-goals
+
+- Changing the scope of the interceptors (such as limiting 
`interceptSendToEndpoint` to the routes it is defined for);
+  see CAMEL-25141.
+- Changing the DSLs or the model classes that end users use.
+
+== Current design
+
+This describes the state of Camel 4.23 (main in September 2026).
+
+=== Model time: merging into the routes
+
+`RoutesDefinition.prepareRoute` merges the definitions of the `RouteBuilder` 
and the matching route configurations
+into each route, and `RouteDefinitionHelper.prepareRoute` adds them to the 
outputs of the route:
+
+- The route configurations are selected by the `routeConfigurationId` of the 
route (a list of ids or patterns), or the
+  global ones (without an id) when the route has none 
(`RouteDefinitionHelper.routesByIdOrPattern`).
+- For `onException`, the order is: the route's own, then the `RouteBuilder`'s, 
then the route configurations'. For the
+  error handler: the route's own, then the `RouteBuilder`'s, then the last 
matching route configuration, then the
+  default of the `CamelContext`. A route with an `onCompletion` of its own 
ignores the global ones.
+- The definition objects are shared by all the routes they apply to (the lists 
are new, the definitions are not).
+- A route is prepared only once (`RouteDefinition.isPrepared`). Changes to the 
route configurations after that are not
+  merged into it. `DefaultModel.addRouteConfiguration` and 
`removeRouteConfiguration` only change the list of
+  configurations.
+
+=== Reify time: processors per route
+
+- *onException / error handler:* `OnExceptionReifier` creates the 
`onException` processors of the route
+  (`Route.setOnException`). An error handler is created for each `Channel` 
(each EIP node), and each gets its own map
+  of exception policies built from the route's `onException` definitions 
(`ErrorHandlerReifier.configure`).
+- *intercept:* `InterceptReifier` adds an `InterceptStrategy` to the route, 
and removes its definition from the route
+  outputs. Each `Channel` applies the intercept strategies when the `Channel` 
is built (`DefaultChannel.initChannel`),
+  so only the `Channel`s built after the intercept is reified are intercepted 
(from reading the code, not verified
+  with a test).
+- *interceptFrom:* `InterceptFromReifier` creates a filter step at the start 
of the route.
+- *interceptSendToEndpoint:* until CAMEL-25140, the reifier registered an 
endpoint callback that wrapped the endpoint
+  with the processors of the route, and removed its definition from the route 
outputs. With CAMEL-25140 the endpoint
+  is wrapped once, and each route registers its interceptor on it while the 
route runs.
+- *onCompletion:* `OnCompletionReifier` stores the processors on the route 
(`Route.setOnCompletion`) and creates an
+  `OnCompletionProcessor` step, which adds a synchronization to the unit of 
work of each exchange.
+
+=== Runtime
+
+- Exception policies are only looked up when an exchange fails 
(`DefaultExceptionPolicyStrategy`), from the policies
+  of the error handler of the failing `Channel`.
+- Intercept strategies cost a wrap per `Channel`, decided when the route is 
built.
+- `onCompletion` adds a synchronization per exchange.
+
+=== Lifecycle
+
+[cols="2,3"]
+|===
+| Change | What happens today
+
+| Route configuration added or removed at runtime
+| Only the list changes. Existing routes keep the merged definitions; only 
routes prepared later see the change.
+
+| Route configuration updated (same id)
+| Only the list changes (the routes loader removes and adds it again). The 
routes keep the old definitions.
+
+| Dev mode reload (default `removeAllRoutes=true`)
+| All the routes are removed, and all the files are loaded again, so the 
routes are prepared again with the new
+  configurations.
+
+| Dev mode reload with `removeAllRoutes=false`
+| Only the routes of the changed files are prepared again. Routes in other 
files keep the old configurations, and
+  configurations removed from a file are never removed.
+
+| `CamelContext` stop and start
+| The routes are created again from their prepared definitions. The 
definitions that removed themselves from the route
+  outputs are lost: `interceptSendToEndpoint` before CAMEL-25140 (verified 
with a test), and `intercept` (from reading
+  the code, not verified).
+
+| Route removed
+| Its processors are removed. Before CAMEL-25140, an `interceptSendToEndpoint` 
shared by other routes stopped working.
+|===
+
+== Proposal
+
+=== A registry of cross-cutting definitions
+
+The `CamelContext` keeps the cross-cutting definitions in a registry, keyed by 
where they come from (a `RouteBuilder`
+or a route configuration), instead of copying them into the routes when the 
routes are prepared. A route knows its
+sources (its `RouteBuilder` and its `routeConfigurationId`), and resolves what 
applies to it from the registry, in the
+same order of precedence as today.
+
+The definitions of a route itself (a route-level `onException`) stay in the 
route, as they belong to it.
+
+=== Resolve late, cache, and check a generation
+
+To keep the hot path fast, the routes do not look up the registry per message:
+
+- The registry has a generation counter, which is incremented when a 
definition is added, updated or removed.
+- Each route (or `Channel`) caches what it resolved, together with the 
generation it resolved it at.
+- On the hot path, the check is a volatile read and an int compare. When the 
generation has changed, the route
+  resolves again (lazily, on the next exchange, or eagerly when the change is 
made).
+- The decision per message (which exception policy, `onWhen`, endpoint 
pattern) is the same as today.
+
+=== The processors of cross-cutting features
+
+The processors of a cross-cutting feature (such as the steps of an 
`onException` or an `intercept`) are created per
+route and per definition, and are created again (and the old ones stopped) 
when the definition changes. They follow the
+lifecycle of the route: started when the route starts, stopped when it stops 
or is removed (as CAMEL-25140 does for
+`interceptSendToEndpoint` with a route service).
+
+=== Per feature
+
+- *Route configurations:* adding, updating or removing a route configuration 
updates the routes that use it, by
+  incrementing the generation (no need to reload the routes). This is the unit 
of change for low-code tooling.
+- *onException / error handler:* the exception policies are resolved from the 
registry (route, `RouteBuilder`, route
+  configurations) by the route, instead of being copied into a map per 
`Channel`. This ties in with the refactoring of
+  `RedeliveryErrorHandler` (CAMEL-24980). The error handler itself (its type 
and redelivery settings) could be resolved
+  the same way.
+- *intercept / interceptFrom:* the `Channel` asks whether an intercept applies 
(cached per generation), instead of
+  being wrapped when it is built. The definitions are not removed from the 
model.
+- *interceptSendToEndpoint:* done in CAMEL-25140 (wrapped once, interceptors 
registered per route).
+- *onCompletion:* the global `onCompletion`s are resolved from the registry 
when the unit of work is created.
+
+=== Order of work
+
+. Route configurations: keep them in the registry and resolve them in the 
routes (the groundwork for the other steps).
+. onException and the error handler.
+. intercept and interceptFrom.
+. onCompletion.
+
+Each step keeps the public DSLs and model classes as they are, and adds tests 
for restart, reload and adding/removing
+routes and route configurations.
+
+== Compatibility
+
+The goal is to do this without changes to the public APIs and the DSLs, in 
which case it can be done in Camel 4.x
+(4.24). If a step needs changes to public APIs (such as `Route`, `Channel` or 
the SPIs of the error handler) or
+changes behaviour users rely on, that step becomes a goal for Camel 5.0.
+
+Known behaviour to keep:
+
+- The order of precedence of the route, the `RouteBuilder` and the route 
configurations.
+- `onCompletion` of a route overriding the global ones.
+- What the dumped routes (`dumpRoutes`, the dev consoles, the route diagrams 
in the TUI) show. Today they show the
+  merged definitions; they should show the resolved view.
+
+== Risks
+
+- *Performance:* every EIP node goes through a `Channel`, so any check there 
must be very cheap. Measure it with the
+  routing benchmarks (on Linux, as `System.nanoTime` does not scale across 
threads on macOS).
+- *Concurrency:* a change while exchanges are in flight. An exchange should 
finish with what it resolved when it
+  started (or at the step), and the old processors should be stopped when no 
exchange uses them.
+- *Semantics:* the order of precedence and the rules for which `onException` 
matches are documented and tested, and
+  must stay the same. The existing tests of camel-core and camel-spring-xml 
are the safety net.
+- *Tooling:* tools that read the merged model need the resolved view.
+
+== Related tickets
+
+- CAMEL-25142: this work.
+- CAMEL-25140: `interceptSendToEndpoint` registered per route on an endpoint 
wrapped once.
+- CAMEL-25141: limit `interceptSendToEndpoint` to the routes it is defined for.
+- CAMEL-18923: updating a route configuration with the routes loader.
+- CAMEL-5629: adding an intercept at runtime.
+- CAMEL-3870: reusable `onException`s (which became route configurations).
+- CAMEL-13912: error handlers are created when the routes are created (for 
performance).
+- CAMEL-24980: refactoring of `RedeliveryErrorHandler`.

Reply via email to