Runtime and observability with Aspire
After Lino generates services, web apps, and infrastructure resources, Aspire helps run everything locally with visibility. Observability is not a luxury: it shows whether APIs, databases, cache, messaging, and workers are healthy.
In applications with events, Outbox, workers, Redis, RabbitMQ, and multiple modules, many problems do not appear only on screen. They appear in logs, queues, jobs, migrations, parameters, and runtime resources. This section shows how to look at the solution as a running system, not only as generated files.
Running with Aspire
The AppHost centralizes local execution for the solution. It starts services, web apps, and dependencies such as PostgreSQL, Redis, RabbitMQ, and workers.
dotnet run --project src/Aspire/AppHost/<ProjectName>.AppHost.csproj
Use the dashboard to inspect logs, variables, endpoints, and resource status. If a service fails to start, begin with the AppHost and the dependencies it injects.
Aspire makes the local topology visible: APIs, web apps, databases, cache, message broker, and workers appear as related resources. This view helps find configuration failures that would stay hidden in manual execution, such as a wrong connection string, a missing parameter, a service without an endpoint, or a dependency that has not started yet.
- Logs: track startup errors, authentication failures, migration problems, and worker messages.
- Endpoints: confirm ports, URLs, health checks, and links to web apps and APIs.
- Parameters: validate secrets and variables propagated to each service.
- Dependencies: verify that Redis, RabbitMQ, and the database have been provisioned before diagnosing the application.
Database resources
Services generated by Lino usually have their own DbContexts, migrations, and connection strings. In modular systems, validate that each module uses the expected database/schema and that migrations were applied in the correct place.
- Confirm connection strings per environment.
- Run migrations before testing flows that depend on a new schema.
- Monitor connection failures and query time.
- Avoid using a production database during local execution.
In a simple service, the database follows the service. In a modular service, the database belongs to the service, but each module can have its own schema, DbContext, migrations, and scripts. This separation also needs to appear in operations: when applying migrations, confirm the service, module, provider, and environment before executing.
Database failures often appear as API errors, but the cause may be in the AppHost, the connection string, a pending migration, a missing schema, or a local credential. For that reason, diagnosis should start with the full chain: Aspire parameter, active database resource, applied migration, correct DbContext, and endpoint calling the expected module.
Redis and cache
Redis can be used for cache, temporary state, tokens, rate limiting, or other resources depending on the enabled templates. Cache improves performance, but introduces invalidation and eventual consistency.
Document keys, expiration time, and the origin of the data. Never store sensitive secrets in cache without understanding encryption, expiration, and access.
When the project uses distributed cache, multiple instances can share values, which is useful for horizontal scaling. When it uses only local in-memory cache, each process keeps its own copy. This difference affects production behavior, load testing, and data invalidation.
| Question | Why it matters |
|---|---|
| What is the origin of the cached data? | Defines how to recalculate the value when it expires or is invalidated. |
| What TTL is acceptable? | Determines how long the system tolerates potentially stale data. |
| Is the cache per tenant, per user, or global? | Prevents data leaks between contexts and poorly modeled keys. |
RabbitMQ and messaging
Messaging supports integration events and asynchronous communication. With Outbox, the application does not depend on publishing the message at the same instant as the transaction; the worker can try again.
- Check the generated exchanges, queues, and bindings.
- Monitor stuck messages and inactive consumers.
- Design idempotent handlers.
- Use correlation logs to follow the message path.
Integration events are useful precisely because the producer does not need to wait for all consumers. But this shifts part of the complexity to operations: messages can be delayed, consumers can fail, payloads can change, and handlers can run more than once. The flow documentation should make clear which event is published, who consumes it, and which local state is updated.
Shadow Entities depend on this observability. If a tenant or user entity is replicated to other modules by event, a consumer failure can leave the local copy outdated. The system needs to make it possible to diagnose the event, the message, the handler, and the update performed in the consumer module database.
Hangfire Dashboard
When background jobs are enabled, the Hangfire Dashboard helps inspect recurring jobs, failures, retries, and Outbox processing execution.
Protect the dashboard in shared environments. It exposes relevant operational information and should not be public without authentication and authorization.
Use the dashboard as a diagnostic tool, not as a replacement for logs and metrics. It shows queues, attempts, and execution history, but a complete investigation still needs a correlation id, structured logs, and business context to understand why a job failed.
- Retries: check whether the failure is transient or whether the job will keep failing indefinitely.
- Recurring jobs: confirm the interval, batch, and average execution duration.
- Outbox: observe stuck messages, reprocessing, and handlers with exceptions.
- Security: restrict access, because payloads and errors can reveal sensitive data.
Workers and background processing
Workers execute tasks that should not block HTTP requests: sending messages, Outbox processing, cleanup, synchronization, and external integrations.
- Use batches appropriate for the volume.
- Define retries and failure handling.
- Log errors with enough context to reprocess.
- Do not hide permanent failures in generic logs.
When adding a worker, also define frequency, batch size, failure behavior, and domain impact. A worker that reads 100 records every few seconds has different operational behavior from a daily job; both need to be observed and sized according to real volume.
Do not run in the background anything that must respond immediately to the user. Use a worker for later tasks, integrations, notifications, reprocessing, and maintenance. If the use case depends on the result to complete the transaction, treat it as a synchronous flow or model an intermediate state visible to the user.
