Hacker News new | ask | show | jobs
by danbruc 3029 days ago
This looks much worse than it actually is. You could implement it in a much flatter way.

  function HandleRequest(request : Request) : Response
  {
    LogRequest(request)
  
    CheckAuthentication(request)
    CheckAuthorization(request)
  
    var response = DispatchAndHandleRequest(request)
  
    LogResponse(response)
  
    return response
  }
This way you would have no deep stack traces and all would be fine. But only until you need to do some additional work somewhere right in the middle, say patch malformed requests between authorization and dispatch. With the above implementation you would be somewhat screwed, the best you could do would be reimplementing HandleRequest() with your additional step in the middle.

But with implementations like those show in the article you just have a list of steps with a common interface, each step does its work and then the next step gets called. If you need to do something new somewhere in the middle, you just add a new step to the list of steps in the right place and you are done, possibly simply by putting the class name of the new step in a configuration file.

  var steps =
  {
    new LogRequestStep(),
    new CheckAuthenticationStep(),
    new CheckAuthorizationStep(),

    // This stupid XYZ client always sends broken requests.
    new PatchMalformedRequestFromXyzStep(),

    new DispatchAndHandleRequestStep(),
    new LogResponseStep()
  }

  function HandleRequest(request : Request) : Response
  {
    var context = new Context(request)

    foreach (var step in steps)
    {
      step.Execute(context)
    }

    return context.Response
  }
This would still avoid deep stack traces because we are iterating over all the steps but note that with this implementation we would not really be able to abort processing the request somewhere in the middle, say if the authorization check failed, but we could fix this by adding a flag to the context and check it inside the loop after a step executed. But a more serious limitation is that every step only gets one chance to act, note for example that we have two separate steps for logging the request and the response.

Imagine we wanted to log the request duration, then we would need a step getting the current time at the beginning and another one getting the current time after the request was handled at the end. And the first step would have to somehow communicate the processing start time to the second one, possibly by storing it in the context. A much more elegant solution is to organize the list of steps as a linked list with all steps looking like this.

  function Execute(request : Request) : Response
  {
    PreprocessRequest(request)

    var response = GetNextStep().Execute(request)

    PostprocessResponse(response)

    return response
  }
This creates those deep stack traces but it also creates a huge amount of flexibility and extensibility. It surly looks crazy if you do not need it, but when you need it, it is really easy with this model.