# Request Response Foundation

## Goal

Implement request/response flows that are explicit about success, expected failures, and timeout behavior.

## Agent enforcement rules

### Do

- Prefer `record` types for request and response contracts. Classes and interfaces are supported when required by an existing contract or interoperability.
- Use `IRequestClient<TRequest>` from DI and respond with explicit success/negative result messages.
- Set cancellation and timeout behavior deliberately.

### Do not

- Do not use exceptions for expected business negatives.
- Do not rely on implicit destination semantics when explicit send semantics are required.

## Build checklist

- Use `IRequestClient<TRequest>` from DI for request callers.
- Use `context.RespondAsync<TResponse>` in consumers.
- Prefer multiple response types for business outcomes (for example, `OrderStatusResult` and `OrderNotFound`) instead of using exceptions for normal negatives.
- Configure a request client address explicitly when you need `Send` semantics instead of default `Publish` semantics.
- Set cancellation tokens and request timeouts deliberately.

## Example: request consumer

```csharp
public record CheckOrderStatus(string OrderId);
public record OrderStatusResult(string OrderId, int StatusCode, string StatusText);
public record OrderNotFound(string OrderId);

public class CheckOrderStatusConsumer(IOrderRepository orderRepository) : IConsumer<CheckOrderStatus>
{
    public async Task Consume(ConsumeContext<CheckOrderStatus> context)
    {
        var order = await orderRepository.Get(context.Message.OrderId);

        if (order == null)
            await context.RespondAsync(new OrderNotFound(context.Message.OrderId));
        else
            await context.RespondAsync(new OrderStatusResult(order.Id, order.StatusCode, order.StatusText));
    }
}
```

## Example: request caller with two response types

```csharp
public class RequestController(IRequestClient<CheckOrderStatus> client) : Controller
{
    [HttpGet("{orderId}")]
    public async Task<IActionResult> Get(string orderId, CancellationToken cancellationToken)
    {
        var response = await client.GetResponse<OrderStatusResult, OrderNotFound>(
            new { OrderId = orderId }, cancellationToken, RequestTimeout.After(s: 10));

        if (response.Is(out Response<OrderStatusResult> ok))
            return Ok(ok.Message);

        if (response.Is(out Response<OrderNotFound> notFound))
            return NotFound(notFound.Message);

        throw new InvalidOperationException("Unexpected response type");
    }
}
```

## Example: configure explicit request destination

```csharp
services.AddMassTransit(x =>
{
    x.AddConsumer<CheckOrderStatusConsumer>()
        .Endpoint(e => e.Name = "order-status");

    x.AddRequestClient<CheckOrderStatus>(new Uri("exchange:order-status"));

    x.UsingInMemory((context, cfg) =>
    {
        cfg.ConfigureEndpoints(context);
    });
});
```

## Guardrails

- Do not throw exceptions for expected outcomes such as "not found."
- Do not forget to start the bus/hosted service before first request.
- Do not omit cancellation/timeout decisions in API-facing code.
- Handle `RequestFaultException` and `RequestTimeoutException` at the application's boundary when a fault or timeout is possible.

## Verification

- `dotnet build`
- `dotnet test --filter Request`
- `dotnet test --filter Response`

## References

- [Requests (Concepts)](/concepts/requests)
- [Request/Response Unit Testing Guide](/guides/unit-testing/request-response)
