<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged spacetimedb</title>
<link>https://coderlegion.com/tag/spacetimedb</link>
<description>Powered by Question2Answer</description>
<item>
<title>SpacetimeDB and Reducers: Architecture and Patterns</title>
<link>https://coderlegion.com/6173/spacetimedb-and-reducers-architecture-and-patterns</link>
<description>&lt;p&gt;SpacetimeDB is a transactional, in-memory relational database that lets you implement database-backed state machines via Rust reducers and table schemas. All data is stored in memory and persisted via an append-only Write-Ahead Log (WAL) called the Commitlog. Reducers are atomic, deterministic functions that execute inside the database, providing ACID guarantees.&lt;/p&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stk2chain/stk2eth&quot;&gt;STK2ETH&lt;/a&gt; we use SpacetimeDB to store USSD menus, sessions, swaps, and audit logs.&lt;/p&gt;
&lt;h3 id=&quot;tables-and-schema&quot;&gt;Tables and Schema&lt;/h3&gt;
&lt;p&gt;Tables are declared using Rust structs annotated with the &lt;code&gt;#[table(name = &amp;lt;table&amp;gt;)]&lt;/code&gt; macro, which generates typed accessors. This provides compile-time guarantees about columns and helps reduce human errors in row operations.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[table(name = ussd_session, public)]
#[derive(Clone, Debug)]
pub struct UssdSession {
    #[primary_key]
    session_id: String,
    // ... other fields ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important tables in STK2ETH:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ussd_menu&lt;/code&gt;, &lt;code&gt;ussd_screen&lt;/code&gt;, &lt;code&gt;menu_item&lt;/code&gt;: represent static menu structure loaded from a file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ussd_session&lt;/code&gt;: session rows for active interactions.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swap&lt;/code&gt;: holds pending/executed swaps and transaction metadata.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eth_audit_logs&lt;/code&gt;: immutable audit trail for regulatory needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;reducer-semantics&quot;&gt;Reducer Semantics&lt;/h3&gt;
&lt;p&gt;Reducers are Rust functions annotated with &lt;code&gt;#[reducer]&lt;/code&gt; that take a &lt;code&gt;&amp;amp;ReducerContext&lt;/code&gt; as the first argument. They are applied deterministically and atomically, and can only return &lt;code&gt;()&lt;/code&gt; or &lt;code&gt;Result&amp;lt;(), E&amp;gt;&lt;/code&gt; where &lt;code&gt;E: Display&lt;/code&gt;. This ensures state changes can be serialized consistently.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[reducer]
pub fn set_name(ctx: &amp;amp;ReducerContext, name: String) -&amp;gt; Result&amp;lt;(), String&amp;gt; {
    // reducer logic
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Guidelines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep business logic testable: move pure logic (parsing, validation) into separate functions. Use reducers only for side-effects and DB writes.&lt;/li&gt;
&lt;li&gt;Use stable string error codes when returning &lt;code&gt;Err(String)&lt;/code&gt; from reducers to allow the UI to map errors to screens.&lt;/li&gt;
&lt;li&gt;Avoid long-running synchronous operations in reducers. Use a table (e.g., &lt;code&gt;ussd_request&lt;/code&gt;) to enqueue work for out-of-band workers when you need to call external services.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;deterministic-design-and-side-effect-management&quot;&gt;Deterministic Design and Side-Effect Management&lt;/h3&gt;
&lt;p&gt;Reducers must be deterministic and cannot perform direct network or filesystem I/O. External operations should be handled by enqueuing requests in a table, and a separate worker can process those requests and write results back to the database.&lt;/p&gt;
&lt;p&gt;Pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Insert a &lt;code&gt;ussd_request&lt;/code&gt; row describing the work.&lt;/li&gt;
&lt;li&gt;An off-chain worker reads the request, performs the external call, and inserts result rows for reducers to consume.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;testing-reducers&quot;&gt;Testing Reducers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Unit test pure functions directly.&lt;/li&gt;
&lt;li&gt;Reducer tests should be small and deterministic. Use the SpacetimeDB test harness for reducers that require database access (when available).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;example-patterns&quot;&gt;Example Patterns&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Validation:&lt;/strong&gt; Pure function &lt;code&gt;validate_amount_value&lt;/code&gt; + reducer wrapper &lt;code&gt;validate_amount&lt;/code&gt; that reads config, then returns &lt;code&gt;Ok(())&lt;/code&gt; or &lt;code&gt;Err(&quot;amount_too_low&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enqueueing:&lt;/strong&gt; Reducers insert &lt;code&gt;ussd_request&lt;/code&gt; rows and return immediately.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;observability&quot;&gt;Observability&lt;/h3&gt;
&lt;p&gt;Reducers should log important events (e.g., session created, swap created, request enqueued) using SpacetimeDB’s logging system. This helps with debugging and auditing state transitions.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;SpacetimeDB offers a deterministic and transactional pattern for implementing state machines. Treat reducers as the boundary for state changes, keep logic testable, enqueue external interactions for off-chain workers, and use stable error codes for UI mapping.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/6173/spacetimedb-and-reducers-architecture-and-patterns</guid>
<pubDate>Tue, 07 Oct 2025 21:33:33 +0000</pubDate>
</item>
</channel>
</rss>