The Spectate Model ================== The Spectate server follows a consistent tree-based model for storing the status of...well, whatever. File changes. Unit test results. Git commits. We don't care. It's optimized for rapid and systematic notification of changes. There are three interesting types of applications in the Spectate system: 1. The **Spectate server** provides REST-based access to the data store and notifies spectators when data changes. The reference implementation is written in Sinatra, with a Tokyo Cabinet B+ tree for a data store. Individual nodes or arbitrarily deep collections can be retrieved quickly and with the same mechanics. 2. **Agents** run on the client side and make updates to the server when something interesting happens. 3. **Spectators** are Web hooks which register themselves to the server on one or more nodes, and are notified of changes to those nodes or any of their children. There's also a command line client to add and remove services, but it's not as interesting. The rest of this document deals with the Spectate server, its data hierarchy and event model, and the API for communicating with it. The architecture of agents and spectators left for service implementers. Anatomy of a Node ----------------- The Spectate data store is a key-value store in which the keys compose a tree structure consisting of _nodes_ and _properties._ These types differ only slightly in their key declaration and are consistent with URI standards, allowing for a flat and simple implementation. * **Nodes** represent objects of any sort -- directories and files, tasks, unit tests, anything that can be graphed hierarchically. The _node key_ is the node's full URI for server retrieval: e.g., `http://localhost:20574/projects/spectate/doc/api.mdown`. Any node can have any number of child nodes and properties. _System nodes_ are ordinary nodes with a base name beginning with an underscore '\_'; this convention denotes data for Spectate's internal use, and is not ordinarily shown in query results. * **Properties** are attributes of nodes, and are denoted by a URI fragment identifier (aka a hash sign, aka '#'). The _property key_ is the property's full URI for server retrieval: e.g., `http://localhost:20574/projects/spectate/doc/api.mdown#last_modified`. Properties may not contain children, but may have multiple values, i.e. an array. ### (Stopped rewriting here.) You can also define any other properties or behavior you wish in subclasses. You can make an property _persistent_ by using the **spectates** declaration: class NiftySpectator < Spectate::Spectator spectates :niftiness, :coolness spectates :hipness, :quiet => true end Internally, persistent properties are just standalone key/value pairs; the key is the concatenation of the spectator's path, the URI _fragment_ identifier (i.e. the hash symbol, '#') and the property's name. (E.g. `/niftyThings/NiftyExample1#coolness`.) The _quiet_ option prevents hooks from running when the property is updated. Ordinarily, every property update is saved immediately, and each hook is notified upon each update. If you're assigning several properties at once or performing operations on a chain of spectators, you should use the **update** method: spectator.update do |s| s.niftiness = :very s.coolness = -40 s.hipness = "tragic" end **update** offers simple transaction behavior: it defers saving and hook announcement _for all spectators_ until the block is completed. This is useful for changes that trickle down to a spectator's children. If an exception is raised out of the block, all changes are canceled and nothing is saved or announced. If you want to defer saving and announcement _globally_, there is also a class method version (**Spectate::Spectator.update**) which supplies no parameters, but otherwise offers the same behavior as calling the instance method on the tree's root node. Sociology of Spectators ----------------------- [describe the tree] The following read-only attributes are available for convenience: * **path** is the URI path used to uniquely locate this spectator in REST requests. It also indicates the spectator's position in the storage tree, and is (not coincidentally) the key value used for hash storage. The path is the concatenation of the parent's path and a URI-safe version of the spectator's name. All of the following relationship attributes are based on transformations of the path. * **parent** is the parent spectator. * **children** is an array of all spectators immediately descended from this one. * **descendants** is an array of the spectator's children, all of _their_ children, and so on, in breadth-first order. * **siblings** is an array of all of the parent's children, excluding the current spectator.