All about the time lens
In this episode, I introduce the time lens, and I posit a law about representing time in complex domains.
[00:00:00] The time lens.
[00:00:02] Hello. My name is Eric Normand and this is my podcast. Welcome.
[00:00:07] So I'm writing a book about executable specifications as a way of doing data modeling, and I'm organizing the book into different lenses. Each lens sheds some perspective on your problem, your software, and lets you get a little bit more information about it so that you can make better design decisions. So it's a kind of software design.
[00:00:39] And in this lens, we're talking about time. So I've said this before, but it bears repeating, that any sufficiently complicated model will include a notion of time.
[00:00:56] The classic example is accounting. So in, in theory, accounting is just keeping track of how much money you have, and you don't need time to do that. It's just how much money do I have right now?
[00:01:11] But it turns out that as your use cases expand, you start to want to ask questions like, Like, well, where did my money go? And when did I spend this? So instead of just tracking how much money I have right now, and every time I take money out, I subtract from that number, what accountants do, and they've done for hundreds of years, is to track the transactions. So they don't say... Okay, you had $100 and now you have $90. They say you moved $10 from your account to somebody else's account. They say which account. So you can keep track. And this has a notion of time in it because the transaction occurs on a certain day. So, that counts as time.
[00:02:14] Now, here's the thing. There's also a second notion of time, which is when you enter the transaction into the book. So, the book, the account ledger, is an append only log. So, you would say on this day, we were on this page, and we added a transaction, but the transaction could have happened in the past, but you wrote it down today. An example of this is if you forgot that you had spent some money on something back in December. And you wanted to count for the previous tax year. So you write on January 10th, I entered into the ledger that I moved $30 out of this account on December 15th.
[00:03:14] That's very possible that you didn't get around to writing it down yet. And that's a valid transaction. In the double entry bookkeeping system. So it has two notions of time. When the transaction happened and when it was entered into the ledger.
[00:03:36] Now this is pure conjecture. I don't have any real evidence of this except anecdotal evidence, but I'll posit a law, which is that as your model becomes more sophisticated, it will have to contain some notion of time. And in our coffee shop example, if you're in the editor of your coffee order, there is an implicit notion of time, which we haven't modeled yet, which is the order in which you are changing the stuff.
[00:04:17] And it will become explicit if your use case now says we need to be able to undo in the UI. So you add soy milk to your coffee. Now you want to undo that. What does that look like? Undo is basically go back in time. So you need a notion of time. You need a notion of the order in which operations happen.
[00:04:44] You can't just keep track of the current coffee or the current order. You also need to have some idea of how you got there so that you can undo them one step at a time. So now you've got an idea of steps, what constitutes a step, what constitutes an order of those steps and boom, there you go.
[00:05:09] Now there's two main ways of capturing that kind of order, that, that notion of time that would be useful in undo. And also useful in audits and things. How did it get to be like this? The first one is to capture the history. By this I mean, you keep around the old states. Every time you call a mutation function on the current order, it generates a new order. You keep that old one around. You put it into a list. And the last thing in the list, that's the current order, the current state.
[00:05:54] Now it could be the other way, right? It could be the first thing in the list is the last thing and you're adding to the front. It doesn't matter. That's not so important. What's important is that you've got this sequence of states that you've been through, and it's a history. And you can go backwards through that history by dropping stuff off the end. You want to go backwards? You drop one off the end. It's like going back in time.
[00:06:21] If you want to redo, you have to save those in another list. So you can go forward, too, by taking it off of one list, adding it to the end of the other list. You can figure that one out.
[00:06:33] So that's what I'm calling a history of states.
[00:06:38] The second way is a history of changes. So instead of keeping the entire state, a copy of the state at each point in time, you write down the change. And that change is usually related to whatever the human user is intending, right? So the human user intends to add soy milk, or let's say, whether or not they intend it, they click that button, and you interpret that as the intention to add soy milk. You write that down. They added soy milk. Then they click the button to add hazelnut. Boom. Write that down. They added hazelnut. They changed the size to large. Boom. Write that down. And so, if you could take all the stuff that they could do to this order and turn it into data, and we've already seen how to do that, it's this reify to interpreter refactoring, you turn it into data, and you can make a big long list of them. And if you want to know what's the current state, you can reduce over all of those actions, those pieces of data that are changing the state one step at a time. So you reduce over the whole thing and now you've got the current state.
[00:08:12] You can do optimizations. You can cache the current state, and so you only need to do the new operations as they come on, you know, you can keep a cache of it, but at any point, any sequence of those actions can be turned into a current state, a realized state of what that sequence of actions will lead to.
[00:08:38] Current state can be thrown away because the source of truth is that sequence of steps and undo is the same. Drop stuff off the end and undoes it. As you drop it, you'd have to save it in another list and then redo and push it back onto that list. You can figure that out.
[00:08:59] Now, this is simply an append only kind of log. If you want to have some other notion of time, like a transaction log has this other notion of time of when the transaction happened, not just when it was recorded, you can build that in, too. You really need to analyze your domain to figure out what you're going to need.
[00:09:24] And there you have it. I'm positing this law that any sufficiently sophisticated domain model is going to include a notion of time. We went over two ways of modeling time, the two main ways. One is the history of states and the other is the history of operations or changes to that state. And I think the history of changes is more interesting.
[00:09:54] It's more useful because you're capturing what the user intended. It's useful in more use cases. It's a little harder to implement, 'cause the states, you already have those. You save them.
[00:10:06] History of operations, you have to do a refactoring to create a data representation of each step. Well, there you have it. That's all I have to say on the time domain. It's important to think about time and how it relates in your domain.
[00:10:26] So, my name is Eric Normand. This has been another episode of my podcast. Thank you for listening, and as always, rock on!