This is a scratch pad for the requirements (bahaviors) of Chain. What it should do and why. Some API examples are also provided to show how.
A “Link” is a piece of the application that can be developed in a focused, reasonably isolated way.
Chaining has proven it’s worth in many systems and allows decoupled components to work together through a minimal interface.
By using a non blocking transition to push the request forward through the chain, callstacks are minimised, and are eliminated between components. This is important to allow many requests to get a slice of the processor. Considering that the event loop has no threads, any one request that holds the attention of the event loop for it’s entire request means that no other request, or out of request process can use the processor till it’s finished. By forcing components to use async transitions to pass the reqest around it gives up control of the event loop so that many things may be processed in a much more timely manner. It’s easier to rely on links being async if they’re all async. Imagine iterating through a huge array inside a browser. Although there’s no IO wait occuring and CPU utilisation is maximised. The event loop is also stalled waiting for the calcs to finish. By spliting things into smaller chunks, we can be more responsive on the server.
Ryah: “With no concurrency, comes great concurrency”
Although the document below describes the things that should be considered when building a chaining approach. The end result should be very simple for the basic case, and allow the complex. Examples of how Chain achives this will be given at the end:
Moving Around the Chain
Moving around the chain of links is
- Provide a default path through the chain
- When using a link, it needs a mechanism to join the links together in a default way such that each link can find the next “default” link in the chain.
- The construction of a path through the chain should not be rigidly envorced. That is, each link should be able to decide what the next link is if any
- Pass a request forward to the next link in the default chain
- Passing a request forward in the chain should be flexible and simple.
- Pass a request forward to an arbitrary link
- A router must be able to pass to an arbitrary link. By ensuring that the link is resonsible for passing the request to the correct place an omnipotent builder is not required, and the community can develop flexible links that work togehter.
- Pass a request back one step (Note, it should only ever go one step back)
- A link should be able to register a function it wants to execute if the request comes back through it, and has not yet been completed. This is the default way chains should work.
- An example of this being useful is if the body of a response is a file and it should be chunked. There are many other examples of useful “middleware” in rack including ETAG, Caching, authentication etc.
- Not all links want or need to modify the request on the way out. These links should not see the request on the way out then.
- A callback stack should be maintained such that when a link recieves a request it can add a function to execute if the request comes back.
- It is important that the callbacks only execute one at a time. Any given callback may decide to either send the application to a differnet link, or may respond directly.
- Stop a request without passing it back (complete / websocket / long connection etc)
- Any link should be able to finish a response at any time. If a link knows what needs to be done, then it should be able to respond and finish the response without passing back to links that are expecting to be able to modify the response before it goes to the client.
In callstack based setups, each component sees the request on the way in, and on the way out. Callbacks are a way to see the request “on the way out”. There are a number of different callbacks required to ensure that long open connections can function correctly.
- Add callbacks so this link sees the env on the backwards traversal.
- Can either act to send it to the client, or modify the response.
- These callbacks may not actually run if something downstream finishes the response and does not continue the callback stack.
- It is inefficient to enforce each link to deal with the request on the way out. By using callbacks, each link that cares can define callbacks to operate on the response before it goes to the client.
- Add callbacks to modify the headers prior to them being sent
- Need to be able to modify the headers hash. Links may want to set content-length content-type or other header values
- Return an array of arguments for sendHeaders
- Add callbacks to modify body chunks prior to them being sent
- Individual chunks should be accessible. eg. gzipping each chunk
- Return an array of arguments for response.sendBody
- When streaming, this is the only way an upstream link may effect the sendBody response. Again this is more efficient than requireing each chunk to pass through each link regardless of the links requirements.
- Add callbacks to perform actions after a response is finished
- Need to run after the response is finished. This way the client gets the response asap, and anything later can be run after the response is finished.
- Mail, Logging, Auditing, DB Connection Cleanup etc.
Building A Chain
- Join links together to provide a default path through the application
- These may not be used by the links but by giving a default path through the chain, you’re setting up the application as you expect it to run in default cases.
- Only Links can be added to a Link. Chains are constructed one link at a time.
It’s easy to stream in Chain. Then environment gives you access to the raw response object. You can stream by using the raw response object, and if you finsih the response with response.finish(), you can.
By doing so you’re finishing the response and you shouldn’t invoke the callback functions.