-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rename module and types. Add "Key-Value" qualifier (#6)
* rename entire project to bitempura * update README * make args for memory.NewDB optional. flesh out example in README * rename Find/Put to Get/Set * rename id to key * rename Attributes to Value. make it the interface{} type * allow nil Values. add missing Put->Set renames * rename Document to VersionedValue * update model named to VersionedKV and update README * Update README
- Loading branch information
Showing
11 changed files
with
759 additions
and
768 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,98 @@ | ||
# bitemporal ⌛ | ||
# bitempura ⌛... ⏳! | ||
|
||
[![Go Reference](https://pkg.go.dev/badge/github.com/elh/bitemporal.svg)](https://pkg.go.dev/github.com/elh/bitemporal) | ||
[![Build Status](https://github.com/elh/bitemporal/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/elh/bitemporal/actions/workflows/go.yml?query=branch%3Amain) | ||
[![Go Reference](https://pkg.go.dev/badge/github.com/elh/bitempura.svg)](https://pkg.go.dev/github.com/elh/bitempura) | ||
[![Build Status](https://github.com/elh/bitempura/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/elh/bitempura/actions/workflows/go.yml?query=branch%3Amain) | ||
|
||
Building intuition about [bitemporal databases](https://en.wikipedia.org/wiki/Bitemporal_Modeling) by building (a toy) one for myself. | ||
**Bitempura.DB is a simple, [in-memory](https://github.com/elh/bitempura/blob/main/memory/db.go) [bitemporal](https://en.wikipedia.org/wiki/Bitemporal_Modeling) key-value database.** | ||
|
||
<br /> | ||
|
||
## Bitemporality | ||
|
||
Temporal databases model time as a core aspect of storing and querying data. A bitemporal database is one that supports these orthogonal axes. | ||
* **Valid time**: When the fact was *true* in the real world. This is the *application domain's* notion of time. | ||
* **Transaction time**: When the fact was *recorded* in the database. This is the *system's* notion of time. | ||
|
||
Because every fact in a bitemporal database has these two dimensions, it enables use cases like... | ||
Because every fact in a bitemporal database has these two dimensions, it enables use cases like this: | ||
```go | ||
// What was Bob's balance on Jan 1 as best we knew on Jan 8? (VT = Jan 1, TT = Jan 8) | ||
doc, err := db.Find("Bob/balance", AsOfValidTime(jan1), AsOfTransactionTime(jan8)) | ||
// We initialize a DB and start using it like an ordinary key-value store. | ||
db, err := memory.NewDB() | ||
err := db.Set("Bob/balance", 100) | ||
val, err := db.Get("Bob/balance") | ||
err := db.Delete("Alice/balance") | ||
// and so on... | ||
|
||
// But what was it on Jan 1 as best we now know? (VT = Jan 1, TT = now) | ||
doc2, err := db.Find("Bob/balance", AsOfValidTime(jan1)) | ||
// We later learn that Bob had a temporary pending charge we missed from Dec 30 to Jan 3. (VT start = Dec 30, VT end = Jan 3) | ||
// Retroactively record it! This does not change his balance today nor does it destroy any history we had about that period. | ||
err := db.Set("Bob/balance", 90, WithValidTime(dec30), WithEndValidTime(jan3)) | ||
|
||
// We just learned that Bob had a temporary charge from Dec 30 to Jan 3 (VT start = Dec 30, VT end = Jan 3). | ||
// Retroactively add it. | ||
err := db.Put("Bob/balance", Attributes{"dollars": 90}, WithValidTime(dec30), WithEndValidTime(jan3)) | ||
// We can at any point seamlessly ask questions about the real world past AND database record past! | ||
// "What was Bob's balance on Jan 1 as best we knew on Jan 8?" (VT = Jan 1, TT = Jan 8) | ||
val, err := db.Get("Bob/balance", AsOfValidTime(jan1), AsOfTransactionTime(jan8)) | ||
|
||
// And let's double check all of our transactions and known states | ||
// More time passes and more corrections are made... When trying to make sense of what happened last month, we can ask again: | ||
// "But what was it on Jan 1 as best we now know?" (VT = Jan 1, TT = now) | ||
val, err := db.Get("Bob/balance", AsOfValidTime(jan1)) | ||
|
||
// And while we are at it, let's double check all of our transactions and known states for Bob's balance. | ||
versions, err := db.History("Bob/balance") | ||
``` | ||
*See [full exampes](https://github.com/elh/bitempura/blob/main/memory/db_examples_test.go) | ||
|
||
Using a bitemporal database allows you to offload management of temporal application data (valid time) and data versions (transaction time) from your code and onto infrastructure. This provides a universal "time travel" capability across models in the database. Adopting bitemporality is proactive because by the time you realize you need to update (or have already updated) data, it may be too late. Context may already be lost or painful to reconstruct manually. | ||
|
||
See [in memory reference implementation](https://github.com/elh/bitemporal/blob/main/memory/db.go) | ||
Using a bitemporal database allows you to offload management of temporal application data (valid time) and data versions (transaction time) from your code and onto infrastructure. This provides a universal "time travel" capability across models in the database. Adopting these capabilities proactively is valuable because by the time you realize you need to update (or have already updated) data, it may be too late. Context may already be lost or painful to reconstruct manually. | ||
|
||
### Design | ||
<br /> | ||
|
||
* Initial DB API is inspired by XTDB (and Datomic). | ||
* Record layout is inspired by Snodgrass' SQL implementations. | ||
## Design | ||
|
||
```go | ||
// DB for bitemporal data. | ||
// | ||
// Temporal control options | ||
// On writes: WithValidTime, WithEndValidTime | ||
// On reads: AsOfValidTime, AsOfTransactionTime | ||
// Temporal control options. | ||
// ReadOpt's: AsOfValidTime, AsOfTransactionTime. | ||
// WriteOpt's: WithValidTime, WithEndValidTime. | ||
type DB interface { | ||
// Find data by id (as of optional valid and transaction times). | ||
Find(id string, opts ...ReadOpt) (*Document, error) | ||
// Get data by key (as of optional valid and transaction times). | ||
Get(key string, opts ...ReadOpt) (*VersionedKV, error) | ||
// List all data (as of optional valid and transaction times). | ||
List(opts ...ReadOpt) ([]*Document, error) | ||
// Put stores attributes (with optional start and end valid time). | ||
Put(id string, attributes Attributes, opts ...WriteOpt) error | ||
// Delete removes attributes (with optional start and end valid time). | ||
Delete(id string, opts ...WriteOpt) error | ||
|
||
// History returns versions by descending end transaction time, descending end valid time | ||
History(id string) ([]*Document, error) | ||
List(opts ...ReadOpt) ([]*VersionedKV, error) | ||
// Set stores value (with optional start and end valid time). | ||
Set(key string, value Value, opts ...WriteOpt) error | ||
// Delete removes value (with optional start and end valid time). | ||
Delete(key string, opts ...WriteOpt) error | ||
|
||
// History returns all versioned key-values for key by descending end transaction time, descending end valid time. | ||
History(key string) ([]*VersionedKV, error) | ||
} | ||
|
||
// VersionedKV is a transaction time and valid time versioned key-value. Transaction and valid time starts are inclusive | ||
// and ends are exclusive. No two VersionedKVs for the same key can overlap both transaction time and valid time. | ||
type VersionedKV struct { | ||
Key string | ||
Value Value | ||
|
||
TxTimeStart time.Time // inclusive | ||
TxTimeEnd *time.Time // exclusive | ||
ValidTimeStart time.Time // inclusive | ||
ValidTimeEnd *time.Time // exclusive | ||
} | ||
|
||
// Value is the user-controlled data associated with a key (and valid and transaction time information) in the database. | ||
type Value interface{} | ||
``` | ||
|
||
See [TODO](https://github.com/elh/bitemporal/blob/main/TODO.md) | ||
* DB interface is inspired by XTDB (and Datomic). | ||
* Storage model is inspired by Snodgrass' SQL implementations. | ||
|
||
<br /> | ||
|
||
## Author | ||
|
||
I'm learning about [bitemporal databases](https://en.wikipedia.org/wiki/Bitemporal_Modeling) and thought the best way to build intuition about their internal design was by building a simple one for myself. My goals are: | ||
* Sharing a viable, standalone key-value store lib | ||
* Creating artifacts to teach others about temporal data | ||
* Launching off this to new tools for gracefully extending existing SQL databases with bitemporality | ||
|
||
Bitempura was the name of my time travelling shrimp. RIP 2049-2022. 🦐 | ||
|
||
See [TODO](https://github.com/elh/bitempura/blob/main/TODO.md) for more. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,24 @@ | ||
## TODO: | ||
- [x] [API v1 done](https://github.com/elh/bitemporal/blob/main/db.go). [In memory implementation](https://github.com/elh/bitemporal/blob/main/memory/db.go) | ||
- [x] Find | ||
- [x] [API v1 done](https://github.com/elh/bitempura/blob/main/db.go). [In-memory implementation](https://github.com/elh/bitempura/blob/main/memory/db.go) | ||
- [x] Get | ||
- [x] List | ||
- [x] Put | ||
- [x] Set | ||
- [x] Delete | ||
- [x] [XTDB, Robinhood example tests pass](https://github.com/elh/bitemporal/blob/main/memory/db_examples_test.go) | ||
- [x] [XTDB, Robinhood example tests pass](https://github.com/elh/bitempura/blob/main/memory/db_examples_test.go) | ||
- [x] Split out in-memory implementation | ||
- [x] History API? | ||
- [ ] Separate "db" and "storage" models. first pass was blending XTDB APIs with Snodgrass style records and things are getting muddled. | ||
- Storage layer will inform choices for querying ability at DB layer. | ||
- [ ] Should data read and write APIs return tx time and valid time context at all? | ||
- [ ] Consider common option handling, common repo test harness later. (Split out in-memory implementation follow on) | ||
- [ ] SQL backed implementation | ||
- [ ] Document new intuition about mutations + the 2D time graph | ||
- [ ] ReadOpt's for History | ||
- [ ] Thread safe writes | ||
- [ ] Exported ReadOpt and WriteOpt handling | ||
- [ ] Exported DB test harness | ||
- [ ] Visualizations. Interactive? | ||
|
||
Candidates | ||
- [ ] Write about new intuition about mutations + the 2D time graph | ||
- [ ] Valid time management as a custom "version rule"? | ||
- [ ] "Domain time"? | ||
- [ ] Explore geographical map idea. 2D of data + transaction time => 3 dimensions? | ||
- [ ] Separate "db" and "storage" models? first pass was blending XTDB APIs with Snodgrass style records and things are getting muddled. Storage layer will inform choices for querying ability at DB layer. | ||
- [ ] Should data read and write APIs return tx time and valid time context at all? | ||
- [ ] SQL backed implementation? | ||
- [ ] Consider Datomic accumulate and retract event style. Immutable storage layer? | ||
- [ ] Visualizations. Interactive? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
// Package bitemporal contains experiments with bitemporal data | ||
package bitemporal | ||
// Package bitempura contains experiments with bitemporal data. | ||
// It defines an interface for bitemporal key-value databases. | ||
package bitempura |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
package bitemporal | ||
package bitempura | ||
|
||
import "errors" | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
module github.com/elh/bitemporal | ||
module github.com/elh/bitempura | ||
|
||
go 1.17 | ||
|
||
|
Oops, something went wrong.