0

I am playing with my simple personal project - a simple REST API application and I am currently struggling with a kind of design problem.

The problem:

How to insert an operation ID (request ID, an identifier of each operation) into each "layer"?

Context:

Let's say I have a UserRepository trait (interface) with 2 implementations (say, InMemory and Database), which is used by multiple services (say, CreateUserService, UpdateUserService). All these services are then used by a facade, let's say the UserFacade. This facade is called by the CLI tool or the Users REST handler. What I would like to be able to do is to create a unique Operation ID for each "operation" (request or a CLI call). This ID would be used for logging across the whole application, I would like to be able to access it in the repositories, services and facade. Later I would like to access the logs and trace how the request was processed by each of the layers.

Possible solutions that come to my mind are:

  • Pass this operation ID to each method as an additional parameter. I consider this ugly since the interface would be polluted by the extra parameter, unrelated to the business logic.

  • Create a whole structure of facades and services using the operation ID (the ID will be provided to each instance via the constructor for each request or CLI action). I like this approach but I think this would be performance/memory heavy because it would need to create a lot of objects for each processed request.

  • Some "global state" (thread-local...) storing the operation ID?

I am trying to not specify any concrete language since I consider this a general problem, more related to the design than the used language or technology.

1
  • Thread-locals are generally fine for cross-cutting concerns such as this, and most logging frameworks have built-in mechanisms to attach this kind of diagnostic data to each thread.
    – casablanca
    May 22, 2020 at 4:25

2 Answers 2

1

The issue is super common with one very common solution. If you're working on a java application, you create a request filter in which you configure an MDC (comes from slf4j and uses a ThreadLocal underneath). Normally you would either populate your MDC with a content of x-request-id header or generate a new UUID if the header is not present. Then the MDC parameters can be easily added to all your logs by configuring the log format. On top of that you can easily read the MDC value at any point in your application.

Here's an implementation example with spring https://medium.com/@d.lopez.j/spring-boot-setting-a-unique-id-per-request-dd648efef2b

2
  • Thank you very much this is helpful. The most interesting part is that MDC - so the general way would be to store the state in some thread-local storage and access it using the logger. The interesting part would be to see how this is implemented in other languages - currently, I am trying to learn Rust so I need to check how this would work there with rocket and logging there (I thing slog could be a good choice)
    – pstanko
    May 22, 2020 at 9:47
  • then watch out. Different languages have different solutions. Node.js (no threads available) for instance does not have ThreadLocal, but it uses async hooks. Rust might have something else. Either way, you want to configure a logger context before the entry point to the application (request filter/first middleware/whatever you have available to use in your framework) May 22, 2020 at 13:25
0

You don't need to inject the operationId into each component, you need to inject it into a logger and then inject the logger into the components.

eg

class MyProgram
{
    ILogger logger
    ProcessMesssage(message)
    {
        this.logger.OperationId = message.OperationId;
        var service = new Service(logger);
        service.Process(message);

    }
}

class Logger : ILogger
{
    Log(message)
    {
        file.write(message + this.operationId);
    }
}

Watch out for thread safety issues

1
  • That is right, I could use the logger/context/... something that will be injected into the service, but the problem is that the logger needs to keep some state that needs to be set and propagated (for example per each request). Either way, it looks like this should be solved by some request-scoped storage (thread-local variable - if we assume that each request is handled by separate thread). Thank you very much.
    – pstanko
    May 22, 2020 at 9:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.