vertical-slice
Skill5 starsChanged 6 days agoMITView on GitHub
Vertical Slice Architecture (VSA) for .NET APIs — feature-first folder structure, IEndpointGroup pattern, cross-cutting concerns, and testing approach.
11 other projects use a version of this.
Adopted by
Most recently taken up by
- codewithmukesh/dotnet-claude-kittheir own version, first seen Nov 8, 2026
- etr/groundworktheir own version, first seen Nov 8, 2026
- okfriansyah-moh/agentic-workflowtheir own version, first seen Oct 25, 2026
- Donchitos/Claude-Code-Game-Studiostheir own version, first seen Oct 19, 2026
- venus2099/codex-game-studiostheir own version, first seen Oct 17, 2026
- Resgrid/Coretheir own version, first seen Oct 8, 2026
Other versions11 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 11
- Donchitos/Claude-Code-Game-Studios★ 27ksame name
- codewithmukesh/dotnet-claude-kit★ 808same name
- Resgrid/Core★ 238same name
- etr/groundwork★ 45same name
- hongyuanc/codex-game-studios★ 5same name
- HunLuanZhiZhu/Game-Studios-Plugin★ 1same name
- sunji-droid/jack-of-all-trades-africa★ 1same name
- venus2099/codex-game-studios★ 1same name
- ziffan/auto-continue-cli-agent★ 1same name
- MatanelGordon/skills★ 0same name
1 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository15 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- aspnet-api-patternsSkill
- authenticationSkill
- cachingSkill
- clean-architectureSkill
- convention-learnerSkill
- dddSkill
- dotnet-project-initSkill
- ef-core-patternsSkill
- error-handlingSkill
- loggingSkill
- modern-csharpSkill
- opentelemetrySkill
- resilienceSkill
- testing-dotnetSkill
The file
We split this skill into the rules it gives an agent
3 rules. 0 are common, 2 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 8 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 101
Use records. Never share request/response types between features — duplication here is intentional and correct.
- Distinctive: line 114
Use FluentValidation per-request.
- Very specific: line 177
Test each slice independently.
“Common” is measured: kept by at least 6 in 10 of the 8 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Vertical Slice Architecture
Reference material for structuring .NET APIs by feature rather than by layer.
Core Concept
Organize code by feature (vertical slice), not by technical layer (horizontal). Each slice owns everything needed for one feature: endpoint, handler, DTOs, validation. Two features never share request/response types — coupling through shared DTOs is how VSA projects collapse back into layered code.
Use when most operations read or write a single entity with little business logic (CRUD-heavy APIs), or Clean Architecture feels like over-engineering.
Avoid when invariants span several entities, business rules outgrow CRUD, or event sourcing is on the roadmap — see "When to Use DDD" in skills/ddd/SKILL.md. Aggregate count alone doesn't decide it.
Folder Structure
src/MyApp/
Features/
Orders/
CreateOrder/
CreateOrderEndpoint.cs # IEndpointGroup implementation
CreateOrderHandler.cs # Command handler (or inline in endpoint)
CreateOrderRequest.cs # Input DTO record
CreateOrderResponse.cs # Output DTO record
CreateOrderValidator.cs # FluentValidation validator
GetOrder/
GetOrderEndpoint.cs
GetOrderQuery.cs
GetOrderResponse.cs
CancelOrder/
CancelOrderEndpoint.cs
CancelOrderCommand.cs
Domain/ # Shared only if 2+ features use it
Orders/
Order.cs
OrderStatus.cs
Infrastructure/
Persistence/
AppDbContext.cs
Configurations/
Program.csThe Domain/ folder is for shared domain concepts only. If only one feature references an entity, keep it inside that feature's folder.
IEndpointGroup Pattern
Each feature registers its own route via IEndpointGroup. The interface is registered and scanned in Program.cs.
public interface IEndpointGroup
{
void MapEndpoints(IEndpointRouteBuilder app);
}// Features/Orders/CreateOrder/CreateOrderEndpoint.cs
public class CreateOrderEndpoint : IEndpointGroup
{
public void MapEndpoints(IEndpointRouteBuilder app)
{
app.MapPost("/orders", HandleAsync)
.WithName("CreateOrder")
.AddEndpointFilter<ValidationFilter<CreateOrderRequest>>()
.RequireAuthorization();
}
private static async Task<IResult> HandleAsync(
CreateOrderRequest request,
AppDbContext db,
CancellationToken ct)
{
var order = new Order(request.CustomerId, request.Items);
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
return TypedResults.Created($"/orders/{order.Id}", new CreateOrderResponse(order.Id));
}
}// Program.cs — scan and register all IEndpointGroup implementations
var endpointGroups = typeof(Program).Assembly
.GetTypes()
.Where(t => t.IsAssignableTo(typeof(IEndpointGroup)) && !t.IsAbstract)
.Select(Activator.CreateInstance)
.Cast<IEndpointGroup>();
foreach (var group in endpointGroups)
group.MapEndpoints(app);DTOs
Use records. Never share request/response types between features — duplication here is intentional and correct.
// CreateOrderRequest.cs
public record CreateOrderRequest(Guid CustomerId, IReadOnlyList<OrderItemRequest> Items);
public record OrderItemRequest(Guid ProductId, int Quantity);
// CreateOrderResponse.cs
public record CreateOrderResponse(Guid OrderId);Validation
Use FluentValidation per-request. Wire via a generic endpoint filter so individual endpoints don't repeat the call.
// Features/Orders/CreateOrder/CreateOrderValidator.cs
public class CreateOrderValidator : AbstractValidator<CreateOrderRequest>
{
public CreateOrderValidator()
{
RuleFor(x => x.CustomerId).NotEmpty();
RuleFor(x => x.Items).NotEmpty();
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(i => i.Quantity).GreaterThan(0);
});
}
}// Infrastructure/Filters/ValidationFilter.cs
public class ValidationFilter<T>(IValidator<T> validator) : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext ctx, EndpointFilterDelegate next)
{
var request = ctx.GetArgument<T>(0);
var result = await validator.ValidateAsync(request);
if (!result.IsValid)
return TypedResults.ValidationProblem(result.ToDictionary());
return await next(ctx);
}
}Register validators and the filter in DI:
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
builder.Services.AddScoped(typeof(ValidationFilter<>));Cross-Cutting Concerns
Handle via endpoint filters, not base classes:
| Concern | Approach |
| Validation | ValidationFilter<TRequest> |
| Authentication | .RequireAuthorization() or policy |
| Logging / timing | LoggingFilter via AddEndpointFilter |
| Rate limiting | RateLimiterOptions + .RequireRateLimiting() |
Extracting to Shared Domain
Extract to Domain/ only when two or more feature slices reference the same concept and it carries meaningful behavior (not just a DTO shape):
Orderentity referenced byCreateOrder,CancelOrder,GetOrder→ extract toDomain/Orders/OrderStatusenum used across multiple features → extractCreateOrderRequestused by only one feature → keep it in the feature folder
The Domain/ folder must stay infrastructure-free: no EF Core attributes, no HTTP types, no Polly.
Testing
Test each slice independently. Use WebApplicationFactory for full-stack slice tests.
public class CreateOrderTests(WebApplicationFactory<Program> factory)
: IClassFixture<WebApplicationFactory<Program>>
{
[Fact]
public async Task CreateOrder_ValidRequest_ReturnsCreated()
{
var client = factory.CreateClient();
var request = new CreateOrderRequest(Guid.NewGuid(), [new(Guid.NewGuid(), 2)]);
var response = await client.PostAsJsonAsync("/orders", request);
response.StatusCode.Should().Be(HttpStatusCode.Created);
}
}For handler-only tests, instantiate the handler directly with a DbContext pointed at a Testcontainers database (see skills/testing-dotnet/SKILL.md). Don't mock DbContext or use the EF in-memory provider — it skips constraints, transactions and SQL translation, so tests pass against behaviour production doesn't have.
Do / Don't
| Do | Don't |
| Keep each feature folder self-contained | Share Request/Response DTOs between features |
| Use endpoint filters for cross-cutting concerns | Create BaseHandler or BaseEndpoint base classes |
Extract to Domain/ only when truly shared | Put infrastructure code (EF, HTTP) in Domain/ |
Name files <Action><Feature><Suffix> (e.g., CreateOrderHandler.cs) | Mix suffix conventions (CreateOrderService.cs + GetOrderHandler.cs) |
Test slices via WebApplicationFactory | Mock DbContext in slice tests |
See Also
knowledge/decisions/adr-001-vsa-default.md— why VSA is the default for new APIsknowledge/decisions/adr-005-multi-architecture.md— when to choose Clean Architecture or DDD insteadknowledge/common-infrastructure.md—IEndpointGroupinterface definition and bootstrap
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

