Wallow Developer Onboarding
This guide gets you from zero to productive. For the full architecture reference, see the Developer Guide.
1. Quick Start
Prerequisites
- Docker Desktop (or Docker Engine + Docker Compose)
- .NET 10 SDK (download)
- Node 24 (see
.nvmrc) and pnpm 11.24.0, for the frontend workspace - Your preferred IDE (Rider, VS Code with C# Dev Kit, or Visual Studio)
- Git
Get It Running
# 1. Clone the repo
git clone https://github.com/your-org/wallow.git
cd wallow
# 2. Create the Docker env file (GF_ADMIN_PASSWORD is required)
cp docker/.env.example docker/.env
# 3. Install the workspace
pnpm install
# 4. Run the whole system -- infrastructure, API, and both React apps
pnpm backend
# You should see:
# [12:34:56 INF] [Api] Now listening on: http://localhost:5001
pnpm backend starts the .NET Aspire host (Wallow.AppHost), which brings up the containerised
infrastructure and the API together. It is the supported way to run Wallow locally.
If you only want the infrastructure containers -- to run or debug the API from your IDE -- use
pnpm backend:infra (and pnpm backend:infra:down to stop them). Note that starting
Wallow.Api on its own does not apply migrations; see
Database Migrations for how schemas are created.
Verify Everything Works
| Service | URL | Credentials |
|---|---|---|
| API Docs (Scalar) | http://localhost:5001/scalar/v1 | N/A |
| Hangfire Dashboard | http://localhost:5001/hangfire | N/A |
| AsyncAPI Viewer | http://localhost:5001/asyncapi | N/A |
| Web app | http://localhost:3000 | N/A |
| Auth app | http://localhost:3002 | N/A |
| Mailpit (Email Sink) | http://localhost:8025 | N/A |
| Grafana (Observability) | http://localhost:3001 | admin / GF_ADMIN_PASSWORD from docker/.env |
| Docs site (DocFX) | http://localhost:5004 | N/A — started separately by ./scripts/docs-serve.sh |
If all URLs load, you are ready. (The web and auth apps only run under pnpm backend; if you started
only pnpm backend:infra, they are not running.)
2. Architecture at a Glance
Wallow is a modular monolith -- a single deployable application internally organized into autonomous modules. Each module owns its own database schema, domain logic, and API endpoints. Modules communicate via Wolverine in-memory events, never direct references.
Module Structure
Every module follows Clean Architecture with four layers:
api/src/Modules/{Module}/
Wallow.{Module}.Domain -- Entities, Value Objects, Domain Events (zero dependencies)
Wallow.{Module}.Application -- Commands, Queries, Handlers, DTOs (depends on Domain)
Wallow.{Module}.Infrastructure -- EF Core, Consumers (implements Application interfaces)
Wallow.{Module}.Api -- Controllers, Request/Response DTOs (depends on Application)
Dependencies point inward. Domain depends on nothing. Infrastructure and Api depend on Application. Application depends on Domain.
Key Concepts
CQRS via Wolverine. Wolverine acts as both mediator and message bus. Handlers are static classes with HandleAsync methods, auto-discovered from all Wallow.* assemblies. No manual registration needed.
Multi-tenancy. The JWT contains an org_id claim. TenantResolutionMiddleware extracts it into ITenantContext. TenantSaveChangesInterceptor auto-stamps TenantId on new entities. EF Core global query filters scope all reads to the current tenant. You rarely need to think about tenancy -- it is automatic.
Module communication. Modules never reference each other directly. They communicate via integration events defined in Wallow.Shared.Contracts, published and consumed through Wolverine.
Shared infrastructure. Cross-cutting capabilities live in separate shared projects:
- Auditing (
Shared.Infrastructure.Core/Auditing/) -- EF CoreSaveChangesInterceptorfor change tracking - Background Jobs (
Shared.Infrastructure.BackgroundJobs/) --IJobSchedulerabstraction over Hangfire - Plugins (
Shared.Infrastructure.Plugins/) -- Isolated loading and lifecycle for fork-specific plugin assemblies
Frontend. The API is headless. The user interfaces are two TanStack Start React apps in the pnpm workspace -- apps/wallow-web (dashboard, port 3000) and apps/wallow-auth (login, signup, MFA, port 3002). See the Frontend Setup guide.
3. Codebase Exploration Checklist
Work through this list to build a mental map of the codebase.
Startup and Infrastructure
- [ ] Read
api/src/Wallow.Api/Program.cs-- See the middleware pipeline (exception handler, auth, tenant resolution, permission expansion, authorization), Wolverine setup, Hangfire, SignalR, and health checks. - [ ] Read
api/src/Wallow.Modules.Registry/WallowModuleRegistry.cs-- The one list of modules the platform ships. Both hosts read it:Wallow.Apifiltered by feature flag,Wallow.MigrationServiceunfiltered. - [ ] Read
api/src/Wallow.Api/WallowModules.cs-- See how that list becomes the enabled set:IsModuleFlagEnabledreadsFeatureManagement:Modules.{Name}straight offIConfiguration. Identity is a core module and is always registered; all other modules are behind feature flags.
Module Deep Dive: Notifications
Notifications is a strong reference implementation for DDD patterns with multi-channel delivery. Start here.
- [ ] Domain:
api/src/Modules/Notifications/Wallow.Notifications.Domain/Channels/-- Aggregates per channel (Email, InApp, Push, SMS), Value Objects (EmailAddress,EmailContent), domain events. - [ ] Application:
api/src/Modules/Notifications/Wallow.Notifications.Application/Channels/-- Commands, queries, and handlers organized by channel, plus integration event handlers inEventHandlers/. - [ ] Infrastructure:
api/src/Modules/Notifications/Wallow.Notifications.Infrastructure/Persistence/NotificationsDbContext.cs-- Schema name, multi-tenancy query filters, provider pattern for channel adapters. - [ ] API:
api/src/Modules/Notifications/Wallow.Notifications.Api/Controllers/-- Thin controllers that delegate to WolverineIMessageBus.
Authentication and Multi-Tenancy
- [ ]
api/src/Modules/Identity/Wallow.Identity.Infrastructure/MultiTenancy/TenantResolutionMiddleware.cs-- How JWTorg_idclaim becomesITenantContext.TenantId. - [ ]
api/src/Modules/Identity/Wallow.Identity.Infrastructure/Authorization/PermissionExpansionMiddleware.cs-- How roles expand to permission claims. - [ ]
api/src/Shared/Wallow.Shared.Kernel/MultiTenancy/TenantSaveChangesInterceptor.cs-- Auto-stamping ofTenantIdon new entities and tampering prevention.
Integration Events
- [ ] Browse
api/src/Shared/Wallow.Shared.Contracts/-- Integration events organized by module. These are module-to-module contracts. - [ ] Browse
api/src/Modules/Notifications/Wallow.Notifications.Application/EventHandlers/-- How to consume events from other modules.
Run the Tests
./scripts/run-tests.sh
This runs the fast suites only -- run-tests.sh appends --filter "Category!=E2E&Category!=Integration"
to every invocation except integration and all, and tells you so in its own output. Run either of
these to exercise the integration tier, and watch for Testcontainers spinning up Postgres and Valkey:
./scripts/run-tests.sh integration # ONLY the Category=Integration tests, solution-wide
./scripts/run-tests.sh all # the fast suites and the integration suites together
The frontend workspace has its own gate. pnpm check runs formatting, both lint passes, manifest and
dependency checks, build, typecheck, tests and export checks -- it is what CI runs, so run it before
opening a PR that touches apps/ or packages/.
4. Common Patterns
Result Pattern
Never throw exceptions for business rule violations. Use Result<T> from Shared.Kernel.
Adding a New Command
- Define the command record in the Application layer
- Add a FluentValidation validator
- Create a static handler class with
HandleAsync-- Wolverine discovers it automatically - Add a thin controller action that dispatches via
IMessageBus
For the full step-by-step guide to creating a new module, see the Developer Guide.
5. Points of Interest
Which modules skip CQRS? Two: Branding (DTOs/ + Interfaces/ only) and ApiKeys (Interfaces/ only). Both go straight to a service or repository because the command/query split would be ceremony without benefit. Every other module -- Identity included -- has Commands/ and Queries/ with Wolverine handlers; Identity's cover setup, service accounts and API scopes, while ASP.NET Core Identity remains the source of truth for user accounts themselves. See API Development for when each shape applies.
Where is email handling? In the Notifications module. It consumes events from Identity, Announcements, and Inquiries to send transactional emails.
Which module is the best example? Notifications. Multi-channel delivery, full CQRS, FluentValidation, Value Objects (EmailAddress, EmailContent), strongly-typed IDs, integration events, provider pattern, and comprehensive tests.
6. Testing
Run all tests with ./scripts/run-tests.sh. Run a specific module with ./scripts/run-tests.sh identity.
Unit tests test domain entities and handlers in isolation with mocked dependencies. Integration tests use Testcontainers to spin up real Postgres and Valkey. Architecture tests (Wallow.Architecture.Tests) enforce structural rules such as module isolation and dependency direction.
For detailed testing patterns and examples, see the Developer Guide.
7. FAQ
Where do I add a new API endpoint? In the module's Api project. Controllers should be thin -- validate and delegate to Wolverine.
How do I add a new migration?
dotnet ef migrations add MigrationName \
--project api/src/Modules/{Module}/Wallow.{Module}.Infrastructure \
--startup-project api/src/Wallow.Api \
--context {Module}DbContext
How do I reset my local database?
cd docker && docker compose down -v && cd .. # -v drops the volumes; pnpm backend:infra:down keeps them
pnpm backend # Aspire runs wallow-migrations, then the seeder, then the API
Nothing migrates on API startup. Under Aspire the wallow-migrations project resource applies
migrations before the API starts; if you run Wallow.Api on its own you must run
Wallow.MigrationService (or dotnet ef database update) yourself. See
Database Migrations.
Can I query across modules? No. Modules are autonomous. If Module A needs data from Module B, Module B publishes an event and Module A stores a local copy (eventual consistency). For rare cases requiring synchronous cross-module reads, Shared.Contracts defines query service interfaces implemented in the owning module's Infrastructure layer.
8. Useful Links
| Resource | Location |
|---|---|
| API Docs (Scalar) | http://localhost:5001/scalar/v1 |
| Web app | http://localhost:3000 |
| Auth app | http://localhost:3002 |
| Mailpit | http://localhost:8025 |
| Grafana | http://localhost:3001 |
| Hangfire Dashboard | http://localhost:5001/hangfire |
| AsyncAPI Viewer | http://localhost:5001/asyncapi |
| Docs site (DocFX) | http://localhost:5004 — ./scripts/docs-serve.sh |
| Developer Guide | developer-guide.md |
| Frontend Setup | ../development/frontend-setup.md |
| Architecture Assessment | ../architecture/assessment.md |
| Deployment Guide | ../operations/deployment.md |
Next Steps
- Pick a small issue from the backlog
- Read the Notifications module end-to-end (it is the reference implementation)
- Pair with a teammate on your first PR
- Run the tests and see what breaks when you change things