Introduction
In the first instalment about pseudotime, I proposed an boldly broad, general definition of "pseudotime", in order to make the concept accessible and immediately actionable in, perhaps, any application (my career has mostly been in business web applications). There, we defined pseudotime as an application-specific sense of time, defined less by the external clocks, but rather by concurrency control (choosing what is and isn't allowed to be "simultaneous").
In this post, I'd like to explore some general strategies that will allow us to "leverage" pseudotime, to get the most usefulness out of it. I'll take it as a given that in the examples we looked at in that earlier post (Git, chess, event sourcing, undo stacks), the usefulness of pseudotime is clear. We just want to make that usefulness repeatable in our projects.
Pseudotime helps us organise the past and can be used to refer to stable, consistent snapshots of our software state. it's a "handle" or a pointer to something useful. Whenever we access (and especially when we serve) the state of our software, that access and it's deriviative outputs can be annotated with a pseudotime to create additional opportunities for ourselves and the customers.
Situations or "Worlds"
The first thing that will give pseudotime lots of usefulness is to allow, where appropriate, multiple entities (and entity types) to be "covered" by the same "timeline" or a "world" whose change is housed in a series of pseudotime moments. 2 different online chess games don't share the same pseudotime (sequence of "turns") timeline, but it would be absurd to consider every piece on the chess board to have it's own timeline (which only advances when that piece moves). Thus, I propose that we follow something like the following rule:
If a decision to update entity A depends in any way on entity B, then A and B belong to the same "world" - a pseudotime timeline.
In a collaborative document (e.g. google docs), I might change paragraph 4, depending on how you, recently, changed paragraph 3. So the paragraphs are "together" like that.
Concurrency Control
Grouping related entities (even across many types) into a few, or ideally one "situation" (which observes pseudotime), makes it practical to serve up a pseudotime value along with a presentation of the entities belonging to the situation. Even without access to "past states" of the system, we can imagine some rudimentary tools that leverage the associated pseudotime:
-
If an error occurs during the "read" of the situation, we can check if the situation has changed since the error report, by checking for the equality of the current pseudotime to the reported one. If they're the same, before the time ticks (as a result of a change), we can save a copy of the full situation to use in a debugging session or a regression-oriented unit test,
-
A client wishing to implement "optimistic updates" or "offline mode" (e.g. as a native mobile app might) can do something basic like "queue up" it's local modifications as a sequence of commands/change-requests that use a particular pseudotime as their anchor, a "basis". When the authoritative server is reachable again, we may confirm that base hasn't changed and our "optimism" was warranted. If the server comes back with a different pseudotime value, we can implement a kind of "rebase" (Git's term) or scrap the changes (hopefully while informing the user of such outcome)
-
A human looking at a web page with a snapshot of the data may want to know, that their form submission was submitted in a context of out-of-date data, and may choose to revise or abort their submission.
-
Even without access to whole "past states", since multiple entities update together and belong to the same pseudotime, we may choose to implement a rudimentary audit table, wherein modifications to the world are annotated with a kind of "commit message" that makes sense for the application.
Access to the Past
Once we're able to label or refer to a whole, consistent state of many entities via their world's pseudotime, retaining and storing past revisions of the situation offers even more opportunities. And for good reasons. Disk storage is cheap, but history is valuable.
-
Even if the situation continues to change, we can respond to earlier error reports, by loading and exploring the state of the data at the time when the reported error occurred.
-
Business analytics become much more first-class accessible. The application can respond to arbitrary queries "as if" the time was of the past.
-
Collaborative software relies on surfacing an "activity history" for human consumption and interpretation. This and similar features (e.g. "undo") become much easier to facilitate, because past states are there and accessible via their pseudotime.
-
Testing and QA tasks can become much more efficient if any (even most rudimentary) form of "undo" is available. Sometimes you need to create a situtation and try a few variants ("branches" if you will) based on that situation. An "undo" feature needs a handle to refer to which, consistent, past state to reset to. Or a basic "save/load" feature can be made, which still benefits from being annotated with the associated pseudotime.
Other attempts at "history" are far less accurate. Logging or CDC (change data capture) don't reveal the time structure of the application and it's domain - you can't tell whether some of the changes are "together" as part of an atomic transaction.
Undo
Since pseudotime gives us a "handle" on a past state, then, if that past state is persisted, we can even offer an "undo" feature, imagine that! "Undo" has fallen out of fashion ever since online software became more collaborative and described almost as more about "communication" rather than "authoring of artifacts". Though, of course, because "undo" is such a useful concept, we don't have to look far to find it in some of the most useful software today - again, Git/Github and something like Google docs come to mind. You can choose an older revision and "revert" to it. To even begin building a useful "undo" or "revert" facility in our web applications, we need the ability to "refer" to an older revision via some handle. Pseudotime makes for a great handle on the past.
Conclusion
As you see, pseudotime can be used to describe the common thread between various kinds of useful software features across many contexts - revision history, enforcing of consistency via concurrency control, undo, collaboration, etc. Give it sufficient scope and/or access to the past, and you can leverage it to make your software much easier to maintain/debug or to provide first-class-valuable functionality to it's operators.