|
| 1 | +# FunctionalStateMachine.CommandRunner |
| 2 | + |
| 3 | +`FunctionalStateMachine.CommandRunner` is an optional DI layer for executing commands produced by a state machine. It discovers and registers `ICommandRunner<TCommand>`/`IAsyncCommandRunner<TCommand>` implementations and provides dispatchers for running commands. |
| 4 | + |
| 5 | +## Quick start |
| 6 | + |
| 7 | +### 1) Define your command hierarchy |
| 8 | + |
| 9 | +```csharp |
| 10 | +public abstract record UserCommand |
| 11 | +{ |
| 12 | + public sealed record SendWelcomeEmail(Guid UserId) : UserCommand; |
| 13 | +} |
| 14 | +``` |
| 15 | + |
| 16 | +### 2) Implement runners |
| 17 | + |
| 18 | +```csharp |
| 19 | +public sealed class SendWelcomeEmailRunner : ICommandRunner<UserCommand.SendWelcomeEmail> |
| 20 | +{ |
| 21 | + public void Run(UserCommand.SendWelcomeEmail command) |
| 22 | + { |
| 23 | + // send email |
| 24 | + } |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +### 3) Register and dispatch |
| 29 | + |
| 30 | +```csharp |
| 31 | +var services = new ServiceCollection() |
| 32 | + .AddCommandRunners<UserCommand>(); |
| 33 | + |
| 34 | +var dispatcher = services |
| 35 | + .BuildServiceProvider() |
| 36 | + .GetRequiredService<ICommandDispatcher<UserCommand>>(); |
| 37 | + |
| 38 | +dispatcher.Run(new UserCommand.SendWelcomeEmail(Guid.NewGuid())); |
| 39 | +``` |
| 40 | + |
| 41 | +## Async runners |
| 42 | + |
| 43 | +If any runner implements `IAsyncCommandRunner<TCommand>`, resolve `IAsyncCommandDispatcher<TCommand>` and call `RunAsync`: |
| 44 | + |
| 45 | +```csharp |
| 46 | +public sealed class ChargeCardRunner : IAsyncCommandRunner<BillingCommand.ChargeCard> |
| 47 | +{ |
| 48 | + public Task RunAsync(BillingCommand.ChargeCard command) |
| 49 | + => Task.CompletedTask; |
| 50 | +} |
| 51 | + |
| 52 | +services.AddCommandRunners<BillingCommand>(); |
| 53 | +var dispatcher = services.BuildServiceProvider() |
| 54 | + .GetRequiredService<IAsyncCommandDispatcher<BillingCommand>>(); |
| 55 | + |
| 56 | +await dispatcher.RunAsync(new BillingCommand.ChargeCard(42m)); |
| 57 | +``` |
| 58 | + |
| 59 | +## Options |
| 60 | + |
| 61 | +```csharp |
| 62 | +services.AddCommandRunners<UserCommand>(new CommandRunnerOptions |
| 63 | +{ |
| 64 | + MissingBehavior = CommandRunnerMissingBehavior.NoOp, |
| 65 | + Lifetime = ServiceLifetime.Scoped, |
| 66 | + AutoRegisterRunners = false |
| 67 | +}); |
| 68 | +``` |
| 69 | +### Missing Behavior |
| 70 | +- `MissingBehavior`: Define how to deal with missing command runners |
| 71 | + - `Throw` - `[default]` throw when a command has no runner. |
| 72 | + - `NoOp` - executing a command that has no runner does nothing. |
| 73 | + |
| 74 | +### Lifetime |
| 75 | +- `Lifetime`: Sets the lifetime that the command runners are registered. |
| 76 | + - `Transient` - `[default]` |
| 77 | + - `Singleton` |
| 78 | + - `Scoped` |
| 79 | + |
| 80 | +### Suppress Autoregistration |
| 81 | +- `AutoRegisterRunners`: Suppress the auto-registration |
| 82 | + - `true` : `[default]` |
| 83 | + - `false` : set to `false` to register runners manually. |
| 84 | + |
| 85 | +## Notes |
| 86 | + |
| 87 | +- The dispatcher is source-generated; ensure `FunctionalStateMachine.CommandRunner.Generator` is referenced as an analyzer in projects that call `AddCommandRunners<T>()`. |
| 88 | +- See `docs/command-runners.md` for full guidance and examples. |
0 commit comments