I'm recently working with microservices in Asp.Net Core. With microservices, there tends to be a lot of network connectivity. One service potentially calls many other services to build a final response. It's difficult to see where a request started, all the subsequent requests that happened back to the final result. Concurrency keys are a way to track a single request through all its dependancies.

The idea is when a request is made a concurrency key is generated. Every subsequent request passes the concurrency key with it. This allows a developer search logs for the concurrency key and requests based on the original. So how do we implement something like this in Asp.Net core?

Concurrency keys with Middleware

Writing simple middleware allows us to inject concurrency key logic throughout the pipeline. If a request comes in and there's a concurrency key specified in the header, the key is left alone. If not, one is generated off the TraceIdentifier for the request and placed in the header.

public class ConcurrencyMiddleware
{
    private const string ConcurrencyKeyHeaderName = "X-Concurrency-Key";
    private readonly RequestDelegate next;

    public ConcurrencyMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public Task Invoke(HttpContext context)
    {
        if (context.Request.Headers.ContainsKey(ConcurrencyKeyHeaderName))
        {
            context.Request.Headers[ConcurrencyKeyHeaderName] = context.TraceIdentifier;
        }
        else
        {
            context
                .Request
                .Headers
                .Add(ConcurrencyKeyHeaderName, context.TraceIdentifier);
        }

        return this.next(context);
    }
}

I've provided some simple unit tests to make sure everything is working.

public class ConcurrencyMiddlewareTests
{
    private const string KEY = "X-Concurrency-Key";

    public ConcurrencyMiddlewareTests()
    {
    }

    private HttpContext BuildContext() =>
        new DefaultHttpContext();

    private ConcurrencyMiddleware BuildMiddleware() =>
        new ConcurrencyMiddleware(next: (inner) => Task.FromResult(0));

    [Fact]
    public async Task Invoke_Request_HasKeyFromTraceIdentifier()
    {
        var context = BuildContext();
        var middleware = BuildMiddleware();

        await middleware.Invoke(context);

        Assert.True(context.Request.Headers.ContainsKey(KEY));
        Assert.Equal(context.TraceIdentifier, context.Request.Headers[KEY]);
    }

    [Fact]
    public async Task Invoke_RequestAlreadyHasKey_ExistingKeyRemains()
    {
        var context = BuildContext();
        var middleware = BuildMiddleware();
        context.Request.Headers.Add(KEY, "Boom");

        await middleware.Invoke(context);

        Assert.True(context.Request.Headers.ContainsKey(KEY));
        Assert.True(context.Request.Headers[KEY].Count < 2);
    }
}

Once it is all built it can be registered in the Startup.cs file.

app.UseMiddleware<ConcurrencyMiddleware>();

Some developers automatically include the concurrency key in the response. I tend to not do that in order to prevent concurrency keys to wind up in requests for customers. Make sure customers can't pass their own concurrency keys as well. Hope this helps.

comments powered by Disqus