Skip to main content

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:

  1. Define or update entities in Wayd.\{Service\}.Domain/Models/
  2. Add value objects if needed
  3. 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
  4. Write domain tests, with fakers in the domain test project's Data/ folder

2. Application Layer​

Add commands, queries, and handlers:

  1. Create the command/query as a public sealed record named *Command or *Query in \{Feature\}/Commands/ or \{Feature\}/Queries/, implementing ICommand, ICommand<TResponse> or IQuery<TResponse>
  2. Create the handler (a public sealed class named *Handler — Wolverine's generated code cannot invoke internal handlers) implementing ICommandHandler<TCommand>, ICommandHandler<TCommand, TResponse> or IQueryHandler<TQuery, TResponse>. A command handler returns Result or Result<T>, and each message has exactly one handler. Inject the module's IXxxDbContext, never WaydDbContext. Handler dependencies are constructor-inlined by codegen; if you inject a service whose implementation class is internal, add it to the AlwaysUseServiceLocationFor<T>() allow-list in WolverineConfiguration (if you forget, CI's codegen write step, or the handler's first local dispatch, fails with an InvalidServiceLocationException naming the type)
  3. Create the validator using FluentValidation
  4. Create DTOs for data transfer in \{Feature\}/Dtos/
  5. Implement Mapster's IMapFrom<TSource> on the DTO, implementing ConfigureMapping(TypeAdapterConfig) where the mapping is not by name
  6. 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:

  1. Add entity configuration in Wayd.Infrastructure/Persistence/Configuration/
  2. Expose the new DbSet on the module's IXxxDbContext and its fake
  3. Create a database migration — see Database & Migrations
  4. Update seed data if necessary

4. API Layer​

Expose the feature through the API:

  1. Add controller actions in Wayd.Web.Api/Controllers/\{DomainArea\}/
  2. Controllers should be thin - delegate to handlers via IDispatcher (Wolverine)
  3. Give each action [MustHavePermission], [EndpointSummary] (and [EndpointDescription] where it helps), and its response types — see API Development
  4. Return appropriate HTTP status codes
  5. Rebuild Wayd.Web.Api in Debug to regenerate specification.json and wayd-api.ts, and commit both

5. Frontend​

Build the UI:

  1. Add pages in the appropriate route directory
  2. Use the NSwag-generated client for API calls
  3. Follow Ant Design patterns and theme tokens
  4. Add to the sidebar navigation if needed

6. Feature Flag (Optional)​

If the feature should be gradually rolled out:

  1. Add flag definition in FeatureFlags.cs
  2. Gate the controller with [FeatureGate]. Where a flag changes behaviour inside an endpoint that exists either way, inject IFeatureFlags into the handler that owns the behaviour instead
  3. Gate the UI with useFeatureFlag or requireFeatureFlag
  4. See Feature Flags for details

7. Before You Finish​

  1. Build the projects you touched and run each affected test project; run Wayd.ArchitectureTests after changing a project reference — see Running Tests
  2. In the client, run npm run typecheck, npm run lint and npm test
  3. A changed CSV import also needs npm run generate:import-templates — see CSV import endpoints
  4. A change users can see updates its page in docs/user-guide/