Skip to content

Commit

Permalink
Add API documentation on for Application Events
Browse files Browse the repository at this point in the history
  • Loading branch information
codebeige committed Dec 13, 2023
1 parent ecca4ed commit 0719539
Showing 1 changed file with 98 additions and 16 deletions.
114 changes: 98 additions & 16 deletions src/moira/event.cljs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(ns moira.event
"Create Application Events using unique [[EventId|IDs]] with deterministic
order based on a
"Implement [[Factory]] for producing Application Events with unique
[[EventId|Event IDs]] and guaranteed ordering based on a
[logical clock](https://en.wikipedia.org/wiki/Logical_clock)."

(:require [clojure.spec.alpha :as s]
Expand Down Expand Up @@ -29,10 +29,24 @@
The counter ensures that events with identical [[date-prefix|timestamp]]
and [[app|application id]] are always unique and in order."))

(defprotocol ^:no-doc Counter
(next-count [this]))
(deftype
^{:doc
"Uniquely identify any Application Event by encoding a
[[timestamp|Dateable]], [[client|Attributable]], and
[[counter|Countable]].
Supports type-and-value based equality through `=`. Relative order can be
determined with `<`, `>`, `sort`, etc.
Tagged literal support is added for both reading and writing.
#event/id \"ktpgcdxgxhmkwoookboe00\"
"}

EventId

[id ^:mutable __hash]

(deftype ^:no-doc EventId [id ^:mutable __hash]
Object
(toString [_] id)
(equiv [this other]
Expand Down Expand Up @@ -87,27 +101,58 @@
(s/def ::now pos-int?)
(s/def ::date-prefix (s/and string? (partial re-matches #"[0-9a-z]{8}")))

(defn string->event-id [s]
(defn string->event-id
"Make [[EventId]] from `s`.
`s` needs to be a string concatenatinge [[date-prefix]], [[app|client
identifyer]], and [[counter]]."

[s]

(->EventId (.toLowerCase s) nil))

(defn counter->suffix [x]
(defn counter->suffix
"Encode `x` into a string suitable for generating [[EventId]]."

[x]

(-> x (.toString 36) (.padStart 4 "0")))

(defn date->prefix
"Encode `date` into format suitable for generating `EventId`."
"Encode `date` into a string suitable for generating [[EventId]]."

[ms]

{:pre [(s/valid? ::now ms)]
:post [(s/valid? ::date-prefix %)]}

(-> ms (.toString 36) (.padStart 8 "0")))

(defn monotonic-now []
(defn monotonic-now
"Get milliseconds elapsed since
[epoch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#the_epoch_timestamps_and_invalid_date)
from the
[monotonic clock](https://w3c.github.io/hr-time/#dfn-monotonic-clock.
Timestamp to be used for generating [[Eventid]]."

[]

{:post [(s/valid? ::now %)]}

(-> (js/performance.now)
(+ js/performance.timeOrigin)
js/Math.floor))

(defn current-date []
(defn current-date
"Get `Date` from [wall clock](https://w3c.github.io/hr-time/#ref-for-dfn-wall-clock-1).
Timestamp to be used as default `:date` value by [[EventFactory]]."

[]

{:post [(s/valid? ::date %)]}

(js/Date.))

(defn- rand-gen []
Expand All @@ -116,27 +161,59 @@
(defn- rand-id [length]
(->> (rand-gen) (take length) (apply str)))

(defn app-id []
(defn- app-id []
(rand-id 12))

(defprotocol Counter
"Produce an incremental stream of counter values.
Ensures a deterministic order of [[EventId|Event IDs]] for [[app]] with the
same [[date|timestamps]]."

(next-count [this]
"Get tuple of current [[monotonic-now|timestamp]] and next [[counter]]
value."))

(defprotocol IdGenerator
(next-id [this] "Generate unique event-id in context of `this`."))
"Produce a stream of unique [[EventId|Event IDs]] in context of [[Factory]]."

(next-id [this]
"Get next [[EventId]]."))

(deftype
^{:doc
"Manage state for creating unique [[EventId|Event IDs]] in context of the
current [[app]]."}

EventIdGenerator

[now app-id counter]

(deftype EventIdGenerator [now app-id counter]
Counter
(next-count [_]
(let [t* (now)]
(vswap! counter (fn [[t x]] [t* (if (= t* t) (inc x) 0)]))))

IdGenerator
(next-id [this]
(let [[t x] (next-count this)]
(string->event-id
(str (date->prefix t) app-id (counter->suffix x))))))

(defprotocol Factory
(->event [this m] "Create event from data `m` with generated `:id`."))
"Make Application Events using unique [[EventId|Event IDs]] with
deterministic order based on a
[logical clock](https://en.wikipedia.org/wiki/Logical_clock)."

(deftype EventFactory [current-date id-generator]
(->event [this m]
"Make Application Event from `m` with `:id` generated by
[[EventIdGenerator]].
`:date` is automatically set from
[wall clock](https://w3c.github.io/hr-time/#ref-for-dfn-wall-clock-1), if
not already present."))

(deftype ^:no-doc EventFactory [current-date id-generator]
Factory
(->event [_ m]
(when (contains? m :id)
Expand All @@ -147,7 +224,12 @@
(assoc :id (next-id id-generator))
(update :date #(or % (current-date))))))

(defn factory []
(defn factory
"Create a [[Factory]] by wrapping an instance of [[EventIdGenerator]] scoped
to the current [[app]]."

[]

(->> (volatile! nil)
(->EventIdGenerator monotonic-now (app-id))
(->EventFactory current-date)))

0 comments on commit 0719539

Please sign in to comment.