<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Timebox</title><link>https://kode4food.github.io/timebox/</link><description>Recent content on Timebox</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://kode4food.github.io/timebox/index.xml" rel="self" type="application/rss+xml"/><item><title>Getting Started</title><link>https://kode4food.github.io/timebox/docs/getting-started/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kode4food.github.io/timebox/docs/getting-started/</guid><description>&lt;h1 id="getting-started"&gt;Getting Started&lt;a class="anchor" href="#getting-started"&gt;#&lt;/a&gt;&lt;/h1&gt;&#10;&lt;h2 id="requirements"&gt;Requirements&lt;a class="anchor" href="#requirements"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Go 1.27 or later&lt;/li&gt;&#10;&lt;li&gt;A supported persistence service when using PostgreSQL or Redis/Valkey&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="install"&gt;Install&lt;a class="anchor" href="#install"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go get github.com/kode4food/timebox@v0.1.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Backend packages are part of the same module:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8bd5ca"&gt;import&lt;/span&gt; (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6da95"&gt;&amp;#34;github.com/kode4food/timebox&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6da95"&gt;&amp;#34;github.com/kode4food/timebox/memory&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="define-an-aggregate"&gt;Define an Aggregate&lt;a class="anchor" href="#define-an-aggregate"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;An aggregate needs a state type, an empty-state constructor, event types, event payloads, and appliers. Appliers rebuild state from stored events and must be deterministic.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ed8796"&gt;type&lt;/span&gt; Account &lt;span style="color:#ed8796"&gt;struct&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Balance &lt;span style="color:#ed8796"&gt;int&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ed8796"&gt;type&lt;/span&gt; Deposited &lt;span style="color:#ed8796"&gt;struct&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Amount &lt;span style="color:#ed8796"&gt;int&lt;/span&gt; &lt;span style="color:#a6da95"&gt;`json:&amp;#34;amount&amp;#34;`&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ed8796"&gt;const&lt;/span&gt; AccountDeposited timebox.EventType = &lt;span style="color:#a6da95"&gt;&amp;#34;account.deposited&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ed8796"&gt;func&lt;/span&gt; &lt;span style="color:#8aadf4"&gt;newAccount&lt;/span&gt;() Account {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; Account{}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ed8796"&gt;var&lt;/span&gt; accountAppliers = timebox.Appliers[Account]{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AccountDeposited: timebox.&lt;span style="color:#8aadf4"&gt;MakeApplier&lt;/span&gt;(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ed8796"&gt;func&lt;/span&gt;(account Account, _ &lt;span style="color:#91d7e3;font-weight:bold"&gt;*&lt;/span&gt;timebox.Event, data Deposited) Account {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; account.Balance &lt;span style="color:#91d7e3;font-weight:bold"&gt;+=&lt;/span&gt; data.Amount&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; account&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;MakeApplier&lt;/code&gt; decodes the payload and passes both the event metadata and typed data to the function.&lt;/p&gt;</description></item><item><title>Tutorial: Place an Order</title><link>https://kode4food.github.io/timebox/docs/tutorial/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kode4food.github.io/timebox/docs/tutorial/</guid><description>&lt;h1 id="tutorial-place-an-order"&gt;Tutorial: Place an Order&lt;a class="anchor" href="#tutorial-place-an-order"&gt;#&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;This tutorial builds a small order service from an empty Go module. It receives inventory, atomically reserves stock while placing an order, confirms and ships the order, and queries the resulting event history, status index, and customer tags.&lt;/p&gt;&#10;&lt;p&gt;The finished program uses the in-memory backend so it runs without infrastructure. The final step shows how to replace memory with durable storage without changing the domain code.&lt;/p&gt;</description></item><item><title>Aggregates and Events</title><link>https://kode4food.github.io/timebox/docs/aggregates/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kode4food.github.io/timebox/docs/aggregates/</guid><description>&lt;h1 id="aggregates-and-events"&gt;Aggregates and Events&lt;a class="anchor" href="#aggregates-and-events"&gt;#&lt;/a&gt;&lt;/h1&gt;&#10;&lt;h2 id="aggregate-ids"&gt;Aggregate IDs&lt;a class="anchor" href="#aggregate-ids"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Every aggregate ID contains a type and key:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;orderID &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; timebox.&lt;span style="color:#8aadf4"&gt;NewAggregateID&lt;/span&gt;(&lt;span style="color:#a6da95"&gt;&amp;#34;order&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6da95"&gt;&amp;#34;ORD-123&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Both components are always populated. For a concept with exactly one aggregate, use &lt;code&gt;NewAggregateType&lt;/code&gt;; it fills the key with &lt;code&gt;timebox.SingletonKey&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;catalogID &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; timebox.&lt;span style="color:#8aadf4"&gt;NewAggregateType&lt;/span&gt;(&lt;span style="color:#a6da95"&gt;&amp;#34;catalog&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Use this pattern for singleton concepts such as a catalog or cluster. Use &lt;code&gt;NewAggregateID&lt;/code&gt; when a type has many instances, such as &lt;code&gt;(&amp;quot;workflow&amp;quot;, workflowID)&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="event-types-and-payloads"&gt;Event Types and Payloads&lt;a class="anchor" href="#event-types-and-payloads"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Event type names are durable identifiers. Payloads are JSON encoded by &lt;code&gt;Aggregator.Raise&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Transactions</title><link>https://kode4food.github.io/timebox/docs/transactions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kode4food.github.io/timebox/docs/transactions/</guid><description>&lt;h1 id="transactions"&gt;Transactions&lt;a class="anchor" href="#transactions"&gt;#&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;&lt;code&gt;Executor.Exec&lt;/code&gt; is a single-aggregate transaction. Use &lt;code&gt;Store.Transact&lt;/code&gt; when one operation must update several aggregates atomically:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; store.&lt;span style="color:#8aadf4"&gt;Transact&lt;/span&gt;(&lt;span style="color:#ed8796"&gt;func&lt;/span&gt;(tx &lt;span style="color:#91d7e3;font-weight:bold"&gt;*&lt;/span&gt;timebox.Transaction) &lt;span style="color:#ed8796"&gt;error&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;if&lt;/span&gt; _, err &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; tx.&lt;span style="color:#8aadf4"&gt;Exec&lt;/span&gt;(accounts, sourceID,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ed8796"&gt;func&lt;/span&gt;(source Account, ag &lt;span style="color:#91d7e3;font-weight:bold"&gt;*&lt;/span&gt;timebox.Aggregator[Account]) &lt;span style="color:#ed8796"&gt;error&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;if&lt;/span&gt; source.Balance &amp;lt; amount {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; ErrInsufficientFunds&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; ag.&lt;span style="color:#8aadf4"&gt;Raise&lt;/span&gt;(MoneyWithdrawn, Money{Amount: amount})&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ); err &lt;span style="color:#91d7e3;font-weight:bold"&gt;!=&lt;/span&gt; &lt;span style="color:#f5a97f"&gt;nil&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; err&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _, err &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; tx.&lt;span style="color:#8aadf4"&gt;Exec&lt;/span&gt;(accounts, destinationID,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ed8796"&gt;func&lt;/span&gt;(_ Account, ag &lt;span style="color:#91d7e3;font-weight:bold"&gt;*&lt;/span&gt;timebox.Aggregator[Account]) &lt;span style="color:#ed8796"&gt;error&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; ag.&lt;span style="color:#8aadf4"&gt;Raise&lt;/span&gt;(MoneyDeposited, Money{Amount: amount})&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; err&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every backend appends all enlisted aggregate batches or none of them. Each aggregate has its own optimistic sequence check. On a conflict, Timebox refreshes state and reruns the entire transaction function.&lt;/p&gt;</description></item><item><title>Backends</title><link>https://kode4food.github.io/timebox/docs/backends/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kode4food.github.io/timebox/docs/backends/</guid><description>&lt;h1 id="backends"&gt;Backends&lt;a class="anchor" href="#backends"&gt;#&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;All backends implement the same append, snapshot, query, and optimistic-concurrency contract. Choose one backend for storage and create one or more stores over it.&lt;/p&gt;&#10;&lt;h2 id="memory"&gt;Memory&lt;a class="anchor" href="#memory"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;backend &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; memory.&lt;span style="color:#8aadf4"&gt;Open&lt;/span&gt;()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;store, err &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; backend.&lt;span style="color:#8aadf4"&gt;NewStore&lt;/span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Use memory for tests and single-process ephemeral state. It supports indexing and archiving.&lt;/p&gt;&#10;&lt;h2 id="postgresql"&gt;PostgreSQL&lt;a class="anchor" href="#postgresql"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;backend, err &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; postgres.&lt;span style="color:#8aadf4"&gt;Open&lt;/span&gt;(postgres.Config{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; URL: &lt;span style="color:#a6da95"&gt;&amp;#34;postgres://localhost:5432/app?sslmode=disable&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Prefix: &lt;span style="color:#a6da95"&gt;&amp;#34;timebox&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MaxConns: &lt;span style="color:#f5a97f"&gt;32&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#c6a0f6"&gt;if&lt;/span&gt; err &lt;span style="color:#91d7e3;font-weight:bold"&gt;!=&lt;/span&gt; &lt;span style="color:#f5a97f"&gt;nil&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; err&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#c6a0f6"&gt;defer&lt;/span&gt; backend.&lt;span style="color:#8aadf4"&gt;Close&lt;/span&gt;()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;store, err &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; backend.&lt;span style="color:#8aadf4"&gt;NewStore&lt;/span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The backend creates and owns its schema. &lt;code&gt;Prefix&lt;/code&gt; is the PostgreSQL schema name. PostgreSQL supports events, snapshots, status, tags, and multi-aggregate transactions; it does not support archiving.&lt;/p&gt;</description></item><item><title>Snapshots, Indexing, and Archiving</title><link>https://kode4food.github.io/timebox/docs/storage/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kode4food.github.io/timebox/docs/storage/</guid><description>&lt;h1 id="snapshots-indexing-and-archiving"&gt;Snapshots, Indexing, and Archiving&lt;a class="anchor" href="#snapshots-indexing-and-archiving"&gt;#&lt;/a&gt;&lt;/h1&gt;&#10;&lt;h2 id="snapshots"&gt;Snapshots&lt;a class="anchor" href="#snapshots"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Snapshots accelerate state loading; events remain authoritative. Executors save snapshots opportunistically when there is no snapshot or when trailing event data exceeds &lt;code&gt;SnapshotRatio&lt;/code&gt; relative to the stored snapshot.&lt;/p&gt;&#10;&lt;p&gt;Set &lt;code&gt;TrimEvents&lt;/code&gt; when retained events before a snapshot are unnecessary:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;store, err &lt;span style="color:#91d7e3;font-weight:bold"&gt;:=&lt;/span&gt; backend.&lt;span style="color:#8aadf4"&gt;NewStore&lt;/span&gt;(timebox.Config{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TrimEvents: &lt;span style="color:#f5a97f"&gt;true&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SnapshotRatio: &lt;span style="color:#f5a97f"&gt;1.0&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Trimming happens with snapshot persistence. Do not enable it when the complete live event history must remain queryable.&lt;/p&gt;&#10;&lt;h2 id="status-and-tag-indexing"&gt;Status and Tag Indexing&lt;a class="anchor" href="#status-and-tag-indexing"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;An indexer derives metadata from the same event batch being appended:&lt;/p&gt;</description></item><item><title>Production Patterns</title><link>https://kode4food.github.io/timebox/docs/patterns/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kode4food.github.io/timebox/docs/patterns/</guid><description>&lt;h1 id="production-patterns"&gt;Production Patterns&lt;a class="anchor" href="#production-patterns"&gt;#&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;These patterns show how the core pieces fit into a long-running replicated service.&lt;/p&gt;&#10;&lt;h2 id="one-backend-several-stores"&gt;One Backend, Several Stores&lt;a class="anchor" href="#one-backend-several-stores"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Open one backend, then create several stores over it when aggregate families need different policies:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;A metadata store for singleton aggregates, with event trimming enabled&lt;/li&gt;&#10;&lt;li&gt;A workflow store with a status/tag indexer and a different snapshot ratio&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Stores share persistence while keeping their caching, snapshotting, trimming, and indexing policies separate.&lt;/p&gt;&#10;&lt;h2 id="one-executor-per-state-model"&gt;One Executor per State Model&lt;a class="anchor" href="#one-executor-per-state-model"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Create separate executors for each state model. Singleton models use &lt;code&gt;NewAggregateType&lt;/code&gt;; models with many instances use &lt;code&gt;NewAggregateID&lt;/code&gt;.&lt;/p&gt;</description></item></channel></rss>