Skip to content

Commit 862646f

Browse files
committed
Doc updates
1 parent 2dd1e66 commit 862646f

4 files changed

Lines changed: 155 additions & 2 deletions

File tree

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,40 @@ Blazingly fast, convention-based C# mediator powered by source generators and in
2020
- 🧪 **Easy testing** - Plain objects, no framework coupling
2121
- 🐛 **Superior debugging** - Short, simple call stacks
2222

23+
### Why Convention-Based?
24+
25+
Traditional mediator libraries force you into rigid interface contracts like `IRequestHandler<TRequest, TResponse>`. This means:
26+
27+
- One handler class per message type
28+
- Fixed method signatures
29+
- Always async (even for simple operations)
30+
- Lots of boilerplate
31+
32+
**Foundatio Mediator's conventions give you freedom:**
33+
34+
```csharp
35+
public class OrderHandler
36+
{
37+
// Sync handler - no async overhead
38+
public decimal Handle(CalculateTotal query) => query.Items.Sum(i => i.Price);
39+
40+
// Async with any DI parameters you need
41+
public async Task<Order> HandleAsync(GetOrder query, IOrderRepo repo, CancellationToken ct)
42+
=> await repo.FindAsync(query.Id, ct);
43+
44+
// Cascading: first element returned, rest auto-published as events
45+
public (Order order, OrderCreated evt) Handle(CreateOrder cmd) { /* ... */ }
46+
}
47+
48+
// Static handlers for maximum performance
49+
public static class MathHandler
50+
{
51+
public static int Handle(Add query) => query.A + query.B;
52+
}
53+
```
54+
55+
> **Prefer explicit interfaces?** Use `IHandler` marker interface or `[Handler]` attributes instead. See [Handler Conventions](https://mediator.foundatio.dev/guide/handler-conventions.html#explicit-handler-declaration).
56+
2357
## 🚀 Complete Example
2458

2559
### 1. Install & Register

docs/guide/handler-conventions.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22

33
Foundatio Mediator uses simple naming conventions to automatically discover handlers at compile time. This eliminates the need for interfaces, base classes, or manual registration while providing excellent compile-time validation.
44

5+
## Why Conventions?
6+
7+
Some developers initially feel that convention-based discovery is "too magical." But consider: **all abstractions are magic until you learn them.**
8+
9+
- ASP.NET discovers `*Controller` classes automatically
10+
- Entity Framework treats `Id` properties as primary keys
11+
- xUnit runs methods decorated with `[Fact]`
12+
- C# records generate equality, `ToString()`, and more from a single line
13+
14+
The real question isn't "is it magic?" but **"does the magic help or hurt?"**
15+
16+
Convention-based handlers provide tangible benefits over traditional interface-based approaches:
17+
18+
| Benefit | Convention-Based | Interface-Based |
19+
| ------- | ---------------- | --------------- |
20+
| **Multiple handlers per class** | ✅ Natural grouping | ❌ One interface per handler |
21+
| **Flexible method signatures** | ✅ Any params via DI | ❌ Fixed `Handle(TRequest, CancellationToken)` |
22+
| **Sync or async** | ✅ Return `void`, `T`, `Task<T>`, etc. | ❌ Must return `Task<T>` |
23+
| **Static handlers** | ✅ Zero allocation | ❌ Always instance-based |
24+
| **Cascading messages** | ✅ Tuple returns auto-publish | ❌ Manual `IPublisher` injection |
25+
| **Boilerplate** | ✅ Just a class + methods | ❌ Interface inheritance per handler |
26+
27+
**Prefer explicit declaration?** Use `IHandler` or `[Handler]` attributes. See [Explicit Handler Declaration](#explicit-handler-declaration).
28+
529
Alternatively, you can mark handlers explicitly using the `IHandler` marker interface or the `[Handler]` attribute. See [Explicit Handler Declaration](#explicit-handler-declaration) for details.
630

731
## Class Naming Conventions

docs/guide/what-is-foundatio-mediator.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ public static class OrderHandler
5252
}
5353
```
5454

55+
Unlike traditional mediator libraries that lock you into rigid interface contracts, conventions give you **unprecedented flexibility**:
56+
57+
- **Sync or async** - Return `void`, `Task`, `T`, `Task<T>`, `ValueTask<T>`
58+
- **Any parameters** - Message first, then any dependencies injected automatically
59+
- **Multiple handlers per class** - Group related operations naturally
60+
- **Static handlers** - Zero allocation for stateless operations
61+
- **Tuple returns** - Cascading messages for event-driven workflows
62+
63+
```csharp
64+
// All of these are valid handlers:
65+
public int Handle(AddNumbers q) => q.A + q.B; // Sync, returns value
66+
public void Handle(LogMessage cmd) => _log.Info(cmd.Text); // Fire-and-forget
67+
public async Task<User> HandleAsync(GetUser q, IRepo r) => ...; // Async with DI
68+
public (Order, OrderCreated) Handle(CreateOrder c) => ...; // Cascading events
69+
```
70+
5571
### 🔧 Seamless Dependency Injection
5672

5773
Full support for Microsoft.Extensions.DependencyInjection with both constructor and method injection:
@@ -134,7 +150,8 @@ public class LoggingMiddleware
134150
- **Simple CRUD** applications with minimal business logic
135151
- **Performance-critical** inner loops where even 10ns matters
136152
- **Legacy codebases** that can't adopt modern .NET features
137-
- **Teams resistant** to convention-based approaches
153+
154+
> **Note:** If you prefer explicit interfaces over conventions, Foundatio Mediator fully supports that too! Use `IHandler` marker interface or `[Handler]` attributes, and optionally disable conventional discovery. See [Handler Conventions](./handler-conventions#explicit-handler-declaration) for details.
138155
139156
## Next Steps
140157

docs/guide/why-foundatio-mediator.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,83 @@ public class UserHandler : IRequestHandler<GetUser, User>
4545
}
4646
```
4747

48+
### But Isn't Convention-Based Discovery "Too Magic"?
49+
50+
Some developers prefer explicit interfaces because they feel conventions are "too magical." Let's address this concern directly.
51+
52+
**All abstractions are "magic."** Consider what you already accept without question:
53+
54+
- **ASP.NET Controllers** - Classes ending in `Controller` are automatically discovered and routed
55+
- **Entity Framework** - Properties named `Id` become primary keys; navigation properties create relationships
56+
- **xUnit/NUnit** - Methods with `[Fact]` or `[Test]` attributes are discovered and executed
57+
- **Dependency Injection** - Constructor parameters are magically resolved from a container
58+
- **C# Records** - `record Person(string Name)` generates equality, `ToString()`, and deconstruction
59+
60+
The difference between "magic" and "convention" is simply **familiarity**. Once you learn a convention, it becomes invisible—you stop thinking about it.
61+
62+
**Why conventions are actually _better_ for mediator patterns:**
63+
64+
| Traditional Interfaces | Convention-Based |
65+
| ---------------------- | ---------------- |
66+
| Forces `IRequestHandler<TRequest, TResponse>` inheritance | Plain classes with any name ending in `Handler` |
67+
| One handler method per class (or awkward multiple interface inheritance) | Multiple handler methods per class, naturally grouped |
68+
| Fixed method signature: `Handle(TRequest, CancellationToken)` | Flexible signatures: sync/async, any parameters via DI |
69+
| Must return `Task<TResponse>` even for sync operations | Return `void`, `Task`, `T`, `Task<T>`, `Result<T>`, tuples |
70+
| Handler classes must be registered in DI | Auto-discovered at compile time |
71+
| Runtime dispatch via reflection | Compile-time interceptors for near-direct call performance |
72+
73+
**With conventions, you get unprecedented flexibility:**
74+
75+
```csharp
76+
// Sync handler - no async overhead
77+
public int Handle(AddNumbers query) => query.A + query.B;
78+
79+
// Async handler with injected dependencies
80+
public async Task<User> HandleAsync(GetUser query, IUserRepo repo, CancellationToken ct)
81+
=> await repo.FindAsync(query.Id, ct);
82+
83+
// Static handler - zero allocation, maximum performance
84+
public static class MathHandler
85+
{
86+
public static int Handle(Multiply query) => query.A * query.B;
87+
}
88+
89+
// Multiple handlers in one cohesive class
90+
public class OrderHandler
91+
{
92+
public Result<Order> Handle(CreateOrder cmd) { /* ... */ }
93+
public Result<Order> Handle(GetOrder query) { /* ... */ }
94+
public Result Handle(DeleteOrder cmd) { /* ... */ }
95+
}
96+
97+
// Cascading messages with tuple returns
98+
public (User user, UserCreated evt) Handle(CreateUser cmd)
99+
{
100+
var user = CreateUser(cmd);
101+
return (user, new UserCreated(user.Id)); // Event auto-published!
102+
}
103+
```
104+
105+
**If you still prefer explicit declaration**, that's supported too:
106+
107+
```csharp
108+
// Use IHandler marker interface
109+
public class MyService : IHandler
110+
{
111+
public User Handle(GetUser query) { /* ... */ }
112+
}
113+
114+
// Or use [Handler] attribute on class or method
115+
[Handler]
116+
public class AnotherService
117+
{
118+
public void Process(SendEmail cmd) { /* ... */ }
119+
}
120+
121+
// Disable conventions entirely via MSBuild
122+
// <MediatorDisableConventionalDiscovery>true</MediatorDisableConventionalDiscovery>
123+
```
124+
48125
### Rich Error Handling
49126

50127
Built-in `Result<T>` types eliminate exception-driven control flow:
@@ -221,9 +298,10 @@ Compare this to complex reflection-based call stacks in other libraries.
221298

222299
- **Legacy .NET Framework** projects (requires modern .NET)
223300
- **Simple CRUD applications** without complex business logic
224-
- **Teams preferring explicit interfaces** over conventions
225301
- **Existing MediatR codebases** where migration cost isn't justified
226302

303+
> **Note:** If your team prefers explicit interfaces over conventions, Foundatio Mediator supports that too! Use `IHandler` or `[Handler]` attributes and optionally disable conventional discovery entirely. See [Explicit Handler Declaration](./handler-conventions#explicit-handler-declaration).
304+
227305
## Migration from Other Libraries
228306

229307
### From MediatR

0 commit comments

Comments
 (0)