Als je een applicatie hebt gebaseerd op een microservices architectuur, heb je waarschijnlijk te maken met meerdere services die met elkaar communiceren via HTTP-protocollen. Een van de uitdagingen van deze architectuur is het traceren en debuggen van HTTP-verzoeken die door verschillende services heen stromen. Hoe kun je bijvoorbeeld achterhalen waar een verzoek is mislukt, of hoe lang een verzoek heeft geduurd om te worden afgehandeld?
Een mogelijke oplossing is om gebruik te maken van correlation ids. Dit zijn unieke identificatoren die je kunt gebruiken om de verschillende verzoeken en antwoorden die bij een bepaalde actie horen aan elkaar te koppelen. Door correlation ids toe te voegen aan je HTTP-headers, kun je de levenscyclus van een verzoek volgen en analyseren, en eventuele problemen sneller opsporen en oplossen.
In deze blog ga ik je laten zien hoe je correlation ids kunt gebruiken in ASP.NET microservices. We zullen een voorbeeldproject gebruiken dat bestaat uit twee ASP.NET Core Web API-projecten: een OrderService en een ProductService. De OrderService is verantwoordelijk voor het afhandelen van bestellingen, en de ProductService is verantwoordelijk voor het leveren van productinformatie. De OrderService roept de ProductService aan om de productdetails op te halen voor een gegeven bestelling.
Correlation ids zijn unieke ids die je kunt gebruiken om verschillende taken te correleren aan eenzelfde macro-operatie. Door ervoor te zorgen dat elk antwoord een unieke id heeft, kun je het verzoek volgen, monitoren en debuggen wanneer het door meerdere services heen gaat.
Omdat verschillende servicecomponenten betrokken kunnen zijn bij de uitvoering van het verzoek, heb je een methode nodig om al deze servicecomponenten te koppelen aan het verzoek; d.w.z., je hebt een manier nodig om al deze servicecomponenten aan het verzoek te koppelen.
Het idee is dat de service die het verzoek initieert een correlation id genereert en deze doorgeeft aan de volgende service. De tweede service geeft deze correlation id door aan de derde service, enzovoort. Als er een fout optreedt, kan de betreffende service de foutmelding loggen samen met de correlation id. Op deze manier kun je gemakkelijk achterhalen welk verzoek de fout heeft veroorzaakt, en welke services erbij betrokken waren.
Om correlation ids te gebruiken in ASP.NET microservices, moet je de volgende stappen uitvoeren:
Om dit te laten zien, zullen we gebruik maken van het voorbeeldproject dat bestaat uit twee ASP.NET Core Web API-projecten: een OrderService en een ProductService. De OrderService is verantwoordelijk voor het afhandelen van bestellingen, en de ProductService is verantwoordelijk voor het leveren van productinformatie. De OrderService roept de ProductService aan om de productdetails op te halen voor een gegeven bestelling.
Om een correlation id te genereren voor elk inkomend verzoek in de eerste service (in dit gevalde OrderService), kun je gebruik maken van een middleware die de correlation id genereert en toevoegt aan de HTTP-context. Een middleware is een stukje code dat wordt uitgevoerd tussen het ontvangen en het verzenden van een HTTP-verzoek.
De CorrelationIdMiddleware-klasse die we gaan gebruiken ziet er als volgt uit:
public class CorrelationIdMiddleware
{
private readonly RequestDelegate _next;
public CorrelationIdMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Haal de scoped Context op.
var correlationContext = context.RequestServices.GetRequiredService();
// Check of er al een correlation id meegestuurd is.
if (context.Request.Headers.TryGetValue(CorrelationIdContext.CorrelationIdHeaderKey, out var correlationIds) && correlationIds.Any())
{
correlationContext.CorrelationId = correlationIds.First();
}
// Voeg het correlation id toe aan de response headers
context.Response.OnStarting(state => {
var httpContext = (HttpContext)state;
httpContext.Response.Headers.Add(CorrelationIdContext.CorrelationIdHeaderKey, correlationContext.CorrelationId);
return Task.CompletedTask;
}, context);
// Voeg het correlation id toe aan de Serilog LogContext. Zo kan het id aan iedere log regel binnen de scope worden toegevoegd.
using (LogContext.PushProperty("CorrelationId", correlationContext.CorrelationId))
{
await _next.Invoke(context);
}
}
}
In deze middleware maken we gebruik van een CorrelationIdContext. Deze registreren we als scoped service, zodat er 1 context is per request. Deze klasse ziet er als volgt uit:
public class CorrelationIdContext
{
public const string CorrelationIdHeaderKey = "X-Correlation-Id";
public string CorrelationId { get; set; } = Guid.NewGuid().ToString();
}
Om deze middleware en context te registreren in de pipeline, moet je de volgende regels toevoegen aan de Configure-methode en de ConfigureServices-methode in de Startup-klasse van de OrderService:
// Configure
app.UseMiddleware();
// Configure Services
services.AddScoped();
Om de correlation id toe te voegen aan de HTTP-header van het uitgaande verzoek naar de volgende service (in dit geval de ProductService), kun je gebruik maken van een HttpClientFactory die een HttpClient creëert met een vooraf geconfigureerde header.
Om een HttpClientFactory te gebruiken om de correlation id toe te voegen aan de HTTP-header van het uitgaande verzoek, kun je de volgende stappen uitvoeren:
De code om de HttpClient te registreren ziet er als volgt uit:
services.AddHttpClient(client => {
client.BaseAddress = new Uri(“https://localhost:5001”);
}).AddHttpMessageHandler();
De code om de CorrelationIdHandler te implementeren ziet er als volgt uit:
public class CorrelationIdHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CorrelationIdHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var context = _httpContextAccessor.HttpContext?.RequestServices?.GetService();
if (context != null)
{
request.Headers.Add(CorrelationIdContext.CorrelationIdHeaderKey, context.CorrelationId);
}
return base.SendAsync(request, cancellationToken);
}
}
Om het correlation id te lezen uit de HTTP-header van het inkomende verzoek in de volgende service (in dit geval de ProductService), kun je gebruik maken van een middleware component die de correlation id leest uit de header en toevoegt aan de HTTP-context. Dit is vergelijkbaar met de middleware die we hebben gemaakt voor het genereren van correlation ids in de eerste service. We kunnen dan ook exact dezelfde middleware gebruiken en toevoegen aan het project.
Als je meer dan twee services hebt die met elkaar communiceren via HTTP-protocollen, moet je stap 2 en 3 herhalen voor elke volgende service die wordt aangeroepen. Op deze manier kun je ervoor zorgen dat elke service de correlation id ontvangt en doorgeeft aan de volgende service.
Om gebruik te maken van de correlation ids voor het traceren en debuggen van je HTTP-verzoeken, moet je ervoor zorgen dat je ze logt samen met eventuele foutmeldingen of andere relevante informatie. Op deze manier kun je gemakkelijk achterhalen welk verzoek de fout heeft veroorzaakt, en welke services erbij betrokken waren.
In dit voorbeeld maken we gebruik van Serilog. We hebben het correlation id toegevoegd aan de LogContext. Maar in sommige gevallen is er configuratie nodig om deze ook te loggen. Wanneer je gebruik maakt van bijvoorbeeld Splunk is dit niet nodig. Wanneer je een output template gebruikt zou je [CorrelationId] hieraan toe kunnen voegen.
In deze blog heb ik je laten zien hoe je correlation ids kunt gebruiken in ASP.NET microservices. We hebben een voorbeeldproject gebruikt dat bestaat uit twee ASP.NET Core Web API-projecten: een OrderService en een ProductService. De OrderService is verantwoordelijk voor het afhandelen van bestellingen, en de ProductService is verantwoordelijk voor het leveren van productinformatie. De OrderService roept de ProductService aan om de productdetails op te halen voor een gegeven bestelling.
Ik hoop dat deze blog je heeft geholpen om te begrijpen wat correlation ids zijn, waarom ze nuttig zijn, en hoe je ze kunt gebruiken in ASP.NET microservices.
https://www.pexels.com/photo/rock-formation-2335126/