@drunkcoding/dknet-implementation-skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +22 -0
- package/.claude-plugin/plugin.json +38 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/agents/dknet-architect.md +49 -0
- package/agents/dknet-bdd-engineer.md +62 -0
- package/agents/dknet-implementer.md +75 -0
- package/package.json +52 -0
- package/plugin.json +26 -0
- package/skills/README.md +47 -0
- package/skills/dknet-auth-and-ownership/SKILL.md +418 -0
- package/skills/dknet-bdd-tests/SKILL.md +355 -0
- package/skills/dknet-bdd-tests/checklist.md +39 -0
- package/skills/dknet-crud/SKILL.md +483 -0
- package/skills/dknet-ddd-principles/SKILL.md +87 -0
- package/skills/dknet-docs/SKILL.md +296 -0
- package/skills/dknet-docs/checklist.md +58 -0
- package/skills/dknet-docs/templates/README-template.md +68 -0
- package/skills/dknet-docs/templates/api-reference-template.md +275 -0
- package/skills/dknet-docs/templates/architecture-template.md +166 -0
- package/skills/dknet-docs/templates/data-model-template.md +99 -0
- package/skills/dknet-docs/templates/events-template.md +155 -0
- package/skills/dknet-dto-mapping/SKILL.md +278 -0
- package/skills/dknet-efcore-config/SKILL.md +379 -0
- package/skills/dknet-endpoint/SKILL.md +458 -0
- package/skills/dknet-entity/SKILL.md +483 -0
- package/skills/dknet-feature/SKILL.md +139 -0
- package/skills/dknet-feature-lifecycle/SKILL.md +144 -0
- package/skills/dknet-feature-remove/SKILL.md +131 -0
- package/skills/dknet-messaging-events/SKILL.md +395 -0
- package/skills/dknet-package-adoption/SKILL.md +252 -0
- package/skills/dknet-platform-config/SKILL.md +342 -0
- package/skills/dknet-project-structure/SKILL.md +148 -0
- package/skills/dknet-queries-specs/SKILL.md +330 -0
- package/skills/dknet-scaffold/SKILL.md +209 -0
- package/skills/dknet-unit-tests/SKILL.md +382 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# {FeatureName} — Architecture
|
|
2
|
+
|
|
3
|
+
## Vertical Slice Overview
|
|
4
|
+
|
|
5
|
+
This feature follows the DKNet vertical slice architecture.
|
|
6
|
+
Each layer has a single, focused responsibility for this feature.
|
|
7
|
+
|
|
8
|
+
```mermaid
|
|
9
|
+
graph TD
|
|
10
|
+
Client["Client / Browser"]
|
|
11
|
+
|
|
12
|
+
subgraph API["Minimal.Api"]
|
|
13
|
+
EP["{EntityName}V1Endpoint\n(IEndpointConfig)"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph AppServices["Minimal.AppServices"]
|
|
17
|
+
REQ["Request Types\n(Create / Update / Delete\n+ custom actions)"]
|
|
18
|
+
VAL["Validators\n(FluentValidation)"]
|
|
19
|
+
HDL["Command Handlers\n(IHandler)"]
|
|
20
|
+
SPEC["Query Specs\n(Ardalis.Specification)"]
|
|
21
|
+
EVT["Domain Events\n({EntityName}CreatedEvent etc.)"]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
subgraph Domains["Minimal.Domains"]
|
|
25
|
+
ENT["{EntityName}\n(AggregateRoot)"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
subgraph Infra["Minimal.Infra"]
|
|
29
|
+
MAP["{EntityName}Mapper.cs\n(EF Core Config)"]
|
|
30
|
+
REPO["IRepositorySpec\n(EF Core + Spec)"]
|
|
31
|
+
EVH["Event Handlers\n(Azure Bus / In-Memory)"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
DB[("SQL Server")]
|
|
35
|
+
|
|
36
|
+
Client -->|HTTP| EP
|
|
37
|
+
EP -->|Message Bus| REQ
|
|
38
|
+
REQ --> VAL
|
|
39
|
+
VAL --> HDL
|
|
40
|
+
HDL -->|Query via Spec| SPEC
|
|
41
|
+
SPEC -->|Reads| REPO
|
|
42
|
+
HDL -->|Mutations| REPO
|
|
43
|
+
HDL -->|Publish| EVT
|
|
44
|
+
REPO --> MAP
|
|
45
|
+
MAP --> DB
|
|
46
|
+
EVT --> EVH
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Create {EntityName} — Sequence Diagram
|
|
50
|
+
|
|
51
|
+
```mermaid
|
|
52
|
+
sequenceDiagram
|
|
53
|
+
participant C as Client
|
|
54
|
+
participant EP as {EntityName}Endpoints
|
|
55
|
+
participant BUS as MessageBus
|
|
56
|
+
participant VAL as Validator
|
|
57
|
+
participant HDL as Create{EntityName}Handler
|
|
58
|
+
participant SPEC as Spec{EntityName}GetByEmail
|
|
59
|
+
participant REPO as IRepositorySpec
|
|
60
|
+
participant EVT as EventPublisher
|
|
61
|
+
|
|
62
|
+
C->>EP: POST /api/v1/{feature-route}
|
|
63
|
+
EP->>BUS: bus.Send(Create{EntityName}Request)
|
|
64
|
+
BUS->>VAL: Validate request
|
|
65
|
+
VAL-->>BUS: Valid ✓
|
|
66
|
+
|
|
67
|
+
BUS->>HDL: Handle(request)
|
|
68
|
+
HDL->>SPEC: new Spec{EntityName}GetByEmail(email)
|
|
69
|
+
HDL->>REPO: FirstOrDefaultAsync(spec)
|
|
70
|
+
REPO-->>HDL: null (no duplicate)
|
|
71
|
+
|
|
72
|
+
HDL->>HDL: new {EntityName}(...)
|
|
73
|
+
HDL->>REPO: AddAsync(entity)
|
|
74
|
+
HDL->>REPO: SaveChangesAsync()
|
|
75
|
+
REPO-->>HDL: OK
|
|
76
|
+
|
|
77
|
+
HDL->>EVT: PublishAsync({EntityName}CreatedEvent)
|
|
78
|
+
EVT-->>HDL: OK
|
|
79
|
+
|
|
80
|
+
HDL-->>BUS: Result<{EntityName}Dto>.Success(dto)
|
|
81
|
+
BUS-->>EP: {EntityName}Dto
|
|
82
|
+
EP-->>C: 201 Created + {EntityName}Dto
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Component Diagram
|
|
86
|
+
|
|
87
|
+
```mermaid
|
|
88
|
+
classDiagram
|
|
89
|
+
class {EntityName}V1Endpoint {
|
|
90
|
+
+int Version = 1
|
|
91
|
+
+string GroupEndpoint = "/{feature-route}"
|
|
92
|
+
+Map(RouteGroupBuilder group)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class Create{EntityName}Request {
|
|
96
|
+
+string Field1
|
|
97
|
+
+string Field2
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class Create{EntityName}CommandHandler {
|
|
101
|
+
-IMapper _mapper
|
|
102
|
+
-IRepositorySpec _repo
|
|
103
|
+
-IEventPublisher _eventPublisher
|
|
104
|
+
+Handle(request) Result~{EntityName}Dto~
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
class {EntityName} {
|
|
108
|
+
+Guid Id
|
|
109
|
+
+string Field1
|
|
110
|
+
+string Field2
|
|
111
|
+
+string Status
|
|
112
|
+
+Approve(reason)
|
|
113
|
+
+Reject(reason)
|
|
114
|
+
+Update(...)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class {EntityName}Mapper {
|
|
118
|
+
+Configure(EntityTypeBuilder)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
{EntityName}V1Endpoint ..> Create{EntityName}Request : maps request
|
|
122
|
+
Create{EntityName}CommandHandler --> {EntityName} : creates
|
|
123
|
+
Create{EntityName}CommandHandler --> IRepositorySpec : uses
|
|
124
|
+
{EntityName}Mapper --> {EntityName} : configures
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Status State Machine
|
|
128
|
+
|
|
129
|
+
> Remove this section if the entity has no status/approval workflow.
|
|
130
|
+
|
|
131
|
+
```mermaid
|
|
132
|
+
stateDiagram-v2
|
|
133
|
+
[*] --> Pending : {EntityName} Created
|
|
134
|
+
|
|
135
|
+
Pending --> Approved : approve() action
|
|
136
|
+
Pending --> Rejected : reject() action
|
|
137
|
+
|
|
138
|
+
Approved --> [*] : (soft-deleted)
|
|
139
|
+
Rejected --> [*] : (soft-deleted)
|
|
140
|
+
|
|
141
|
+
note right of Pending : Default status on creation
|
|
142
|
+
note right of Approved : Entity available for downstream use
|
|
143
|
+
note right of Rejected : Reason stored for audit trail
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Event Flow
|
|
147
|
+
|
|
148
|
+
```mermaid
|
|
149
|
+
graph LR
|
|
150
|
+
HDL["Create{EntityName}Handler"] -->|Publish| EVT["{EntityName}CreatedEvent"]
|
|
151
|
+
|
|
152
|
+
EVT --> MEM["In-Memory Bus Handler"]
|
|
153
|
+
EVT --> AZ["Azure Service Bus Handler\n(if AzureBus configured)"]
|
|
154
|
+
|
|
155
|
+
MEM -->|Side effects| LOG["Audit Log / Internal"]
|
|
156
|
+
AZ -->|Message to subscribers| EXT["External Systems\n(Notifications, Billing, etc.)"]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Layer Responsibilities
|
|
160
|
+
|
|
161
|
+
| Layer | Responsibility in this feature |
|
|
162
|
+
|-------|-------------------------------|
|
|
163
|
+
| `Minimal.Api` | Route mapping only; zero business logic |
|
|
164
|
+
| `Minimal.AppServices` | Command handling, validation, event publishing |
|
|
165
|
+
| `Minimal.Domains` | Entity state, domain rules, invariants |
|
|
166
|
+
| `Minimal.Infra` | Persistence, EF Core config, message bus setup |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# {FeatureName} — Data Model
|
|
2
|
+
|
|
3
|
+
## Entity Relationship Diagram
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
erDiagram
|
|
7
|
+
{ENTITY_TABLE_NAME} {
|
|
8
|
+
uniqueidentifier Id PK "Auto-generated GUID"
|
|
9
|
+
nvarchar(150) Field1 "Not null"
|
|
10
|
+
nvarchar(256) Field2 UK "Unique, not null"
|
|
11
|
+
nvarchar(50) Field3 "Nullable"
|
|
12
|
+
nvarchar(50) Status "Enum: Pending / Approved / Rejected"
|
|
13
|
+
bit IsDeleted "Soft delete flag; default false"
|
|
14
|
+
nvarchar(450) CreatedBy FK "Linked to user"
|
|
15
|
+
datetime2 CreatedAt "UTC, auto-set on insert"
|
|
16
|
+
nvarchar(450) UpdatedBy "Nullable"
|
|
17
|
+
datetime2 UpdatedAt "UTC, auto-updated"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
RELATED_TABLE {
|
|
21
|
+
uniqueidentifier Id PK
|
|
22
|
+
uniqueidentifier {EntityName}Id FK
|
|
23
|
+
nvarchar(100) SomeField
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
{ENTITY_TABLE_NAME} ||--o{ RELATED_TABLE : "has many"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> Remove the `RELATED_TABLE` block if there are no related entities.
|
|
30
|
+
> Add `UK` (unique key) annotation to columns with unique indexes.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Properties
|
|
35
|
+
|
|
36
|
+
| Property | C# Type | DB Column | Constraints |
|
|
37
|
+
|----------|---------|-----------|-------------|
|
|
38
|
+
| `Id` | `Guid` | `Id` (PK) | Not null, auto-generated |
|
|
39
|
+
| `Field1` | `string` | `Field1` | Not null, max {N} chars |
|
|
40
|
+
| `Field2` | `string` | `Field2` | Not null, max {N} chars, unique index |
|
|
41
|
+
| `Field3` | `string?` | `Field3` | Nullable, max {N} chars |
|
|
42
|
+
| `Status` | `string` | `Status` | Not null, max 50 chars |
|
|
43
|
+
| `IsDeleted` | `bool` | `IsDeleted` | Default: `false` |
|
|
44
|
+
| `CreatedBy` | `string` | `CreatedBy` | Not null (from the request's `[FromClaim(ClaimTypes.Name)]` `ByUser`) |
|
|
45
|
+
| `CreatedAt` | `DateTime` | `CreatedAt` | UTC, auto-set on insert |
|
|
46
|
+
| `UpdatedBy` | `string?` | `UpdatedBy` | Nullable |
|
|
47
|
+
| `UpdatedAt` | `DateTime?` | `UpdatedAt` | UTC, auto-updated |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## EF Core Mapping Configuration
|
|
52
|
+
|
|
53
|
+
Source: `ApiEndpoints/Minimal.Infra/Features/{EntityFolder}/Mappers/{EntityName}Mapper.cs`
|
|
54
|
+
|
|
55
|
+
Key mapping decisions:
|
|
56
|
+
|
|
57
|
+
| Configuration | Value | Reason |
|
|
58
|
+
|--------------|-------|--------|
|
|
59
|
+
| Table name | `{EntityTableName}` (schema: `dbo`) | Convention |
|
|
60
|
+
| Unique index | `{Field2}` | Business uniqueness constraint |
|
|
61
|
+
| Unique index | `{Field1}` (if applicable) | {Reason} |
|
|
62
|
+
| Global query filter | `IsDeleted == false` | Automatically excludes soft-deleted records from all queries |
|
|
63
|
+
| Column type | `datetime2(7)` for `UpdatedAt` | Sub-second precision for audit trail |
|
|
64
|
+
| Column precision | `nvarchar({N})` for `Field1` | Max length constraint from business rules |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Validation Rules
|
|
69
|
+
|
|
70
|
+
| Field | Rule | Enforcement |
|
|
71
|
+
|-------|------|-------------|
|
|
72
|
+
| `Field2` (e.g., Email) | Must be unique per entity | DB unique index + FluentValidation Spec check before insert |
|
|
73
|
+
| `Field1` (e.g., Name) | 2–{N} characters | FluentValidation in `Create{EntityName}RequestValidator` |
|
|
74
|
+
| `Field3` (e.g., Phone) | Valid phone format if provided | FluentValidation with `.When()` conditional |
|
|
75
|
+
| `Status` | Valid transition only (`Pending → Approved/Rejected`) | Domain entity method enforces valid transitions |
|
|
76
|
+
| Soft delete | `IsDeleted` flag | EF Core Global Query Filter excludes deleted from all queries |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Status Values
|
|
81
|
+
|
|
82
|
+
> Remove this section if the entity has no status field.
|
|
83
|
+
|
|
84
|
+
| Value | Meaning | Transitions to |
|
|
85
|
+
|-------|---------|----------------|
|
|
86
|
+
| `Pending` | Default on create; awaiting review | `Approved`, `Rejected` |
|
|
87
|
+
| `Approved` | Passed review; active | N/A (terminal for workflow) |
|
|
88
|
+
| `Rejected` | Failed review; inactive | N/A (terminal for workflow) |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Migration History
|
|
93
|
+
|
|
94
|
+
| Migration Name | Description |
|
|
95
|
+
|----------------|-------------|
|
|
96
|
+
| `Initial_{EntityName}` | Create initial `{EntityTableName}` table |
|
|
97
|
+
| `Add_{Field}_To_{EntityName}` | {Reason for the change} |
|
|
98
|
+
|
|
99
|
+
> Keep this table updated when running `dotnet ef migrations add <Name> -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj`.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# {FeatureName} — Domain Events
|
|
2
|
+
|
|
3
|
+
## Events Published
|
|
4
|
+
|
|
5
|
+
### {EntityName}CreatedEvent
|
|
6
|
+
|
|
7
|
+
Raised immediately after a new {entityName} is successfully created and persisted.
|
|
8
|
+
|
|
9
|
+
**Published by**: `Create{EntityName}CommandHandler`
|
|
10
|
+
|
|
11
|
+
**Payload**
|
|
12
|
+
|
|
13
|
+
```csharp
|
|
14
|
+
public sealed record {EntityName}CreatedEvent(Guid Id, string {KeyField});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| Property | Type | Description |
|
|
18
|
+
|----------|------|-------------|
|
|
19
|
+
| `Id` | `Guid` | The newly created {entityName}'s ID |
|
|
20
|
+
| `{KeyField}` | `string` | {Key identifying property, e.g., Name or Email} |
|
|
21
|
+
|
|
22
|
+
**Current Subscribers**
|
|
23
|
+
|
|
24
|
+
| Handler Class | Bus Type | Action |
|
|
25
|
+
|--------------|----------|--------|
|
|
26
|
+
| `{EntityName}CreatedFromMemoryHandler` | In-Memory | Internal audit / testing |
|
|
27
|
+
| _{Add external handlers as they are added}_ | Azure Service Bus | External notifications |
|
|
28
|
+
|
|
29
|
+
**Code Example** — How to subscribe:
|
|
30
|
+
|
|
31
|
+
```csharp
|
|
32
|
+
internal sealed class SendWelcomeNotificationHandler :
|
|
33
|
+
Fluents.EventsConsumers.IHandler<{EntityName}CreatedEvent>
|
|
34
|
+
{
|
|
35
|
+
public Task OnHandle(
|
|
36
|
+
{EntityName}CreatedEvent notification,
|
|
37
|
+
CancellationToken cancellationToken)
|
|
38
|
+
{
|
|
39
|
+
// TODO: implement side-effect logic
|
|
40
|
+
return Task.CompletedTask;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### {EntityName}StatusChangedEvent
|
|
48
|
+
|
|
49
|
+
> Add this section if status changes (approve/reject) publish events.
|
|
50
|
+
|
|
51
|
+
Raised when a {entityName}'s status changes (e.g., Approved or Rejected).
|
|
52
|
+
|
|
53
|
+
**Published by**: `Approve{EntityName}CommandHandler`, `Reject{EntityName}CommandHandler`
|
|
54
|
+
|
|
55
|
+
**Payload**
|
|
56
|
+
|
|
57
|
+
```csharp
|
|
58
|
+
public sealed record {EntityName}StatusChangedEvent(
|
|
59
|
+
Guid Id,
|
|
60
|
+
string OldStatus,
|
|
61
|
+
string NewStatus,
|
|
62
|
+
string? Reason);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| Property | Type | Description |
|
|
66
|
+
|----------|------|-------------|
|
|
67
|
+
| `Id` | `Guid` | The {entityName}'s ID |
|
|
68
|
+
| `OldStatus` | `string` | Status before change |
|
|
69
|
+
| `NewStatus` | `string` | Status after change |
|
|
70
|
+
| `Reason` | `string?` | Reason provided (e.g., rejection reason) |
|
|
71
|
+
|
|
72
|
+
**Current Subscribers**
|
|
73
|
+
|
|
74
|
+
| Handler Class | Bus Type | Action |
|
|
75
|
+
|--------------|----------|--------|
|
|
76
|
+
| _{None yet — add as needed}_ | — | — |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### {EntityName}DeletedEvent
|
|
81
|
+
|
|
82
|
+
> Add this section only if deletion publishes an event.
|
|
83
|
+
|
|
84
|
+
Raised when a {entityName} is soft-deleted.
|
|
85
|
+
|
|
86
|
+
**Published by**: `Delete{EntityName}CommandHandler`
|
|
87
|
+
|
|
88
|
+
**Payload**
|
|
89
|
+
|
|
90
|
+
```csharp
|
|
91
|
+
public sealed record {EntityName}DeletedEvent(Guid Id, string DeletedBy);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Events Consumed
|
|
97
|
+
|
|
98
|
+
> List any events from OTHER features that this feature subscribes to.
|
|
99
|
+
> If none, keep the statement below.
|
|
100
|
+
|
|
101
|
+
This feature does not currently consume events from other features.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Event Bus Configuration
|
|
106
|
+
|
|
107
|
+
Events are dispatched via the Minimal message bus with two bus types:
|
|
108
|
+
|
|
109
|
+
| Bus Type | When Active | Purpose |
|
|
110
|
+
|----------|-------------|---------|
|
|
111
|
+
| **In-Memory** | Always (all environments) | Same-process handlers; local side effects |
|
|
112
|
+
| **Azure Service Bus** | When `ConnectionStrings:AzureBus` is non-empty | Cross-service/process messaging |
|
|
113
|
+
|
|
114
|
+
See `ApiEndpoints/Minimal.Infra/Extensions/ServiceBusSetup.cs` for wiring configuration.
|
|
115
|
+
|
|
116
|
+
```mermaid
|
|
117
|
+
graph LR
|
|
118
|
+
HDL["Create{EntityName}Handler"]
|
|
119
|
+
EVT["{EntityName}CreatedEvent"]
|
|
120
|
+
MEM["In-Memory Bus"]
|
|
121
|
+
AZ["Azure Service Bus\n(if configured)"]
|
|
122
|
+
INTL["Internal Handlers\n(same process)"]
|
|
123
|
+
EXT["External Systems\n(notifications, billing...)"]
|
|
124
|
+
|
|
125
|
+
HDL -->|PublishAsync| EVT
|
|
126
|
+
EVT --> MEM
|
|
127
|
+
EVT --> AZ
|
|
128
|
+
MEM --> INTL
|
|
129
|
+
AZ --> EXT
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Adding a New Event Subscriber
|
|
135
|
+
|
|
136
|
+
To subscribe to an event from this feature:
|
|
137
|
+
|
|
138
|
+
1. Create a handler class in the consuming feature's AppServices project
|
|
139
|
+
2. Implement `Fluents.EventsConsumers.IHandler<TEvent>`
|
|
140
|
+
3. The message bus auto-discovers handlers via assembly scan — no manual registration needed
|
|
141
|
+
|
|
142
|
+
```csharp
|
|
143
|
+
// In the consuming feature's AppServices project:
|
|
144
|
+
internal sealed class On{EntityName}CreatedHandler :
|
|
145
|
+
Fluents.EventsConsumers.IHandler<{EntityName}CreatedEvent>
|
|
146
|
+
{
|
|
147
|
+
public Task OnHandle(
|
|
148
|
+
{EntityName}CreatedEvent notification,
|
|
149
|
+
CancellationToken cancellationToken)
|
|
150
|
+
{
|
|
151
|
+
// React to the event
|
|
152
|
+
return Task.CompletedTask;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|