ArticlesEngineering

Envelopes

Cool properties of envelopes and apply them to program state

Envelopes are an interesting concept. Long-distance physical mail is a good example:

Someone writes a note and puts it an envelope marked with a recipient. That envelope is picked up by a mailman responsible for the sender's block. That mailman takes their bag of collected envelopes to their post office. That post office assigns the envelope to the correct outgoing bin. That bin is put into an airplane.

The plane arrives and someone takes the bin to the post office. The post office distributes the bin to the mail bags. Some mailman distributes their bag to the recipient's block. The recipient gets the envelope and opens it for the note.

Envelope properties

Thinking abstractly, there are quite a few "envelopes" in this process. The envelope, bag, bin, airplane. Even the note if the value is what is written on it. These envelopes range in complexity, but:

  • Contain things
  • Have information other than their content
  • Can be opened and inspected (disregarding legality)
  • Do not restrict what their contents are (excluding size)
  • Do not restrict how they are contained
  • Do not do anything on their own

We see a very similar process in computer networks. Frames, packets, and what not serve as envelopes for routing information.

The properties of envelopes are pretty cool. An envelope implemented in code would:

  • Be generic, like Envelope<T> where T could be anything or nothing
  • Express some information in itself

With a definition like Envelope<T> an envelope could contain another.

An envelope would also be side-effect free and as close to data as the language would allow as envelopes are inert.

Envelopes would be best built so they are inspect-able at each level just as a bin can be looked in and an envelope can be opened.

System properties

There is another value in envelopes: decoupled responsibility. The sender, receiver, mailmen, and pilots work in their own domains. Even though they are facilitating the same communication, they are only acting in the context of their own envelopes. This is a tree of behaviors interacting with messages.

Imagine we wanted to inspect every piece of mail, censor certain pieces of mail, or just block all mail between the sender and recipient. We could do these things at the plane-level as all letters flow through that point. If the sender and recipient were in the same city we could intervene at the post office level. If the sender was the receiver, everyone in the tree could intervene.

Imagine the bins were overflowing and it required two planes, or there was bad weather and there were no planes, or maybe a new post office hub combined the original post office with others. The sender and recipient do not care about changes in the tree.

Application state

Apply these features to the idea of application state. Application state is a tree structure wherein there are global and local states. We can communicate up and down this structure. We have choke-points for messages to be observed or manipulated. We have localized state relative to what program is handling which envelopes. We can grow out the state without breaking existing communications.

The decoupling means these layers would be simple to test. We could give a layer an empty envelope and test if it was routed correctly. If envelopes are just plain data, there is nothing to mock; tests are very small, input-output.

These are very powerful characteristics.

Further reading

There are implementations of this envelope architecture. The Elm Architecture (TEA) is built around these communication constructs. The programming language Elm and JavaScript framework Raj are good things to try to really gain an in depth understanding.

I created Raj based on TEA and have grown to appreciate this power and simplicity as I have built more apps and libraries leveraging it. I do recommend Elm, but Raj better highlights the full extent of these patterns because of JavaScript's dynamic and generic nature.