|
| 1 | +--- |
| 2 | +adr: "0031" |
| 3 | +status: "Proposed" |
| 4 | +date: 2026-06-23 |
| 5 | +tags: [server] |
| 6 | +--- |
| 7 | + |
| 8 | +# 0031 - Adopt Minimal APIs |
| 9 | + |
| 10 | +<AdrTable frontMatter={frontMatter}></AdrTable> |
| 11 | + |
| 12 | +## Context and problem statement |
| 13 | + |
| 14 | +.NET 6 introduced minimal APIs — a lighter alternative to controllers designed for simplicity, |
| 15 | +speed, and ahead-of-time (AOT) compilation. As of .NET 10, minimal APIs now support automatic |
| 16 | +validation via data annotation attributes, closing the last significant feature gap with |
| 17 | +controllers. |
| 18 | + |
| 19 | +The key architectural advantage: minimal APIs let us decompose the server into smaller, composable |
| 20 | +pieces. Each feature in its own library owns its `.csproj` — and its dependencies — making it |
| 21 | +straightforward to deploy a feature independently or bundle several into a single Lite container. |
| 22 | + |
| 23 | +## Considered options |
| 24 | + |
| 25 | +- **Keep using controllers** - Continue using controllers for all our APIs. Controllers are well |
| 26 | + understood by the team and have a proven track record. However, controllers are no longer |
| 27 | + Microsoft's preferred direction, have no path to AOT compatibility, and resist the composable |
| 28 | + architecture we're moving toward. |
| 29 | +- **Use minimal APIs for new endpoints** - Controllers and minimal APIs can be used side-by-side; |
| 30 | + new features can use minimal APIs exclusively, segregating their concerns in their own project. |
| 31 | + This also keeps a future AOT compilation path open. |
| 32 | +- **Migrate controllers to minimal APIs** - We could ban controllers entirely and embark on an |
| 33 | + aggressive switch to minimal APIs, resulting in a consistent codebase with no mixed styles. |
| 34 | + However, this is very time-consuming, and there are other pieces that need to fall into place |
| 35 | + before we can achieve AoT compilation anyway — so we wouldn't get the performance benefits right |
| 36 | + away. |
| 37 | + |
| 38 | +## Decision outcome |
| 39 | + |
| 40 | +Chosen option: **Use minimal APIs for new endpoints**. |
| 41 | + |
| 42 | +New endpoints use minimal APIs, placed in a feature-scoped library that exposes two methods: |
| 43 | +`Add[Feature]Services` and `Map[Feature]Endpoints`. The host service calls both under an endpoint |
| 44 | +group, which makes extracting any feature into a standalone service behind a reverse proxy |
| 45 | +straightforward later. |
| 46 | + |
| 47 | +```csharp |
| 48 | +// Feature library: MyFeature/ServiceCollectionExtensions.cs |
| 49 | +public static IServiceCollection AddMyFeatureServices(this IServiceCollection services) |
| 50 | +{ |
| 51 | + services.TryAddScoped<IMyFeatureService, MyFeatureService>(); |
| 52 | + return services; |
| 53 | +} |
| 54 | + |
| 55 | +// Feature library: MyFeature/EndpointRouteBuilderExtensions.cs |
| 56 | +public static RouteGroupBuilder MapMyFeatureEndpoints(this IEndpointRouteBuilder routes) |
| 57 | +{ |
| 58 | + var group = routes.MapGroup(""); |
| 59 | + group.MapGet("/", GetAll); |
| 60 | + group.MapPost("/", Create); |
| 61 | + return group; |
| 62 | +} |
| 63 | + |
| 64 | +// Host service: Program.cs |
| 65 | +builder.Services.AddMyFeatureServices(); |
| 66 | + |
| 67 | +app.UseEndpoints(endpoints => |
| 68 | +{ |
| 69 | + endpoints.MapDefaultControllerRoute(); |
| 70 | + endpoints.MapGroup("/my-feature") |
| 71 | + .MapMyFeatureEndpoints(); |
| 72 | +}); |
| 73 | +``` |
| 74 | + |
| 75 | +### Positive consequences |
| 76 | + |
| 77 | +- Better segregation of features similar to clients and sdk-internal |
| 78 | +- Teams own their feature library's `.csproj`, giving them direct control over their package |
| 79 | + dependencies |
| 80 | +- Easier path to breaking a feature out as its own service, or composing features into a Lite |
| 81 | + container |
| 82 | +- Enables a future path to AoT compilation, improving cold-start performance |
| 83 | + |
| 84 | +### Negative consequences |
| 85 | + |
| 86 | +- A new way of writing endpoints has to be learned |
| 87 | +- Controllers and minimal APIs will coexist in the codebase for the foreseeable future, which |
| 88 | + increases the surface area developers need to be familiar with |
| 89 | +- `ActionFilterAttribute`s must be rewritten as endpoint filters before any controller that uses |
| 90 | + them can be migrated to minimal APIs |
| 91 | + |
| 92 | +### Plan |
| 93 | + |
| 94 | +An `ENDPOINT_LIBRARY.md` in `src/Libraries` will document the canonical library shape, how to wire |
| 95 | +it into the existing monolith, and the path to extracting a feature as a standalone service. |
| 96 | + |
| 97 | +New endpoints added to existing controller-based projects should still use minimal APIs where |
| 98 | +possible, even if not yet in their own feature-scoped library. Existing controllers will be migrated |
| 99 | +opportunistically. Icons, Notifications, and Events are prioritized as early migration targets given |
| 100 | +their small endpoint surface area; migration for a project is considered complete when it has no |
| 101 | +remaining controller classes. Heavier projects will be migrated as opportunities arise. |
0 commit comments