Lab 5: Run It Like Production
30 minutes · Starts from checkpoint-4 · Ends at checkpoint-5 (the finished Roastery API)
Steps 1 and 2 need only the SDK. Step 3 needs Docker or Podman. Step 4 is the first to be cut if the day is running long.
Step 1: Add the Aspire projects
From the src folder:
dotnet new aspire-apphost -n Roastery.AppHost
dotnet new aspire-servicedefaults -n Roastery.ServiceDefaults
dotnet sln add Roastery.AppHost Roastery.ServiceDefaults
cd Roastery.AppHost
dotnet add reference ../Roastery.Api ../Roastery.Payments
cd ../Roastery.Api
dotnet add reference ../Roastery.ServiceDefaults
cd ../Roastery.Payments
dotnet add reference ../Roastery.ServiceDefaults
Replace the AppHost's Program.cs with the version on the concepts page.
In both the API and the payments project, add builder.AddServiceDefaults(); immediately after CreateBuilder, and app.MapDefaultEndpoints(); near the other Map calls. Then remove the hand-written OpenTelemetry block and the two MapHealthChecks lines from Lab 4; ServiceDefaults now owns them. Keep the AddDbContextCheck registration; ServiceDefaults adds the endpoints, not your checks.
Change the payment client's base address to https://payments and delete the Payments:BaseUrl setting.
Step 2: Run it and read the trace
Set Roastery.AppHost as the startup project and run it (or dotnet run --project src/Roastery.AppHost). The dashboard opens in the browser.
- Confirm both
apiandpaymentsshow Running and both health endpoints are green. - Using
module-5.http(the base URL variable now points at the address shown in the dashboard), get a staff token and a customer token, then place an order as the customer. - Open Traces, find
POST /api/orders, and identify: the EF Core spans, the outbound call topayments, and any retry spans. Place a few more orders until you catch a retry. - Advance the order to
Readyas staff, then open Structured logs and find theOrderReadyNotifierentry. Click its trace id and confirm it links back to thePUTrequest that caused it.
Step 3: Build and run the container
dotnet publish src/Roastery.Api -c Release --os linux --arch x64 /t:PublishContainer -p:ContainerRepository=roastery-api
docker images roastery-api
Run it with configuration supplied as environment variables. Because the container cannot reach the Aspire-hosted payments service by name, point it at the standalone payments process from Lab 4 (start it in another terminal first):
docker run --rm -p 8080:8080 \
-e ASPNETCORE_ENVIRONMENT=Development \
-e ConnectionStrings__Roastery="Data Source=/tmp/roastery.db" \
-e Jwt__SigningKey="roastery-dev-signing-key-change-me-32-bytes!!" \
-e Jwt__Issuer=roastery -e Jwt__Audience=roastery \
-e services__payments__https__0="https://host.docker.internal:7281" \
roastery-api
Browse to http://localhost:8080/api/products. It is the same code, the same configuration keys, and a different environment.
tip
docker inspect roastery-api shows the user the SDK chose (app, not root) and the exposed port. Neither required a line of Dockerfile.
Step 4: Version the API
cd src/Roastery.Api && dotnet add package Asp.Versioning.Http
Add the AddApiVersioning registration and the version set from the concepts page. Move the MapProductEndpoints() and MapOrderEndpoints() calls under the versioned group so every URL becomes /api/v1/.... Update the base path variable in module-5.http.
Add one v2 endpoint: GET /api/v2/products/{id} that returns the ProductDto plus an Origin string. Map it with .MapToApiVersion(2). Refresh Scalar and confirm both v1 and v2 documents exist and the Api-Supported-Versions header appears on responses.
Stretch goals
- Replace SQLite with Postgres:
builder.AddPostgres("db").AddDatabase("roastery")in the AppHost,.WithReference(db)on the API, theNpgsqlEF Core provider, and a fresh migration. Note how little of the API changed. - Deploy:
azd initin thesrcfolder, choose the AppHost, thenazd up. Requires an Azure subscription and about ten minutes. - Add the Aspire Redis resource and switch
HybridCacheto use it as the L2 withAddRedisDistributedCache("cache").
Common pitfalls
- Health endpoints 404 under Aspire.
MapDefaultEndpoints()is missing, or you deleted theAddDbContextCheckregistration along with the oldMapHealthCheckslines. paymentsdoes not resolve.AddServiceDefaults()is not called in the API (it registers service discovery), orWithReference(payments)is missing in the AppHost.- Container starts and immediately exits. Read
docker logs; it is almost always a missingJwt__SigningKey. - Versioned routes 404. The route template must be
/api/v{version:apiVersion}exactly, and the group needsWithApiVersionSet.
Checkpoint
git add -A && git commit -m "Lab 5 complete"
# or: git checkout checkpoint-5
That is the finished Roastery API. Head to the Wrap-up.