A concurrent customer dashboard
The registered customer has an identifier. A dashboard now needs that customer, order history and balance information. Those independent reads can overlap. Within each branch, a later step still waits for the preceding step's result.
The customer repository is the one already built in the walkthrough. The host supplies an HttpClient whose BaseAddress points to its account gateway. The two HTTP routes below are contracts for that gateway; this example does not implement the remote service. The host also owns a shared request Gate and keeps it alive until all dashboard users finish.
The result and client
import Global.System
import Global.System.Collections.Generic
import Global.System.Net.Http
public class CustomerDashboard
public readonly Customer as Customer
public readonly OrdersJson as String
public readonly BalanceJson as String
public constructor(customer as Customer, orders as String, balance as String)
self.Customer = arguments.customer
OrdersJson = orders
BalanceJson = balance
end constructor
end class
public class CustomerDashboardClient
private readonly customers as CustomerRegistrationService
private readonly http as HttpClient
private readonly requests as Gate
public constructor(service as CustomerRegistrationService, client as HttpClient, requestGate as Gate)
require service is not nothing
require client is not nothing
require requestGate is not nothing
customers = service
http = client
requests = requestGate
end constructor
public async action LoadAsync(customerId as Guid)
returns CustomerDashboard
require customerId <> Guid.Empty
concurrent all
returns customer as Customer, orders as String, balance as String
customer = run
let found = await customers.FindAsync(customerId)
if found is nothing then
throw new KeyNotFoundException("Customer was not found")
end if
return found
end run
orders = run
enter requests
let json = await http.GetStringAsync("customers/{{customerId}}/orders", Cancellation.Token)
return json.Trim()
leave
end run
balance = run
enter requests
return await http.GetStringAsync("customers/{{customerId}}/balance", Cancellation.Token)
leave
end run
end concurrent
return new CustomerDashboard(customer, orders, balance)
end action
end class
The orders branch trims its response as soon as that request finishes. It does not wait for the balance branch. A branch return completes its own body after required cleanup; it does not return from LoadAsync. The owning scope joins all producers before publishing the three values. No sibling can read another's unpublished result.
This example needs all three results, so a missing customer or failed remote request fails the dashboard. One producer failure propagates unchanged; several eligible failures become AggregateException in source order. The scope still joins its owned work. A try around LoadAsync can handle that outcome; a try inside a branch is appropriate only when the application has a real fallback for that branch.
Lifetime and policy choices
Create one Gate(4) for gateway requests shared across dashboard clients. The Gate limits requests across callers; it does not change the HttpClient's native request contract. It also does not automatically make unrelated mutable application state safe. Both the Gate and HttpClient are borrowed by the client and disposed by their owner after active dashboards settle.
The registration service remains scoped. Do not store a request-scoped service in a singleton dashboard client. This repository creates a separate connection per call; never parallelize several operations through one shared DbContext or live reader just because the syntax permits overlap.
This is not a cross-service transaction or a point-in-time snapshot. If access to orders depends on the customer lookup succeeding, perform that lookup and its access check before launching the HTTP branches. Running all three immediately is appropriate only when the host has already authorized the identifier and speculative independent reads are acceptable.
Newly registered customers remain inactive. A read-only dashboard does not activate them, and it does not bypass the separate order workflow's active-customer requirement.
For a list of customers, the bounded dashboard batch recipe adds concurrent for_each and source-ordered collect. The same shared Gate still limits total outgoing requests.
Previous: checks and changes. Next: queued reports.