boson_runtime/builder.rs
1//! [`BosonBuilder`] — construct a [`Boson`] runtime with injected ports.
2
3use std::sync::Arc;
4
5use boson_core::{ExecutionContextFactory, QueueBackend};
6use boson_telemetry::{ConsoleOpsLog, OpsLog};
7
8use crate::registry::TaskRegistry;
9use crate::Boson;
10
11/// Builder for [`Boson`].
12///
13/// **Required:** a queue backend ([`queue_backend`](Self::queue_backend) or
14/// [`queue_backend_from_global`](Self::queue_backend_from_global)) and an
15/// [`execution_context_factory`](Self::execution_context_factory). For `#[task]` handlers, call
16/// [`auto_registry`](Self::auto_registry) so inventory entries from linked crates are collected.
17///
18/// **Optional:** [`worker_id`](Self::worker_id) and [`lease_ttl_secs`](Self::lease_ttl_secs) for
19/// distributed workers, [`ops_log`](Self::ops_log) for telemetry, [`without_worker`](Self::without_worker)
20/// when driving [`ManualWorker`](crate::ManualWorker) in tests.
21///
22/// # Example
23///
24/// After [`build`](Self::build), call [`configure`](crate::configure) if callers use macro
25/// `send_with` (not required when holding `Boson` and calling [`Boson::enqueue`] directly).
26///
27/// ```rust,no_run
28/// use std::sync::Arc;
29///
30/// use boson_backend_mem::MemQueueBackend;
31/// use boson_core::JsonExecutionContextFactory;
32/// use boson_runtime::{configure, Boson};
33///
34/// # fn main() -> boson_core::Result<()> {
35/// let boson = Boson::builder()
36/// .queue_backend(Arc::new(MemQueueBackend::new()))
37/// .execution_context_factory(JsonExecutionContextFactory)
38/// .auto_registry()
39/// .build()?;
40/// configure(boson);
41/// # Ok(())
42/// # }
43/// ```
44#[derive(Default)]
45pub struct BosonBuilder {
46 pub(crate) queue_backend: Option<Arc<dyn QueueBackend>>,
47 pub(crate) execution_context_factory: Option<Arc<dyn ExecutionContextFactory>>,
48 pub(crate) ops_log: Option<Arc<dyn OpsLog>>,
49 pub(crate) registry: Option<Arc<TaskRegistry>>,
50 pub(crate) use_auto_registry: bool,
51 pub(crate) spawn_worker: bool,
52 pub(crate) worker_id: Option<String>,
53 pub(crate) lease_ttl_secs: Option<i64>,
54 pub(crate) runtime_label: Option<String>,
55 pub(crate) worker_pools: Option<Vec<String>>,
56 pub(crate) worker_poll_interval_ms: Option<u64>,
57 pub(crate) idempotency_mode: boson_core::IdempotencyMode,
58}
59
60impl BosonBuilder {
61 /// Worker identity for lease claims (default: `INSTANCE_ID` / `BOSON_WORKER_ID` / `boson-worker-1`).
62 #[must_use]
63 pub fn worker_id(mut self, worker_id: impl Into<String>) -> Self {
64 self.worker_id = Some(worker_id.into());
65 self
66 }
67
68 /// Run lease TTL in seconds; when `> 0`, claim path acquires leases before job claim.
69 #[must_use]
70 pub const fn lease_ttl_secs(mut self, secs: i64) -> Self {
71 self.lease_ttl_secs = Some(secs);
72 self
73 }
74
75 /// Telemetry/runtime label (default `embedded`; bench uses topology slug).
76 #[must_use]
77 pub fn runtime_label(mut self, label: impl Into<String>) -> Self {
78 self.runtime_label = Some(label.into());
79 self
80 }
81
82 /// Restrict this worker to specific pools (comma-free list). Unset = poll all queued pools.
83 ///
84 /// Also available via `BOSON_WORKER_POOLS=pool-a,pool-b`. Pin workers to disjoint pool sets
85 /// for shared-nothing scaling (each worker skips `distinct_pools_queued` fan-out).
86 #[must_use]
87 pub fn worker_pools(
88 mut self,
89 pools: impl IntoIterator<Item = impl Into<String>>,
90 ) -> Self {
91 self.worker_pools = Some(pools.into_iter().map(Into::into).collect());
92 self
93 }
94
95 /// Milliseconds between worker poll ticks (default 50; use 0 for bench drain tests).
96 #[must_use]
97 pub const fn worker_poll_interval_ms(mut self, ms: u64) -> Self {
98 self.worker_poll_interval_ms = Some(ms);
99 self
100 }
101
102 /// Default enqueue idempotency mode when a task does not override it.
103 ///
104 /// [`boson_core::IdempotencyMode::Lwt`] (default) is exactly-once under concurrent enqueue.
105 /// [`boson_core::IdempotencyMode::None`] is at-least-once and skips coordination (higher throughput).
106 #[must_use]
107 pub const fn idempotency_mode(mut self, mode: boson_core::IdempotencyMode) -> Self {
108 self.idempotency_mode = mode;
109 self
110 }
111
112 /// Inject queue persistence backend explicitly.
113 #[must_use]
114 pub fn queue_backend(mut self, backend: Arc<dyn QueueBackend>) -> Self {
115 self.queue_backend = Some(backend);
116 self
117 }
118
119 /// Use global [`QueueRouter`](boson_core::QueueRouter) default backend.
120 #[must_use]
121 pub fn queue_backend_from_global(mut self) -> Self {
122 self.queue_backend = None;
123 self
124 }
125
126 /// Identity factory for handler dispatch.
127 ///
128 /// Maps stored `actor_json` to `Box<dyn ExecutionContext>` when a worker runs a job. For examples
129 /// and smoke tests, pass [`JsonExecutionContextFactory`](boson_core::JsonExecutionContextFactory);
130 /// production apps typically implement [`ExecutionContextFactory`](boson_core::ExecutionContextFactory).
131 ///
132 /// # Example
133 ///
134 /// ```rust,no_run
135 /// use std::sync::Arc;
136 ///
137 /// use boson_backend_mem::MemQueueBackend;
138 /// use boson_core::JsonExecutionContextFactory;
139 /// use boson_runtime::Boson;
140 ///
141 /// # fn main() -> boson_core::Result<()> {
142 /// let _boson = Boson::builder()
143 /// .queue_backend(Arc::new(MemQueueBackend::new()))
144 /// .execution_context_factory(JsonExecutionContextFactory)
145 /// .build()?;
146 /// # Ok(())
147 /// # }
148 /// ```
149 #[must_use]
150 pub fn execution_context_factory(
151 mut self,
152 factory: impl ExecutionContextFactory + 'static,
153 ) -> Self {
154 self.execution_context_factory = Some(Arc::new(factory));
155 self
156 }
157
158 /// Identity factory from existing `Arc`.
159 #[must_use]
160 pub fn execution_context_factory_arc(
161 mut self,
162 factory: Arc<dyn ExecutionContextFactory>,
163 ) -> Self {
164 self.execution_context_factory = Some(factory);
165 self
166 }
167
168 /// Install ops log adapter (default [`boson_telemetry::NoOpsLog`]).
169 #[must_use]
170 pub fn ops_log(mut self, log: impl OpsLog + 'static) -> Self {
171 self.ops_log = Some(Arc::new(log));
172 self
173 }
174
175 /// Use console stderr ops log.
176 #[must_use]
177 pub fn ops_log_console(mut self) -> Self {
178 self.ops_log = Some(Arc::new(ConsoleOpsLog));
179 self
180 }
181
182 /// Use an existing task registry (e.g. testkit manual registration).
183 #[must_use]
184 pub fn registry(mut self, registry: Arc<TaskRegistry>) -> Self {
185 self.registry = Some(registry);
186 self.use_auto_registry = false;
187 self
188 }
189
190 /// Discover tasks registered via Quark inventory (for example `#[boson::task]`).
191 ///
192 /// The worker binary must link every crate that defines inventory submissions; otherwise
193 /// tasks defined in library crates will not appear in the registry. Add the task-owning crate
194 /// as a dependency of the binary (for example `use my_worker as _;`).
195 ///
196 /// # Example
197 ///
198 /// ```rust,no_run
199 /// use std::sync::Arc;
200 ///
201 /// use boson_backend_mem::MemQueueBackend;
202 /// use boson_core::{ExecutionContext, JsonExecutionContextFactory};
203 /// use boson_macros::task;
204 /// use boson_runtime::{configure, Boson};
205 ///
206 /// #[task(name = "ping")]
207 /// async fn ping(_ctx: Box<dyn ExecutionContext>) -> boson_core::Result<()> {
208 /// Ok(())
209 /// }
210 ///
211 /// // When handlers live in a library crate, link it from `main`:
212 /// // use my_worker as _;
213 ///
214 /// # fn main() -> boson_core::Result<()> {
215 /// let boson = Boson::builder()
216 /// .queue_backend(Arc::new(MemQueueBackend::new()))
217 /// .execution_context_factory(JsonExecutionContextFactory)
218 /// .auto_registry()
219 /// .build()?;
220 /// configure(boson);
221 /// # Ok(())
222 /// # }
223 /// ```
224 #[must_use]
225 pub const fn auto_registry(mut self) -> Self {
226 self.use_auto_registry = true;
227 self
228 }
229
230 /// Do not spawn background worker (caller drives [`ManualWorker`](crate::ManualWorker)).
231 #[must_use]
232 pub const fn without_worker(mut self) -> Self {
233 self.spawn_worker = false;
234 self
235 }
236}
237
238impl Boson {
239 /// Create a new builder.
240 #[must_use]
241 pub fn builder() -> BosonBuilder {
242 BosonBuilder {
243 spawn_worker: true,
244 ..Default::default()
245 }
246 }
247}