Your AI Demo Works. Production Is Where It Breaks. (Sponsored)
Most AI projects look great in a demo, then break the moment real data and real traffic hit them. MongoDB's freeAI Skill Badges teach you how to build AI apps that hold up in production β short, hands-on courses on RAG, agent memory, vector search, and semantic search with Voyage AI. Each one takes 30 to 75 minutes and earns a verifiable credential you can add to your LinkedIn.
The Developer Toolkit for Shipping AI Features with Confidence (Sponsored)
When building AI-powered applications, the hard part isn't writing the code, but knowing what happens after you deploy. The Datadog Developer Toolkit addresses the gap between getting a model working locally and keeping it reliable in production, with resources built specifically for developers who are done experimenting and ready to ship.
Here is what's inside the free toolkit:
Product Briefs detailing how CI Pipeline Visibility and Test Optimization give you end-to-end delivery metrics and faster, cleaner pipelines
An eBook exploring what it takes to monitor LLM applications, agents, and retrieval pipelines in production
An On-Demand Webinar covering best practices for modern software delivery in the AI age, including how to use CI Visibility, Test Optimization, and Feature Flags to keep delivery workflows from becoming the limiting factor
For developers already shipping AI features, the value is in having one place where code quality, delivery speed, and model observability connect.
Vertical Slice Architecture (VSA) is one of the most popular ways to structure .NET projects today.
It structures an application by features instead of technical layers.
In a traditional layered project, you have a Controllers folder, a Services folder, and a Repositories folder (and probably way more).
To change one feature, you jump between three or four folders (or even projects).
In VSA, every feature is a single slice.
The endpoint, business logic, validation, and data access for that feature live next to each other.
This brings several benefits:
High cohesion within a feature
Loose coupling between features
Faster navigation in the codebase
Independent changes to one feature do not take effect on the others
I have built many systems with VSA and shipped them to production, and over the years, my layout has settled into a single shape that I now reuse in every new project.
In this post, we will explore:
How I Structure my Projects with Vertical Slice Architecture
The naming convention uses a dot suffix: {Slice}.Endpoint.cs, {Slice}.Handler.cs, and so on.
This keeps files easy to find in the IDE search and easy to scan in the file tree.
I also extract cross-cutting concerns, such as validators and mappers, into separate files, so I don't clutter the main file with too many classes.
This interface lets me discover and register all endpoints in a single line at startup (more on that later).
response.Errors.ToProblem() is an extension method that maps error types (Conflict, NotFound, Validation, and so on) to the appropriate HTTP status codes.
Validation runs explicitly in the endpoint, before the handler is called.
I do not use a MediatR pipeline behavior, like in many implementations.
The validation flow stays simple and easy to follow.
Notice how simple the structure is.
No extra interfaces, no commands, no command handlers, no mediator, no magic navigation to implementation, just a direct call to an exact class.
Instead of throwing exceptions for expected errors, the handler uses a Result Pattern and returns Result<ShipmentResponse>.
Errors for the module live in a single static class, so they stay consistent and easy to find:
csharp
1internalstaticclassShipmentErrors2{3privateconststring ErrorPrefix ="Shipments";45internalstaticErrorNotFound(string shipmentNumber)=>6 Error.NotFound($"{ErrorPrefix}.{nameof(NotFound)}",7$"Shipment with number '{shipmentNumber}' not found");89internalstaticErrorAlreadyExists(string orderId)=>10 Error.Conflict($"{ErrorPrefix}.{nameof(AlreadyExists)}",11$"Shipment for order '{orderId}' already exists");12}
Cross-module calls go through public APIs.
The handler does not reference any internals of the Stocks module.
It only uses IStockModuleApi, which is exposed by a separate Modules.Stocks.PublicApi project.
Events fire after the save.
Once the shipment is persisted, the handler publishes a ShipmentCreatedEvent.
Other slices and other modules can react to this event without the handler knowing about them.
Some things are needed by more than one slice in the same module (of a Modular Monolith): route constants, errors and response shapes for related slices.
To avoid duplication, I put them in a Shared/ folder inside the module's Features project.
Auto-Registration of Endpoints, Handlers, and Validators
With four files per slice, you would expect a lot of DI registration code.
Instead, I use a small helper that scans the module's assembly and registers everything at startup.
I have small reflection helpers that scan an assembly and register all its members.
The Shipments module references only Modules.Stocks.PublicApi.
It cannot reference the Stocks domain entities, DbContext, or internal services.
The implementation of IStockModuleApi lives inside the Stocks module and is internal sealed.
This gives you the separation benefits of microservices (clear contracts, no shared internals) while keeping the simplicity of a single deployable application.
If you ever extract a module into its own service, the PublicApi contract remains unchanged, and only the implementation of underlying transport changes.
If you need to read data or perform transactions across multiple modules, I created a detailed guide outlining the trade-offs involved.
Read it here.
This is the slice layout I now use in every new .NET project:
One folder per feature, named after the use case
Four files per slice: Endpoint, Handler, Mapping, Validators
Optional Events/ subfolder when the slice raises events
Manual handlers via an IHandler marker interface without extra interfaces and MediatR
Minimal API endpoints via an IApiEndpoint marker interface
FluentValidation called explicitly in the endpoint
Direct DbContext in handlers without repositories
Result<T> for business errors instead of exceptions
A Shared/ folder per module for cross-slice things
Cross-module calls only through PublicApi projects
Auto-registration of endpoints, handlers
In practice, this layout is fast to navigate, predictable across the team, easy to debug (no decorators, no magic), simple to test, and free of too many 3rd party dependencies.
Hope you find this newsletter useful. See you next time.
Whenever you're ready, here's how I can help you:
The .NET Senior Playbook β 800+ real-world interview questions with expert answers across 50 chapters. You try to answer each question first, then reveal the full solution β and a test after every chapter proves it actually stuck. Finish, and you earn a verifiable certificate for your LinkedIn.