A Marketplace built on .NET 10, Aspire, FastEndpoints, EF Core with PostgreSQL, Temporal, Dapr, Keycloak and a React + Vite web app. The domain vocabulary is in CONTEXT.md and the engineering rules are in the constitution.
| Tool | Version | Check |
|---|---|---|
| .NET SDK | 10.0.400 (pinned in global.json) |
dotnet --version |
| Aspire CLI | 13.6 | aspire --version |
| Node.js and pnpm | Node 24 or later, pnpm | node -v && pnpm -v |
| Container runtime | Docker or Podman | docker info or podman info |
| Dapr CLI and runtime | 1.15 or later | dapr --version |
One-time setup:
dapr init --slim # installs daprd only; no containers
pnpm --dir src/MicroCommerce.Web installPodman users need these environment variables in their shell profile. Aspire and
Testcontainers don't see a shell docker alias.
export ASPIRE_CONTAINER_RUNTIME=podman
export DOCKER_HOST="unix://$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')"
export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sockaspire runThis starts PostgreSQL, Redis, Temporal, Keycloak (with the micro-commerce realm imported), Azure
Blob Storage as the Azurite emulator (storage and blobs, for Product images), the ApiService
with its Dapr sidecar, and the web app. In Development the API also seeds a starter Category tree.
Then:
- Dashboard: the URL that
aspire runprints. Every resource should become Running or Healthy. - Web app: http://localhost:5173.
- API reference (Scalar):
<apiservice-url>/scalar/v1, in Development only. The OpenAPI document is at<apiservice-url>/openapi/v1.json. - Temporal UI: the
uiendpoint of thetemporalresource. - Dev sign-in: the seeded Accounts are
buyer,merchant(no Merchant yet: open one with Open your Shop) andoperator(a Platform Operator). Their passwords are generated on the first run and kept in user secrets. Find them in the dashboard under Parameters:dev-buyer-password,dev-merchant-passwordanddev-operator-password.
Dev runs keep their containers and data volumes between runs. Keycloak imports the realm only when
it doesn't have it yet, so after editing src/MicroCommerce.AppHost/Realms/micro-commerce-realm.json,
delete the Keycloak volume. Coming from the 001 skeleton? The realm gained the merchant user, so
delete the Keycloak volume once.
The Catalog (open a Merchant, list Products, browse, Categories, moderation) is walked through in specs/002-product-catalog/quickstart.md.
global.json # SDK 10.0.400, Microsoft.Testing.Platform runner
MicroCommerce.slnx
Directory.Build.props # nullable, warnings as errors
Directory.Packages.props # every package version, pinned centrally
src/
├── MicroCommerce.AppHost/ # Aspire: postgres, redis, temporal, keycloak, azurite, dapr pubsub, apiservice + sidecar, web
│ └── Realms/ # Keycloak dev realm; passwords are ${ENV} placeholders
├── MicroCommerce.ServiceDefaults/ # OpenTelemetry, health checks, service discovery, resilience
├── MicroCommerce.Domain/ # aggregates, value objects and typed IDs; references only Vogen
├── MicroCommerce.ApiService/
│ ├── Features/ # vertical slices: endpoints, validators, workflows, handlers
│ │ ├── Accounts/ # Merchants (own DbContext, schema accounts)
│ │ └── Catalog/ # Categories, Products, images, Marketplace (own DbContext, schema catalog)
│ └── Infrastructure/ # Data (EF Core), Messaging (Dapr), Mapping (Mapster), OpenApi
└── MicroCommerce.Web/ # React + Vite + TypeScript (pnpm)
tests/
├── MicroCommerce.UnitTests/ # no infrastructure
├── MicroCommerce.FunctionalTests/ # API in process, Testcontainers
└── MicroCommerce.IntegrationTests/ # whole AppHost, Aspire.Hosting.Testing
Migrations are applied at startup in Development. Each area has its own DbContext, schema and migrations. To add one:
cd src/MicroCommerce.ApiService
dotnet ef migrations add <Name> --context AppDbContext -o Infrastructure/Data/Migrations
dotnet ef migrations add <Name> --context AccountsDbContext -o Features/Accounts/Data/Migrations
dotnet ef migrations add <Name> --context CatalogDbContext -o Features/Catalog/Data/Migrations| Suite | Covers | Needs |
|---|---|---|
tests/MicroCommerce.UnitTests |
Domain rules, validators, the Mapster configuration (Compile()), request context, exception handling, activities with NSubstitute doubles, and the workflow on Temporal's time-skipping test server |
No database, container or running service. The Temporal test server binary is downloaded on first use and cached. |
tests/MicroCommerce.FunctionalTests |
Every endpoint in process (FastEndpoints.Testing) with signed test tokens: success, validation, 401 and 403. Also the inbox de-duplication, and the workflow against a real Temporal server | A container runtime (Testcontainers: PostgreSQL, Temporal and Azurite) |
tests/MicroCommerce.IntegrationTests |
The whole AppHost (Aspire.Hosting.Testing): every resource healthy, real Keycloak tokens against /api/me, the sample process end to end through the real Dapr sidecar, and an API restart while a process waits |
A container runtime and the Dapr CLI (dapr init --slim) |
src/MicroCommerce.Web (Vitest) |
The home page signed out, signed in, and with the API unreachable; the Variant grid, price, image order and authenticated image helpers | Nothing |
dotnet test --solution MicroCommerce.slnx # every .NET suite
dotnet test --project tests/MicroCommerce.UnitTests # unit tests only
pnpm --dir src/MicroCommerce.Web test # web app unit testsIntegration tests start the AppHost with --EphemeralResources=true: they use throwaway containers
on random ports and random seeded passwords, and remove everything when the run ends. They never
touch your dev containers or data.
The Marketplace performance check (10,000 Products, p95 under 1 s) is an explicit test, so it only runs when asked for:
dotnet test --project tests/MicroCommerce.FunctionalTests --explicit onlyThe restart test (Category=Restart) also runs on its own for the 10-run reliability check:
for i in $(seq 10); do dotnet test --project tests/MicroCommerce.IntegrationTests -- --filter-trait "Category=Restart" || break; done