Module: ActsAsTbackend::Fact
- Defined in:
- lib/acts_as_tbackend/fact.rb
Overview
Builds facts with a deterministic, domain-derived id so a retry re-sends the same id and collapses to an idempotent replay instead of a duplicate.
NEVER put wall-clock in the id. Derive it from the source record's own version
stamp (e.g. updated_at), which is stable across retries and advances on every
real edit. The observation wall-clock belongs in transaction_time only.
id = ActsAsTbackend::Fact.derive_id(
store: "orders", record_id: order.id, event_type: "order.accepted",
source_version: order.updated_at)
fact = ActsAsTbackend::Fact.build(
id:, store: "orders", key: "order:#{order.id}", value: {...},
valid_time: order.scheduled_at)
Class Method Summary collapse
-
.build(id:, store:, key:, value:, valid_time: nil, transaction_time: nil, causation: nil, schema_version: 1, producer: nil) ⇒ Object
Builds a fact envelope.
-
.derive_id(store:, record_id:, event_type:, source_version:) ⇒ Object
Deterministic occurrence id.
- .now ⇒ Object
-
.version_token(source_version) ⇒ Object
A Time is encoded as an integer microsecond epoch — fully deterministic (no float formatting) and colon-free.
Class Method Details
.build(id:, store:, key:, value:, valid_time: nil, transaction_time: nil, causation: nil, schema_version: 1, producer: nil) ⇒ Object
Builds a fact envelope. value_hash is intentionally omitted: the daemon
stamps its own canonical hash on write_fact_once and is the authority.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/acts_as_tbackend/fact.rb', line 40 def build(id:, store:, key:, value:, valid_time: nil, transaction_time: nil, causation: nil, schema_version: 1, producer: nil) fact = { "id" => id, "store" => store.to_s, "key" => key.to_s, "value" => value, "transaction_time" => (transaction_time || now).to_f, "schema_version" => schema_version } fact["valid_time"] = valid_time.to_f unless valid_time.nil? fact["causation"] = causation unless causation.nil? fact["producer"] = producer unless producer.nil? fact end |
.derive_id(store:, record_id:, event_type:, source_version:) ⇒ Object
Deterministic occurrence id. Components must be colon-free (the ":" is the id separator); source_version is normalised to a colon-free token.
24 25 26 |
# File 'lib/acts_as_tbackend/fact.rb', line 24 def derive_id(store:, record_id:, event_type:, source_version:) "#{store}:#{record_id}:#{event_type}:#{version_token(source_version)}" end |
.now ⇒ Object
56 57 58 |
# File 'lib/acts_as_tbackend/fact.rb', line 56 def now Process.clock_gettime(Process::CLOCK_REALTIME) end |
.version_token(source_version) ⇒ Object
A Time is encoded as an integer microsecond epoch — fully deterministic (no float formatting) and colon-free. Anything else is used as-is (stringified).
30 31 32 33 34 35 36 |
# File 'lib/acts_as_tbackend/fact.rb', line 30 def version_token(source_version) if source_version.respond_to?(:usec) && source_version.respond_to?(:to_i) source_version.to_i * 1_000_000 + source_version.usec else source_version.to_s end end |