Adding a Feature
This guide walks through the typical process of adding a new feature to Wayd, touching all layers of the architecture.
1. Domain Layer
Start with the domain model:
- Define or update entities in
Wayd.\{Service\}.Domain/Models/ - Add value objects if needed
- Define domain events for significant state changes, following Domain Events. An aggregate that raises events must raise one from every public method that changes its state
- Write domain tests, with fakers in the domain test project's
Data/folder
2. Application Layer
Add commands, queries, and handlers:
- Create the command/query as a
public sealed recordnamed*Commandor*Queryin\{Feature\}/Commands/or\{Feature\}/Queries/, implementingICommand,ICommand<TResponse>orIQuery<TResponse> - Create the handler (a
public sealed classnamed*Handler— Wolverine's generated code cannot invoke internal handlers) implementingICommandHandler<TCommand>,ICommandHandler<TCommand, TResponse>orIQueryHandler<TQuery, TResponse>. A command handler returnsResultorResult<T>, and each message has exactly one handler. Inject the module'sIXxxDbContext, neverWaydDbContext. Handler dependencies are constructor-inlined by codegen; if you inject a service whose implementation class isinternal, add it to theAlwaysUseServiceLocationFor<T>()allow-list inWolverineConfiguration(if you forget, CI'scodegen writestep, or the handler's first local dispatch, fails with anInvalidServiceLocationExceptionnaming the type) - Create the validator using FluentValidation
- Create DTOs for data transfer in
\{Feature\}/Dtos/ - Implement Mapster's
IMapFrom<TSource>on the DTO, implementingConfigureMapping(TypeAdapterConfig)where the mapping is not by name - Write application tests against the module's fake DbContext — see Testing
Wayd.ArchitectureTests checks the naming, visibility, handler and layering rules above; see
Architecture tests.
3. Infrastructure Layer
If the feature requires database changes:
- Add entity configuration in
Wayd.Infrastructure/Persistence/Configuration/ - Expose the new
DbSeton the module'sIXxxDbContextand its fake - Create a database migration — see Database & Migrations
- Update seed data if necessary
4. API Layer
Expose the feature through the API:
- Add controller actions in
Wayd.Web.Api/Controllers/\{DomainArea\}/ - Controllers should be thin - delegate to handlers via
IDispatcher(Wolverine) - Give each action
[MustHavePermission],[EndpointSummary](and[EndpointDescription]where it helps), and its response types — see API Development - Return appropriate HTTP status codes
- Rebuild
Wayd.Web.Apiin Debug to regeneratespecification.jsonandwayd-api.ts, and commit both
5. Frontend
Build the UI:
- Add pages in the appropriate route directory
- Use the NSwag-generated client for API calls
- Follow Ant Design patterns and theme tokens
- Add to the sidebar navigation if needed
6. Feature Flag (Optional)
If the feature should be gradually rolled out:
- Add flag definition in
FeatureFlags.cs - Gate the controller with
[FeatureGate]. Where a flag changes behaviour inside an endpoint that exists either way, injectIFeatureFlagsinto the handler that owns the behaviour instead - Gate the UI with
useFeatureFlagorrequireFeatureFlag - See Feature Flags for details
7. Before You Finish
- Build the projects you touched and run each affected test project; run
Wayd.ArchitectureTestsafter changing a project reference — see Running Tests - In the client, run
npm run typecheck,npm run lintandnpm test - A changed CSV import also needs
npm run generate:import-templates— see CSV import endpoints - A change users can see updates its page in
docs/user-guide/