Pavel Panchekha

By

Share under CC-BY-SA.

Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author and do not necessarily reflect the views of the National Science Foundation.

Architecture Patterns

Applications often implement one of a small number of architecture. These core architectures are like "design patterns", except they are patterns in dataflow and computation structure, not in class design and structure.

I've documented some patterns documented below, along with links for common problems those architectures face.

Event loop

Many applications have a "main loop", which processes events and fires off callbacks as a result of these events. For example, the read-eval-print loop in an interpreter's interactive shell is precisely this pattern, as is the main loop in in a GUI library and as are monitoring programs and tools like Dropbox.

This architecture makes sure all events are processed, and makes it easy to add new types of events or callbacks. This architecture usually requires waiting on one of several types of requests, for example using the OS primitives select or poll.

Garbage management

Most programs manage memory, clean up temporary files, rotate logs, or clear old session data. These tasks are different forms of garbage collection. They can be done statically, maintaining a stack—but make sure to handle exceptions!1 [1 Some languages have constructs, like unwind-protect or Python's with, to help with that.] They can also be done by counting references (such as migrating a cluster of computers from one data center to another, where connections between machines must be maintained until they close). Or finally some sort of mark-and-sweep (often combined with timeouts for things like session cleanup) can be used.

Sometimes, language features intended for real garbage collection can be used, like the RAII pattern in C++. But usually the language garbage collector won't work well for application garbage like temporary files. It'd be interesting if advanced garbage collection algorithms correspond to interesting garbage management approaches.

Hierarchical Storage

Configuration file, logging data, and class hierarchies all imply some kind of hierarchical storage. Usually, these systems grow file system features like links (see YAML), mounting, and different filesystem types. Hierarchical storage is usually easy to code and to use, but consider whether tags or relational data would not be more flexible.

Versioned data

Data is often versioned, whether this involves keeping the previous value of some variable in a loop for comparison, guarding from concurrency issues, or providing fallbacks and backups if anything goes wrong. Version control systems are a good place to take inspiration. In simple uses, you never need to merge two values. If you do, it immediately becomes a hard problem and you likely start growing features like branches. The Operational Transform is a principled way to deal with those issues.

Servers

A server model, where chunks of code execute separately and communicate through well-defined interfaces, is present in lots and lots of code. It's the core of object-oriented programming, after all. The servers might run in their own threads and push notifications to anyone interested; eventually, servers are allowed to fail, so server monitoring and restarting becomes important. Erlang is one language kind of designed for this model.

Scheduling

A lot of code has to maintain a list of tasks and choose which to do next. Eventually you grow a concept of subtasks (so that when you abort a parent task you abort subtasks), of priorities, and of idle tasks. Operating system process management is the "proper" version of this. In some cases, you can find an algorithm that always chooses the correct process to run next, like with A* search.

Storing metadata

One often has data and metadata; one usually cares about data, but sometimes one cares about metadata as well. Depending on the particular application, you may even want the metadata to influence (implicitly) how a computation proceeds, much the way that the "You can't do that" metadata of an exception causes a stack unwind.

The challenge in this case is to avoid having to mention the metadata constantly, yet still be able to refer to it when necessary. Monads and similar structures like arrows are one good way to handle this issue.

Conclusion

Most of these high-level patterns are not yet abstracted by language features, especially because they often need to be customized to the task at hand: a library that can expand to cover the scope of all garbage management will be unwieldy for any particular job. Still, perhaps there is some sort of common core in each pattern that can be understood, studied, and abstracted.

Footnotes:

1

Some languages have constructs, like unwind-protect or Python's with, to help with that.