Skip to content

Commit 042fe0f

Browse files
authored
✨ Migrate from Minimal APIs to FastEndpoints (#198)
This pull request introduces FastEndpoints as the primary API framework for the SSW.VerticalSliceArchitecture template, replacing the previous Minimal APIs approach. It updates dependencies, refactors infrastructure and endpoint code to align with FastEndpoints 7.0, and documents the architectural decision and build status. The changes ensure improved structure, maintainability, and developer productivity while maintaining compatibility with existing domain patterns and event handling via MediatR. **Architectural Documentation and Guidelines:** * Added comprehensive Copilot instructions for creating ADRs in `.github/instructions/adrs.instructions.md`, detailing file naming, structure, content guidelines, and project-specific conventions. * Added a new ADR (`docs/adrs/20251018-api-use-fastendpoints-instead-of-minimal-apis.md`) documenting the decision to adopt FastEndpoints over Minimal APIs, including context, drivers, options, outcomes, and consequences. **Dependency and Infrastructure Updates:** * Updated `Directory.Packages.props` to add FastEndpoints (`7.0.0`) and FastEndpoints.Swagger as dependencies, ensuring the project uses the latest version and supports OpenAPI documentation. [[1]](diffhunk://#diff-5baf5f9e448ad54ab25a091adee0da05d4d228481c9200518fcb1b53a65d4156R6) [[2]](diffhunk://#diff-5baf5f9e448ad54ab25a091adee0da05d4d228481c9200518fcb1b53a65d4156R24) **Implementation and Code Refactoring:** * Refactored infrastructure and endpoint files to use FastEndpoints 7.0 API, including updating processors to global interfaces, removing FastEndpoints event handler, and changing response handling to direct `HttpContext.Response` usage. * Updated event handling to route domain events exclusively through MediatR, removing incompatible FastEndpoints event publishing and handlers. **Documentation of Build and Implementation Status:** * Added a detailed build success report in `docs/cli-tasks/BUILD_SUCCESS.md`, summarizing the changes, current implementation, testing instructions, and known limitations of the FastEndpoints integration.
1 parent 8db7614 commit 042fe0f

File tree

74 files changed

+1488
-1200
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1488
-1200
lines changed

.github/copilot-instructions.md

Lines changed: 129 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ This is an enterprise-ready Vertical Slice Architecture template for .NET 9 with
66
## Architecture Patterns
77

88
### Vertical Slice Organization
9-
- **Features**: `src/WebApi/Features/{FeatureName}/` contains Commands, Queries, and a `{FeatureName}Feature.cs` implementing `IFeature`
10-
- **Commands/Queries**: Each is a static class containing nested `Request`, `Handler`, `Validator`, and `Endpoint` classes
11-
- **Handlers**: Always named `Handler` (enforced by architecture tests in `tests/WebApi.ArchitectureTests/`)
12-
- **Endpoints**: Implement `IEndpoint` with `MapEndpoint(IEndpointRouteBuilder)` - auto-discovered via reflection in `Host/EndpointDiscovery.cs`
9+
- **Features**: `src/WebApi/Features/{FeatureName}/` contains Endpoints and a `{FeatureName}Feature.cs` implementing `IFeature`
10+
- **Endpoints**: Located in `Features/{FeatureName}/Endpoints/` - each endpoint is a separate file inheriting from `Endpoint<TRequest, TResponse>` or `EndpointWithoutRequest<TResponse>`
11+
- **Endpoint Discovery**: FastEndpoints automatically discovers and registers all endpoints at startup
12+
- **Groups**: Each feature defines a `Group` class to configure routing prefix and shared settings (e.g., `HeroesGroup` for `/api/heroes`)
1313

1414
### Domain Layer (`Common/Domain/`)
1515
- **Entities**: Inherit from `Entity<TId>` or `AggregateRoot<TId>` for domain events
@@ -24,16 +24,119 @@ This is an enterprise-ready Vertical Slice Architecture template for .NET 9 with
2424
- Apply with `.WithSpecification(new YourSpec())` on DbSet queries
2525
- Example: `dbContext.Heroes.WithSpecification(new HeroByIdSpec(heroId)).FirstOrDefault()`
2626

27-
### Request/Response Flow
28-
1. **Endpoint** receives HTTP request → sends `Request` via MediatR `ISender`
29-
2. **ValidationErrorOrResultBehavior** validates using FluentValidation → returns `ErrorOr<T>` on failure
30-
3. **Handler** executes business logic → returns `ErrorOr<TResponse>`
31-
4. **Endpoint** maps result: `.Match(TypedResults.Ok, CustomResult.Problem)` for queries, `.Match(_ => TypedResults.Created(), CustomResult.Problem)` for commands
32-
5. Use endpoint extension methods: `ProducesGet<T>()`, `ProducesPost()`, `ProducesPut()`, `ProducesDelete()`
27+
### FastEndpoints
28+
29+
This project uses **FastEndpoints** for defining HTTP endpoints with a clean, strongly-typed API structure.
30+
31+
#### Endpoint Structure
32+
Each feature slice in `Features/{FeatureName}/Endpoints/` contains:
33+
- **Endpoint classes**: Inherit from `Endpoint<TRequest, TResponse>` or `EndpointWithoutRequest<TResponse>`
34+
- **Request records**: Define the input contract (e.g., `CreateHeroRequest`)
35+
- **Response records**: Define the output contract (e.g., `CreateHeroResponse`)
36+
- **Validator classes**: Inherit from `Validator<TRequest>` for request validation
37+
- **Summary classes**: Inherit from `Summary<TEndpoint>` for OpenAPI documentation with examples
38+
39+
#### Creating Endpoints
40+
41+
**With Request and Response:**
42+
```csharp
43+
public record CreateHeroRequest(string Name, string Alias, IEnumerable<PowerDto> Powers);
44+
public record CreateHeroResponse(Guid Id);
45+
46+
public class CreateHeroEndpoint(ApplicationDbContext dbContext)
47+
: Endpoint<CreateHeroRequest, CreateHeroResponse>
48+
{
49+
public override void Configure()
50+
{
51+
Post("/");
52+
Group<HeroesGroup>();
53+
Description(x => x.WithName("CreateHero"));
54+
}
55+
56+
public override async Task HandleAsync(CreateHeroRequest req, CancellationToken ct)
57+
{
58+
// Business logic here
59+
await dbContext.SaveChangesAsync(ct);
60+
await Send.OkAsync(new CreateHeroResponse(id), ct);
61+
}
62+
}
63+
```
64+
65+
**Without Request (GET all):**
66+
```csharp
67+
public class GetAllHeroesEndpoint(ApplicationDbContext dbContext)
68+
: EndpointWithoutRequest<GetAllHeroesResponse>
69+
{
70+
public override void Configure()
71+
{
72+
Get("/");
73+
Group<HeroesGroup>();
74+
Description(x => x.WithName("GetAllHeroes"));
75+
}
76+
77+
public async override Task HandleAsync(CancellationToken ct)
78+
{
79+
// Query logic here
80+
await Send.OkAsync(new GetAllHeroesResponse(heroes), ct);
81+
}
82+
}
83+
```
84+
85+
#### Endpoint Groups
86+
Define a `Group` class per feature to configure common settings and routing:
87+
```csharp
88+
public class HeroesGroup : Group
89+
{
90+
public HeroesGroup()
91+
{
92+
Configure("heroes", ep => ep.Description(x => x.ProducesProblemDetails(500)));
93+
}
94+
}
95+
```
96+
- The prefix (e.g., "heroes") becomes `/api/heroes` and is used as the OpenAPI tag
97+
- All endpoints using `Group<HeroesGroup>()` inherit this configuration
98+
99+
#### Validation
100+
Create validators using FluentValidation patterns:
101+
```csharp
102+
public class CreateHeroRequestValidator : Validator<CreateHeroRequest>
103+
{
104+
public CreateHeroRequestValidator()
105+
{
106+
RuleFor(v => v.Name).NotEmpty();
107+
RuleFor(v => v.Alias).NotEmpty();
108+
}
109+
}
110+
```
111+
- Validation runs automatically before `HandleAsync()`
112+
- Failed validation returns 400 Bad Request with error details
113+
114+
#### OpenAPI Documentation
115+
Use `Summary<TEndpoint>` classes for rich API documentation:
116+
```csharp
117+
public class CreateHeroSummary : Summary<CreateHeroEndpoint>
118+
{
119+
public CreateHeroSummary()
120+
{
121+
Summary = "Create a new hero";
122+
Description = "Creates a new hero with the specified name, alias, and powers.";
123+
ExampleRequest = new CreateHeroRequest(...);
124+
Response(200, "Hero created successfully", example: new CreateHeroResponse(...));
125+
}
126+
}
127+
```
128+
129+
#### Response Handling
130+
FastEndpoints provides `Send` helper for responses:
131+
- `await Send.OkAsync(response, ct)` - 200 OK
132+
- `await Send.CreatedAsync(url, response, ct)` - 201 Created
133+
- `await Send.NoContentAsync(ct)` - 204 No Content
134+
- `await Send.NotFoundAsync(ct)` - 404 Not Found
33135

34136
### Error Handling
35-
- Use `ErrorOr<T>` result pattern (not exceptions for flow control)
36-
- Return `Error.Validation()`, `Error.NotFound()`, `Error.Conflict()`, etc.
137+
- FastEndpoints provides built-in response helpers: `Send.NotFoundAsync()`, `Send.ForbiddenAsync()`, etc.
138+
- Use `ThrowError()` in endpoints to return validation errors with custom messages
139+
- Global exception handling catches unhandled exceptions and returns appropriate HTTP responses
37140
- For eventual consistency failures in domain event handlers, throw `EventualConsistencyException` (handled by middleware)
38141

39142
## Adding New Features
@@ -102,8 +205,10 @@ dotnet test tests/WebApi.IntegrationTests/
102205
## Key Conventions
103206

104207
### Endpoint Routing
105-
- Use `endpoints.MapApiGroup(FeatureName)` to create `/api/{featurename}` routes with OpenAPI tags
106-
- Example: `HeroesFeature.FeatureName``/api/heroes`
208+
- FastEndpoints automatically discovers and registers all endpoints at startup
209+
- Use `Group<TGroup>()` in endpoint `Configure()` to assign to a route group
210+
- Define a `Group` class per feature to set the route prefix (e.g., `HeroesGroup` configures `"heroes"``/api/heroes`)
211+
- Example: `Group<HeroesGroup>()` in an endpoint routes to `/api/heroes/{endpoint-route}`
107212

108213
### Global Usings (`GlobalUsings.cs`)
109214
- `EntityFrameworkCore`, `FluentValidation`, `ErrorOr`, `Vogen`, `Ardalis.Specification` pre-imported
@@ -114,10 +219,10 @@ dotnet test tests/WebApi.IntegrationTests/
114219
- `Directory.Packages.props`: Central package management - update versions here
115220
- Release builds enforce `TreatWarningsAsErrors` and `EnforceCodeStyleInBuild`
116221

117-
### MediatR Behaviors (Order Matters)
118-
1. `UnhandledExceptionBehaviour` - catches unhandled exceptions
119-
2. `ValidationErrorOrResultBehavior` - validates requests, returns `ErrorOr` errors
120-
3. `PerformanceBehaviour` - logs slow requests
222+
### FastEndpoints Pipeline
223+
- **Validation**: Automatically runs FluentValidation validators before `HandleAsync()` - returns 400 Bad Request on failure
224+
- **Exception Handling**: Global exception handler catches unhandled exceptions and returns appropriate error responses
225+
- **Performance Logging**: Built-in logging for slow requests
121226

122227
### Domain Events & Eventual Consistency
123228
- Events dispatched by `DispatchDomainEventsInterceptor` after `SaveChangesAsync()`
@@ -148,10 +253,11 @@ dotnet test tests/WebApi.IntegrationTests/
148253

149254
## Common Pitfalls
150255
1. Forgetting to register strongly typed IDs in `VogenEfCoreConverters` → runtime EF Core errors
151-
2. Not using `ProducesGet/Post/Put/Delete()` extensions → inconsistent OpenAPI documentation
152-
3. Throwing exceptions in handlers instead of returning `ErrorOr<T>` → breaks error handling pipeline
153-
4. Not inheriting handlers from nested `Handler` class → architecture test failures
154-
5. Loading aggregates without specifications → missing related entities or inconsistent query patterns
256+
2. Not creating `Summary<TEndpoint>` classes → missing OpenAPI documentation and examples
257+
3. Forgetting to call `Group<TGroup>()` in endpoint `Configure()` → endpoint not properly grouped/routed
258+
4. Not using `await Send.OkAsync()` or similar response methods → no HTTP response sent
259+
5. Forgetting to add `return` after `await Send.OkAsync()` (or similar)
260+
6. Loading aggregates without specifications → missing related entities or inconsistent query patterns
155261

156262
## What's NOT Included
157263
- **Authentication/Authorization**: This is a template - implement auth as needed for your use case
@@ -248,4 +354,4 @@ This approach:
248354
- Keeps all work contained within the project
249355
- Prevents pollution of system directories
250356
- Makes cleanup easier and more predictable
251-
- Ensures proper git ignore handling
357+
- Ensures proper git ignore handling

0 commit comments

Comments
 (0)