2025-12-18 13:05:40 +01:00
# API Levels: Builder DSL vs. Direct Instantiation
The framework supports two styles for constructing scenarios:
1. **High-level Builder DSL** (recommended): fluent helper methods (e.g. `.transactions_with(...)` )
2. **Low-level direct instantiation** : construct workload/expectation types explicitly, then attach them
Both styles produce the same runtime behavior because they ultimately call the same core builder APIs.
## High-Level Builder DSL (Recommended)
The DSL is implemented as extension traits (primarily `testing_framework_workflows::ScenarioBuilderExt` ) on the core scenario builder.
2025-12-20 09:16:23 +01:00
```rust,ignore
2025-12-18 13:05:40 +01:00
use std::time::Duration;
use testing_framework_core::scenario::ScenarioBuilder;
use testing_framework_workflows::ScenarioBuilderExt;
2026-01-26 16:36:51 +01:00
let plan = ScenarioBuilder::topology_with(|t| t.network_star().nodes(3))
2025-12-18 13:05:40 +01:00
.wallets(5)
.transactions_with(|txs| txs.rate(5).users(3))
.expect_consensus_liveness()
.with_run_duration(Duration::from_secs(60))
.build();
```
**When to use:**
- Most test code (smoke, regression, CI)
- When you want sensible defaults and minimal boilerplate
## Low-Level Direct Instantiation
Direct instantiation gives you explicit control over the concrete types you attach:
2025-12-20 09:16:23 +01:00
```rust,ignore
2025-12-18 13:05:40 +01:00
use std::{
2026-01-26 16:36:51 +01:00
num::NonZeroUsize,
2025-12-18 13:05:40 +01:00
time::Duration,
};
use testing_framework_core::scenario::ScenarioBuilder;
use testing_framework_workflows::{
expectations::ConsensusLiveness,
2026-01-26 16:36:51 +01:00
workloads::transaction,
2025-12-18 13:05:40 +01:00
};
let tx_workload = transaction::Workload::with_rate(5)
.expect("transaction rate must be non-zero")
.with_user_limit(NonZeroUsize::new(3));
2026-01-26 16:36:51 +01:00
let plan = ScenarioBuilder::topology_with(|t| t.network_star().nodes(3))
2025-12-18 13:05:40 +01:00
.wallets(5)
.with_workload(tx_workload)
.with_expectation(ConsensusLiveness::default())
.with_run_duration(Duration::from_secs(60))
.build();
```
**When to use:**
- Custom workload/expectation implementations
- Reusing preconfigured workload instances across multiple scenarios
- Debugging / exploring the underlying workload types
## Method Correspondence
| High-Level DSL | Low-Level Direct |
|----------------|------------------|
| `.transactions_with(\|txs\| txs.rate(5).users(3))` | `.with_workload(transaction::Workload::with_rate(5).expect(...).with_user_limit(...))` |
| `.expect_consensus_liveness()` | `.with_expectation(ConsensusLiveness::default())` |
## Bundled Expectations (Important)
Workloads can bundle expectations by implementing `Workload::expectations()` .
These bundled expectations are attached automatically whenever you call `.with_workload(...)` (including when you use the DSL), because the core builder expands workload expectations during attachment.
## Mixing Both Styles
Mixing is common: use the DSL for built-ins, and direct instantiation for custom pieces.
2025-12-20 09:16:23 +01:00
```rust,ignore
2025-12-18 13:05:40 +01:00
use std::time::Duration;
use testing_framework_core::scenario::ScenarioBuilder;
2025-12-18 19:47:29 +01:00
use testing_framework_workflows::{ScenarioBuilderExt, workloads::transaction};
2025-12-18 13:05:40 +01:00
2025-12-18 19:47:29 +01:00
let tx_workload = transaction::Workload::with_rate(5)
.expect("transaction rate must be non-zero");
2025-12-18 13:05:40 +01:00
2026-01-26 16:36:51 +01:00
let plan = ScenarioBuilder::topology_with(|t| t.network_star().nodes(3))
2025-12-18 19:47:29 +01:00
.wallets(5)
.with_workload(tx_workload) // direct instantiation
.expect_consensus_liveness() // DSL
2025-12-18 13:05:40 +01:00
.with_run_duration(Duration::from_secs(60))
.build();
```
## Implementation Detail (How the DSL Works)
The DSL methods are thin wrappers. For example:
2025-12-18 19:47:29 +01:00
`builder.transactions_with(|txs| txs.rate(5).users(3))`
2025-12-18 13:05:40 +01:00
is roughly equivalent to:
2025-12-18 19:47:29 +01:00
`builder.transactions().rate(5).users(3).apply()`
2025-12-18 13:05:40 +01:00
## Troubleshooting
**DSL method not found**
- Ensure the extension traits are in scope, e.g. `use testing_framework_workflows::ScenarioBuilderExt;`
- Cross-check method names in [Builder API Quick Reference ](dsl-cheat-sheet.md )
## See Also
- [Builder API Quick Reference ](dsl-cheat-sheet.md )
- [Example: New Workload & Expectation (Rust) ](custom-workload-example.md )
- [Extending the Framework ](extending.md )