Skip to content

Commit 64ad76e

Browse files
author
developerworks
committed
Update child spec builder implementation and docs
- Improve child spec builder implementation and test coverage - Align example child spec builder usage with implementation - Update English and Chinese child spec builder documentation
1 parent 07c483e commit 64ad76e

7 files changed

Lines changed: 490 additions & 115 deletions

File tree

examples/child_spec_builder.rs

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ fn main() -> Result<(), SupervisorError> {
2121

2222
// Build and print a worker child with fluent setters.
2323
demo_worker_builder()?;
24-
// Build and print a one-shot job override on a worker base.
24+
// Build and print a service child with service role defaults.
25+
demo_service_builder()?;
26+
// Build and print a job child with job role defaults.
2527
demo_job_builder()?;
2628
// Build and print a nested supervisor child.
2729
demo_supervisor_builder()?;
28-
// Build and print a sidecar from the minimal `new` entry point.
30+
// Build and print a sidecar child with sidecar role defaults.
31+
demo_sidecar_builder()?;
32+
// Build and print a worker from the minimal `new` entry point.
2933
demo_minimal_new_builder()?;
3034
// Show that `build` rejects invalid sidecar combinations.
3135
demo_build_failure()?;
@@ -61,25 +65,47 @@ fn demo_worker_builder() -> Result<(), SupervisorError> {
6165
Ok(())
6266
}
6367

64-
/// Builds a one-shot job child by overriding role and restart policy on a worker base.
68+
/// Builds a service child with the `service` entry point.
69+
fn demo_service_builder() -> Result<(), SupervisorError> {
70+
// Create a no-op async worker factory.
71+
let factory = Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded }));
72+
73+
// Build a service child with service role defaults.
74+
let spec = ChildSpecBuilder::service(
75+
ChildId::new("api-service"),
76+
"API Service",
77+
TaskKind::AsyncWorker,
78+
factory,
79+
)
80+
.tag("service")
81+
.group("api")
82+
.build()?;
83+
84+
// Print the service summary.
85+
println!("--- service entry ---");
86+
print_spec_summary(&spec);
87+
println!();
88+
Ok(())
89+
}
90+
91+
/// Builds a one-shot job child with the `job` entry point.
6592
fn demo_job_builder() -> Result<(), SupervisorError> {
6693
// Create a no-op async worker factory.
6794
let factory = Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded }));
6895

69-
// Build a job child by overriding role and restart policy on a worker base.
70-
let spec = ChildSpecBuilder::worker(
96+
// Build a job child with job role defaults and a temporary restart policy.
97+
let spec = ChildSpecBuilder::job(
7198
ChildId::new("nightly-export"),
7299
"Nightly Export",
73100
TaskKind::AsyncWorker,
74101
factory,
75102
)
76-
.task_role(TaskRole::Job)
77103
.restart_policy(RestartPolicy::Temporary)
78104
.tag("job")
79105
.build()?;
80106

81107
// Print the job summary.
82-
println!("--- job override on worker base ---");
108+
println!("--- job entry ---");
83109
print_spec_summary(&spec);
84110
println!();
85111
Ok(())
@@ -99,27 +125,47 @@ fn demo_supervisor_builder() -> Result<(), SupervisorError> {
99125
Ok(())
100126
}
101127

102-
/// Builds a sidecar from the minimal `new` entry point plus required fields.
103-
fn demo_minimal_new_builder() -> Result<(), SupervisorError> {
128+
/// Builds a sidecar child with the `sidecar` entry point.
129+
fn demo_sidecar_builder() -> Result<(), SupervisorError> {
104130
// Identify the primary child that the sidecar follows.
105131
let primary_id = ChildId::new("api");
106132
// Create a no-op async worker factory.
107133
let factory = Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded }));
108134

109-
// Build a sidecar from the minimal `new` entry point plus required fields.
110-
let spec = ChildSpecBuilder::new(ChildId::new("metrics-sidecar"), "Metrics Sidecar")
135+
// Build a sidecar whose primary child is also added as a dependency.
136+
let spec = ChildSpecBuilder::sidecar(
137+
ChildId::new("metrics-sidecar"),
138+
"Metrics Sidecar",
139+
TaskKind::AsyncWorker,
140+
factory,
141+
SidecarConfig::new(primary_id, false),
142+
)
143+
.tag("sidecar")
144+
.build()?;
145+
146+
// Print the sidecar summary including dependency ids.
147+
println!("--- sidecar entry ---");
148+
print_spec_summary(&spec);
149+
println!(" dependencies = {:?}", spec.dependencies);
150+
println!();
151+
Ok(())
152+
}
153+
154+
/// Builds a worker from the minimal `new` entry point plus required fields.
155+
fn demo_minimal_new_builder() -> Result<(), SupervisorError> {
156+
// Create a no-op async worker factory.
157+
let factory = Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded }));
158+
159+
// Build a worker from the minimal `new` entry point plus required fields.
160+
let spec = ChildSpecBuilder::new(ChildId::new("custom-worker"), "Custom Worker")
111161
.kind(TaskKind::AsyncWorker)
112162
.factory(factory)
113-
.task_role(TaskRole::Sidecar)
114-
.sidecar_config(SidecarConfig::new(primary_id.clone(), false))
115-
.dependency(primary_id)
116-
.tag("sidecar")
163+
.tag("custom")
117164
.build()?;
118165

119-
// Print the sidecar summary including dependency ids.
166+
// Print the custom worker summary.
120167
println!("--- new entry + setters ---");
121168
print_spec_summary(&spec);
122-
println!(" dependencies = {:?}", spec.dependencies);
123169
println!();
124170
Ok(())
125171
}

manual/en/child-spec-builder.md

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ Legacy code may still mutate fields after construction. New code should prefer t
3131
| Method | Purpose | Default highlights |
3232
| --- | --- | --- |
3333
| `worker(id, name, kind, factory)` | Async or blocking worker | Matches `ChildSpec::worker`: `Transient` restart, `Critical` criticality, `TaskRole::Worker`, and so on |
34+
| `service(id, name, kind, factory)` | Long-running service | Based on worker defaults: `TaskRole::Service`, `Critical` criticality |
35+
| `job(id, name, kind, factory)` | Finite job | Based on worker defaults: `TaskRole::Job`, `Optional` criticality |
36+
| `sidecar(id, name, kind, factory, sidecar_config)` | Sidecar attached to a primary child | Based on worker defaults: `TaskRole::Sidecar`, writes `sidecar_config`, and automatically adds the primary child dependency |
3437
| `supervisor(id, name)` | Nested supervisor | `kind = Supervisor`, `factory = None`, `task_role = Supervisor`, `criticality = Critical` |
3538
| `new(id, name)` | Minimal skeleton | Sets only `id` / `name` plus baseline policies; caller must add `kind` and, for workers, `factory` |
3639

@@ -40,6 +43,8 @@ Legacy code may still mutate fields after construction. New code should prefer t
4043
| --- | --- |
4144
| `build()` | Takes the inner `ChildSpec`, calls `validate()`, returns `Ok(spec)` or `SupervisorError` |
4245

46+
All entry methods and setters return `ChildSpecBuilder`, which means construction is still in progress. Only `build()` consumes the builder and returns the final `ChildSpec`.
47+
4348
There is **no** `build_validated()`. Validation is always performed inside `build()`.
4449

4550
`ChildSpec::worker(...)` also returns `Result<ChildSpec, SupervisorError>` via `ChildSpecBuilder::worker(...).build()`.
@@ -87,31 +92,45 @@ Naming convention: plural fields use `dependencies(...)`, `tags(...)`; singular
8792

8893
## Common combinations
8994

90-
### Job override
95+
### Service
96+
97+
Long-running services should prefer `service(...)`; callers do not need to set `TaskRole::Service` by hand:
98+
99+
```rust
100+
ChildSpecBuilder::service(id, "API Service", TaskKind::AsyncWorker, factory)
101+
.tag("service")
102+
.build()?;
103+
```
104+
105+
### Job
91106

92-
Override `task_role` and `restart_policy` on a worker base:
107+
Finite work should prefer `job(...)`. You can still override `restart_policy` for one-shot behavior:
93108

94109
```rust
95-
ChildSpecBuilder::worker(id, "Nightly Export", TaskKind::AsyncWorker, factory)
96-
.task_role(TaskRole::Job)
110+
ChildSpecBuilder::job(id, "Nightly Export", TaskKind::AsyncWorker, factory)
97111
.restart_policy(RestartPolicy::Temporary)
98112
.build()?;
99113
```
100114

101115
### Sidecar
102116

103-
When `task_role = Sidecar`, you must also set `sidecar_config`, or `build()` validation fails:
117+
Sidecars attached to a primary child should prefer `sidecar(...)`. This entry writes `sidecar_config` and automatically adds the primary child dependency:
104118

105119
```rust
106-
use rust_supervisor::policy::task_role_defaults::{SidecarConfig, TaskRole};
107-
108-
ChildSpecBuilder::worker(id, "Metrics Sidecar", TaskKind::AsyncWorker, factory)
109-
.task_role(TaskRole::Sidecar)
110-
.sidecar_config(SidecarConfig::new(primary_id.clone(), false))
111-
.dependency(primary_id)
112-
.build()?;
120+
use rust_supervisor::policy::task_role_defaults::SidecarConfig;
121+
122+
ChildSpecBuilder::sidecar(
123+
id,
124+
"Metrics Sidecar",
125+
TaskKind::AsyncWorker,
126+
factory,
127+
SidecarConfig::new(primary_id.clone(), false),
128+
)
129+
.build()?;
113130
```
114131

132+
If you still configure `task_role = Sidecar` manually with setters, you must also set `sidecar_config`, or `build()` validation fails.
133+
115134
### Worker from `new()`
116135

117136
```rust
@@ -124,7 +143,7 @@ ChildSpecBuilder::new(ChildId::new("custom"), "custom")
124143
## Data flow (short)
125144

126145
```text
127-
ChildSpecBuilder::worker / supervisor / new
146+
ChildSpecBuilder::worker / service / job / sidecar / supervisor / new
128147
|
129148
v
130149
fluent setters (policy, role, deps, env, ...)
@@ -144,7 +163,7 @@ Runnable demo:
144163
cargo run --example child_spec_builder
145164
```
146165

147-
Source: [`examples/child_spec_builder.rs`](../../examples/child_spec_builder.rs). Covers worker, job override, supervisor, `new()` + sidecar, and an intentionally invalid sidecar combination.
166+
Source: [`examples/child_spec_builder.rs`](../../examples/child_spec_builder.rs). Covers worker, service, job, sidecar, supervisor, the `new()` path, and an intentionally invalid sidecar combination.
148167

149168
## Tests and regression
150169

@@ -154,6 +173,9 @@ External tests: [`src/spec/tests/child_builder_test.rs`](../../src/spec/tests/ch
154173
| --- | --- |
155174
| `worker_builder_matches_child_spec_worker_defaults` | Builder output matches `ChildSpec::worker` field-for-field |
156175
| `supervisor_builder_produces_valid_supervisor_child` | Supervisor entry has no factory and validates |
176+
| `service_builder_sets_service_role` | Service entry sets `TaskRole::Service` and `Critical` criticality |
177+
| `job_builder_sets_job_role_and_optional_criticality` | Job entry sets `TaskRole::Job` and `Optional` criticality |
178+
| `sidecar_builder_sets_sidecar_role_binding_and_dependency` | Sidecar entry sets the binding and automatically adds the primary child dependency |
157179
| `builder_setters_apply_expected_fields` | Sidecar, dependency, tag, and related setters |
158180
| `build_rejects_invalid_sidecar_combination` | Missing `sidecar_config` makes `build()` fail |
159181
| `new_builder_can_build_valid_worker_with_factory` | `new()` path works after required fields are set |

manual/en/child-spec.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ Entry methods:
9191
| Method | Purpose |
9292
| ----------------------------------- | ----------------------------------------------------------------------- |
9393
| `ChildSpecBuilder::worker(...)` | Async or blocking worker; defaults match `ChildSpec::worker` |
94+
| `ChildSpecBuilder::service(...)` | Long-running service; sets `TaskRole::Service` |
95+
| `ChildSpecBuilder::job(...)` | Finite job; sets `TaskRole::Job` |
96+
| `ChildSpecBuilder::sidecar(...)` | Sidecar; sets sidecar binding and the primary child dependency |
9497
| `ChildSpecBuilder::supervisor(...)` | Nested supervisor; no factory |
9598
| `ChildSpecBuilder::new(...)` | Minimal skeleton; caller must set `kind` and, for workers, `factory` |
9699

manual/zh/child-spec-builder.md

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ use rust_supervisor::spec::child_builder::ChildSpecBuilder;
3131
| 方法 | 用途 | 默认值要点 |
3232
|---|---|---|
3333
| `worker(id, name, kind, factory)` | 异步或阻塞 worker |`ChildSpec::worker` 一致: `Transient` 重启, `Critical` 关键性, `TaskRole::Worker`|
34+
| `service(id, name, kind, factory)` | 常驻 service(服务) | 基于 worker 默认值: `TaskRole::Service`, `Critical` 关键性 |
35+
| `job(id, name, kind, factory)` | 有限生命周期 job(一次性任务) | 基于 worker 默认值: `TaskRole::Job`, `Optional` 关键性 |
36+
| `sidecar(id, name, kind, factory, sidecar_config)` | 跟随 primary child(主子任务) 的 sidecar(边车) | 基于 worker 默认值: `TaskRole::Sidecar`, 写入 `sidecar_config`, 并自动加入 primary child 依赖 |
3437
| `supervisor(id, name)` | 嵌套 supervisor(监督器) | `kind = Supervisor`, `factory = None`, `task_role = Supervisor`, `criticality = Critical` |
3538
| `new(id, name)` | 最小骨架 | 仅填 `id` / `name` 与 baseline(基线) 策略; 调用方需自行补 `kind`, 以及 worker 所需的 `factory` |
3639

@@ -40,6 +43,8 @@ use rust_supervisor::spec::child_builder::ChildSpecBuilder;
4043
|---|---|
4144
| `build()` | 取出内部 `ChildSpec`, 调用 `validate()`, 成功返回 `Ok(spec)`, 失败返回 `SupervisorError` |
4245

46+
所有入口方法和 setter(设置器) 都返回 `ChildSpecBuilder`, 只表示"还在构造中". 只有 `build()` 会消费 builder(构建器), 并返回最终 `ChildSpec`.
47+
4348
**没有** `build_validated()`. 校验统一在 `build()` 内完成.
4449

4550
`ChildSpec::worker(...)` 同样返回 `Result<ChildSpec, SupervisorError>`, 实现为 `ChildSpecBuilder::worker(...).build()`.
@@ -87,31 +92,45 @@ fn build_worker() -> Result<ChildSpec, SupervisorError> {
8792

8893
## 常见组合示例
8994

90-
### Job (一次性任务) 覆盖
95+
### Service (服务)
96+
97+
常驻服务优先使用 `service(...)`, 不需要手动设置 `TaskRole::Service`:
98+
99+
```rust
100+
ChildSpecBuilder::service(id, "API Service", TaskKind::AsyncWorker, factory)
101+
.tag("service")
102+
.build()?;
103+
```
104+
105+
### Job (一次性任务)
91106

92-
在 worker 基座上改 `task_role` `restart_policy`:
107+
有限生命周期任务优先使用 `job(...)`. 如果需要一次运行后停止, 可以继续覆盖 `restart_policy`:
93108

94109
```rust
95-
ChildSpecBuilder::worker(id, "Nightly Export", TaskKind::AsyncWorker, factory)
96-
.task_role(TaskRole::Job)
110+
ChildSpecBuilder::job(id, "Nightly Export", TaskKind::AsyncWorker, factory)
97111
.restart_policy(RestartPolicy::Temporary)
98112
.build()?;
99113
```
100114

101115
### Sidecar (边车)
102116

103-
`task_role = Sidecar` 时必须同时设置 `sidecar_config`, 否则 `build()` 校验失败:
117+
跟随主任务的边车优先使用 `sidecar(...)`. 这个入口会写入 `sidecar_config`, 并把 primary child 自动加入依赖:
104118

105119
```rust
106-
use rust_supervisor::policy::task_role_defaults::{SidecarConfig, TaskRole};
107-
108-
ChildSpecBuilder::worker(id, "Metrics Sidecar", TaskKind::AsyncWorker, factory)
109-
.task_role(TaskRole::Sidecar)
110-
.sidecar_config(SidecarConfig::new(primary_id.clone(), false))
111-
.dependency(primary_id)
112-
.build()?;
120+
use rust_supervisor::policy::task_role_defaults::SidecarConfig;
121+
122+
ChildSpecBuilder::sidecar(
123+
id,
124+
"Metrics Sidecar",
125+
TaskKind::AsyncWorker,
126+
factory,
127+
SidecarConfig::new(primary_id.clone(), false),
128+
)
129+
.build()?;
113130
```
114131

132+
如果仍然用 setter(设置器) 手动设置 `task_role = Sidecar`, 就必须同时设置 `sidecar_config`, 否则 `build()` 校验失败.
133+
115134
### `new()` 拼出 worker
116135

117136
```rust
@@ -124,7 +143,7 @@ ChildSpecBuilder::new(ChildId::new("custom"), "custom")
124143
## 数据流 (简图)
125144

126145
```text
127-
ChildSpecBuilder::worker / supervisor / new
146+
ChildSpecBuilder::worker / service / job / sidecar / supervisor / new
128147
|
129148
v
130149
链式 setter (policy, role, deps, env, ...)
@@ -144,7 +163,7 @@ ChildSpecBuilder::worker / supervisor / new
144163
cargo run --example child_spec_builder
145164
```
146165

147-
源码: [`examples/child_spec_builder.rs`](../../examples/child_spec_builder.rs). 覆盖 worker, job 覆盖, supervisor, `new()` + sidecar, 以及故意失败的 sidecar 组合.
166+
源码: [`examples/child_spec_builder.rs`](../../examples/child_spec_builder.rs). 覆盖 worker, service, job, sidecar, supervisor, `new()` 路径, 以及故意失败的 sidecar 组合.
148167

149168
## 测试与回归
150169

@@ -154,6 +173,9 @@ cargo run --example child_spec_builder
154173
|---|---|
155174
| `worker_builder_matches_child_spec_worker_defaults` | Builder 与 `ChildSpec::worker` 字段一致 |
156175
| `supervisor_builder_produces_valid_supervisor_child` | supervisor 入口无 factory 且可校验 |
176+
| `service_builder_sets_service_role` | service 入口设置 `TaskRole::Service``Critical` 关键性 |
177+
| `job_builder_sets_job_role_and_optional_criticality` | job 入口设置 `TaskRole::Job``Optional` 关键性 |
178+
| `sidecar_builder_sets_sidecar_role_binding_and_dependency` | sidecar 入口设置绑定并自动加入 primary child 依赖 |
157179
| `builder_setters_apply_expected_fields` | sidecar, dependency, tag 等 setter |
158180
| `build_rejects_invalid_sidecar_combination` |`sidecar_config``build()` 失败 |
159181
| `new_builder_can_build_valid_worker_with_factory` | `new()` 路径补全后可构建 |

manual/zh/child-spec.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ let spec = ChildSpecBuilder::worker(
8989
| 方法 | 用途 |
9090
| ----------------------------------- | ---------------------------------------------------- |
9191
| `ChildSpecBuilder::worker(...)` | 异步或阻塞 worker, 默认值与 `ChildSpec::worker` 一致 |
92+
| `ChildSpecBuilder::service(...)` | 常驻 service(服务), 自动设置 `TaskRole::Service` |
93+
| `ChildSpecBuilder::job(...)` | 有限生命周期 job(一次性任务), 自动设置 `TaskRole::Job` |
94+
| `ChildSpecBuilder::sidecar(...)` | sidecar(边车), 自动设置绑定和主子任务依赖 |
9295
| `ChildSpecBuilder::supervisor(...)` | 嵌套 supervisor, 无 factory |
9396
| `ChildSpecBuilder::new(...)` | 最小骨架, 需自行补 `kind``factory` |
9497

0 commit comments

Comments
 (0)