<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kairi</title>
    <description>The latest articles on DEV Community by kairi (@kary_0009).</description>
    <link>https://dev.to/kary_0009</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4118744%2F056d5f37-da3f-4aab-bde8-1e7ce710249b.png</url>
      <title>DEV Community: kairi</title>
      <link>https://dev.to/kary_0009</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kary_0009"/>
    <language>en</language>
    <item>
      <title>[Event Sourcing] Running an Existing C# Business Model on Wasm with SekibanWasmRuntime</title>
      <dc:creator>kairi</dc:creator>
      <pubDate>Wed, 16 Sep 2026 06:05:09 +0000</pubDate>
      <link>https://dev.to/kary_0009/event-sourcing-running-an-existing-c-business-model-on-wasm-with-sekibanwasmruntime-1gcf</link>
      <guid>https://dev.to/kary_0009/event-sourcing-running-an-existing-c-business-model-on-wasm-with-sekibanwasmruntime-1gcf</guid>
      <description>&lt;p&gt;In the previous article, we created a C# application using Sekiban DCB to handle students, classes, and class enrollment. This time, we will try running that application with &lt;strong&gt;SekibanWasmRuntime&lt;/strong&gt;, which allows Sekiban to run with Wasm (WebAssembly).&lt;/p&gt;

&lt;p&gt;In this article, we will run a project written in C# as Wasm. SekibanWasmRuntime currently provides primary support for C# and Rust, while Go, TypeScript, MoonBit, and Swift are also available as experimental implementations.&lt;/p&gt;

&lt;p&gt;The sample code used in this article is available &lt;a href="https://github.com/kdaigo/SekibanSamples/tree/main/DCBNativeWasmProject" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Roles of Sekiban and Wasm
&lt;/h2&gt;

&lt;p&gt;Sekiban is an event sourcing framework that provides a mechanism for storing events that occur in an application and building the current state from those events.&lt;/p&gt;

&lt;p&gt;Wasm is an executable format produced by compiling code. In this example, &lt;strong&gt;SekibanWasmRuntime&lt;/strong&gt; running on the server loads and executes the Wasm file. The application contains the business code related to students, while the Runtime provides the environment for executing that code and the mechanism for storing events.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Runs in Wasm
&lt;/h2&gt;

&lt;p&gt;The architecture used in this example is as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scalar / PowerShell
    ↓ 
API
    ↓ ISekibanExecutor
RemoteSekibanExecutor
    ├── Executes existing Command handlers on the API side
    └── Requests state retrieval, event commit, and Query execution over HTTP
            ↓ 
SekibanWasmRuntime
    ├── Executes the Projector and list Query in Wasm
    └── Stores events in PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command handlers run on the API side, while the Projector that builds state from events and the student list processing run as Wasm inside the Runtime.&lt;/p&gt;

&lt;p&gt;When a student is created, the process works in the following order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The HTTP endpoint receives the request to create a student.&lt;/li&gt;
&lt;li&gt;The existing Command handler runs on the API side and creates an event according to the business rules.&lt;/li&gt;
&lt;li&gt;The API requests the Runtime to store the event through the Remote connection.&lt;/li&gt;
&lt;li&gt;The Runtime stores the event, and the Projector in Wasm applies the event when state is retrieved or the list is updated.&lt;/li&gt;
&lt;li&gt;The retrieval API can then be used to check the state of the created student or the student list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The business logic remains unchanged, but we need to add an entry point for calling Wasm, type registration, and API connection settings. The entire Web API and all Commands do not run inside Wasm.&lt;/p&gt;

&lt;p&gt;For this example, I used the following versions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;.NET&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sekiban.Dcb&lt;/td&gt;
&lt;td&gt;10.19.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sekiban.Dcb.WasmRuntime.Aspire&lt;/td&gt;
&lt;td&gt;1.0.0-preview.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sekiban.Dcb.WasmRuntime.Remote&lt;/td&gt;
&lt;td&gt;1.0.0-preview.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime container&lt;/td&gt;
&lt;td&gt;1.0.0-preview.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The NuGet packages and the Runtime container use different version series.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What Was Added or Changed
&lt;/h2&gt;

&lt;p&gt;To reuse the existing business logic, I added the Wasm entry point and execution environment.&lt;/p&gt;

&lt;p&gt;The official &lt;a href="https://github.com/J-Tech-Japan/SekibanWasmRuntime/blob/main/docs/templates/sekiban-wasm-decider.md" rel="noopener noreferrer"&gt;C# Wasm template&lt;/a&gt; specifies using Docker when building Wasm on Windows or macOS. Following this procedure, we prepare the build environment for compiling C# to Wasm inside Docker. The generated Wasm file is then loaded and executed by SekibanWasmRuntime.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Added or changed item&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wasm project&lt;/td&gt;
&lt;td&gt;References the existing EventSource and builds it as Wasm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wasm entry point and type registration&lt;/td&gt;
&lt;td&gt;Connects the Runtime to the existing Projector and provides the type information required for ahead-of-time compilation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;manifest&lt;/td&gt;
&lt;td&gt;Tells the Runtime which Wasm, events, Projectors, and Queries are associated with each other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build scripts&lt;/td&gt;
&lt;td&gt;Build Wasm inside Docker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AppHost settings&lt;/td&gt;
&lt;td&gt;Start the Runtime container and a dedicated PostgreSQL instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote connection for the API&lt;/td&gt;
&lt;td&gt;Replaces the &lt;code&gt;ISekibanExecutor&lt;/code&gt; implementation with &lt;code&gt;RemoteSekibanExecutor&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Wasm project references the existing code as follows. The following code snippets show the main settings and implementations that were added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ProjectReference Include="..\DCBNativeWasmProject.EventSource\DCBNativeWasmProject.EventSource.csproj" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the entry point called by the Runtime, we use the existing Projector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;instance.State = StudentProjector.Project(instance.State, ev);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the API side, we register the implementation used to communicate with the Runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddHttpClient&amp;lt;RemoteSekibanExecutor&amp;gt;(http =&amp;gt;
{
    http.BaseAddress = new Uri(builder.Configuration["Runtime:BaseUrl"]!);
    http.Timeout = TimeSpan.FromSeconds(90);
});

builder.Services.AddTransient&amp;lt;ISekibanExecutor&amp;gt;(
    sp =&amp;gt; sp.GetRequiredService&amp;lt;RemoteSekibanExecutor&amp;gt;());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, the existing endpoints can continue calling &lt;code&gt;executor.ExecuteAsync(command)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Package and Wasm Configuration
&lt;/h3&gt;

&lt;p&gt;Run the following commands from the project root directory. Prepare the .NET 10 SDK and Docker Desktop beforehand. On Windows, use Git Bash, and on macOS, use a terminal. Because the build tools are provided inside Docker, you do not need to install the WASI SDK directly.&lt;/p&gt;

&lt;p&gt;The packages added to AppHost and the API are as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/DCBNativeWasmProject.AppHost\DCBNativeWasmProject.AppHost.csproj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Sekiban.Dcb.WasmRuntime.Aspire&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1.0.0-preview.6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/DCBNativeWasmProject.ApiService\DCBNativeWasmProject.ApiService.csproj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Sekiban.Dcb.WasmRuntime.Remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1.0.0-preview.6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following settings are added to &lt;code&gt;DCBNativeWasmProject.Wasm/DCBNativeWasmProject.Wasm.csproj&lt;/code&gt; to generate Wasm output. These are the main settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;TargetFramework&amp;gt;net10.0&amp;lt;/TargetFramework&amp;gt;
&amp;lt;RuntimeIdentifier&amp;gt;wasi-wasm&amp;lt;/RuntimeIdentifier&amp;gt;
&amp;lt;OutputType&amp;gt;library&amp;lt;/OutputType&amp;gt;
&amp;lt;AllowUnsafeBlocks&amp;gt;true&amp;lt;/AllowUnsafeBlocks&amp;gt;
&amp;lt;InvariantGlobalization&amp;gt;true&amp;lt;/InvariantGlobalization&amp;gt;
&amp;lt;IsAotCompatible&amp;gt;true&amp;lt;/IsAotCompatible&amp;gt;
&amp;lt;SelfContained&amp;gt;true&amp;lt;/SelfContained&amp;gt;
&amp;lt;IlcExportUnmanagedEntrypoints&amp;gt;true&amp;lt;/IlcExportUnmanagedEntrypoints&amp;gt;
&amp;lt;NativeLib&amp;gt;Shared&amp;lt;/NativeLib&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual file also includes a package reference for NativeAOT-LLVM and linker settings that expose the functions called by the Runtime. In &lt;code&gt;WasmExports.cs&lt;/code&gt;, type information is explicitly registered, and entry points are provided for applying events, saving and restoring state, and retrieving lists. &lt;code&gt;NuGet.wasm.config&lt;/code&gt; specifies the package sources required for this build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Map Wasm and Processing in the manifest
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;config/sekiban-manifest.json&lt;/code&gt; tells the Runtime which Projectors and Queries are provided by each Wasm module. The following is an excerpt related to the student list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "defaultModulePath": "/app/modules/dcb-native.wasm",
  "queryAssemblyVersion": "wasm",
  "projectors": [
    {
      "projectorName": "StudentListProjection",
      "projectorVersion": "1.0.1",
      "modulePath": "/app/modules/dcb-native.wasm",
      "abiKind": "wasi-preview1",
      "moduleVersion": "1.0.0"
    }
  ],
  "queryProjectors": {
    "GetStudentListQuery": "StudentListProjection"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connect the Runtime and API from AppHost
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;DCBNativeWasmProject.AppHost/WasmAppHost.cs&lt;/code&gt;, we start the Runtime and a PostgreSQL instance. &lt;code&gt;root&lt;/code&gt; is the absolute path to the project root.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var pg = builder.AddPostgres("wasm-postgres");
var events = pg.AddDatabase("SekibanDcb", "dcb_native_wasm_events");

var runtime = builder.AddSekibanWasmRuntime(
    "wasm-runtime",
    new SekibanWasmRuntimeOptions
    {
        ConfigDirectory = Path.Combine(root, "config"),
        ModulesDirectory = Path.Combine(root, "modules"),
        WasmModulePath = "/app/modules/dcb-native.wasm",
        EventStoreDatabase = events,
        ProjectionMode = "memory-only",
        HostPort = 5280
    })
    .WithEnvironment("SEKIBAN_WASM_POOL_SIZE", "0")
    .WaitFor(events);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;memory-only&lt;/code&gt; is the projection mode used to build projections by applying events. The events themselves are stored in PostgreSQL. &lt;code&gt;SEKIBAN_WASM_POOL_SIZE=0&lt;/code&gt; is set as a workaround for an issue in this preview version where consecutive list updates could wait indefinitely. This is not a recommended value that has been evaluated for production use.&lt;/p&gt;

&lt;p&gt;Next, we pass the Runtime URL to the API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.AddProject&amp;lt;DCBNativeWasmProject_ApiService&amp;gt;(
        "wasm-api", launchProfileName: null)
    .WithEnvironment("Runtime__BaseUrl", runtime.GetEndpoint("http"))
    .WithEnvironment("ASPNETCORE_ENVIRONMENT", "Development")
    .WithHttpEndpoint(
        port: 5241, targetPort: 5241, name: "http", isProxied: false)
    .WithEnvironment("ASPNETCORE_URLS", "http://localhost:5241")
    .WaitFor(runtime);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Runtime__BaseUrl&lt;/code&gt; passes the SekibanWasmRuntime endpoint to the API. The API uses this endpoint to request state retrieval and event storage from the Runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Build the Wasm File
&lt;/h2&gt;

&lt;p&gt;Start Docker Desktop and move to the project root directory in your terminal. Adjust the path to match your working directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash scripts/build-wasm.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script uses &lt;code&gt;scripts/Dockerfile.wasm-build&lt;/code&gt; to create a Docker image for the build, and then &lt;code&gt;scripts/build-wasm-container.sh&lt;/code&gt; runs the following command inside the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet publish DCBNativeWasmProject.Wasm/DCBNativeWasmProject.Wasm.csproj \
  -c Release -r wasi-wasm \
  -o artifacts/wasm/publish --configfile NuGet.wasm.config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build result is copied to the location used by the Runtime, and &lt;code&gt;wasm-tools validate&lt;/code&gt; is used to validate the Wasm format. When the build succeeds, a log similar to the following is displayed. The file size may change depending on the implementation.&lt;/p&gt;

&lt;p&gt;Example output when the Wasm build completes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[build-wasm] OK: modules/dcb-native.wasm (25282599 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output Locations of the Build Files
&lt;/h3&gt;

&lt;p&gt;The following paths are relative to the project root.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;artifacts/wasm/publish/DCBNativeWasmProject.Wasm.wasm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wasm generated by &lt;code&gt;publish&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;modules/dcb-native.wasm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A copy of the generated Wasm file that is loaded by the Runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;DCBNativeWasmProject.Wasm/bin/&lt;/code&gt; and &lt;code&gt;obj/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Build outputs and intermediate files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config/sekiban-manifest.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runtime configuration created manually. This is not a build output.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;modules&lt;/code&gt; folder on the host is mounted into the Runtime container. Therefore, the manifest and AppHost specify &lt;code&gt;/app/modules/dcb-native.wasm&lt;/code&gt;, which is the path &lt;strong&gt;inside the container&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Wasm project used in this example is built separately from the normal solution build. Run the dedicated build script before starting AppHost.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Start the Application
&lt;/h2&gt;

&lt;p&gt;In the same terminal, set the environment variables and start AppHost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ASPIRE_ALLOW_UNSECURED_TRANSPORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
export &lt;/span&gt;&lt;span class="nv"&gt;ASPNETCORE_URLS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:15900
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:15901
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:15902

dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; DCBNativeWasmProject.AppHost &lt;span class="nt"&gt;--no-launch-profile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These settings are for local execution. AppHost starts the dedicated PostgreSQL instance, the Runtime container, and the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Execution Results
&lt;/h2&gt;

&lt;p&gt;Create a student from Scalar, and then retrieve the student using the same ID. The following is the actual response returned by the API for the request shown below. The IDs will be different each time you run it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/students/
Content-Type: application/json

{
  "studentId": "10c8f945-8314-4631-8c3d-627dbd22934a",
  "name": "山田太郎",
  "maxClassCount": 5
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10c8f945-8314-4631-8c3d-627dbd22934a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01a08f5c-d51e-7cf9-9e22-ad1348d4137e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortableUniqueId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"063924708420894567502099121181"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Student created successfully"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, retrieve the student that was created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/students/10c8f945-8314-4631-8c3d-627dbd22934a
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10c8f945-8314-4631-8c3d-627dbd22934a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10c8f945-8314-4631-8c3d-627dbd22934a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"山田太郎"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maxClassCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enrolledClassRoomIds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The student creation event is applied by the Projector running in Wasm, and we can retrieve the student's state. List retrieval and updates can also be executed from the same Scalar screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusion
&lt;/h2&gt;

&lt;p&gt;By using SekibanWasmRuntime, we can change the execution environment while continuing to use existing models and Projectors, as in this example, by adding a Wasm entry point and connection settings. The business logic we have already created can also be used with this architecture.&lt;/p&gt;

&lt;p&gt;I also felt that by leaving event storage and Wasm execution to SekibanWasmRuntime, the application side can focus more on implementing the business logic: what kinds of operations the application accepts and what kinds of events those operations produce. Because the API can use the Runtime through a Remote connection, the part that calls the business processing can be separated from the part responsible for execution and storage.&lt;/p&gt;

&lt;p&gt;Samples for Go and TypeScript are also available as experimental/reference implementations, and I am looking forward to seeing further support added in the future.&lt;/p&gt;

</description>
      <category>eventsourcing</category>
      <category>webassembly</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>[Event Sourcing] Trying out Sekiban DCB: Implementation</title>
      <dc:creator>kairi</dc:creator>
      <pubDate>Wed, 16 Sep 2026 04:50:24 +0000</pubDate>
      <link>https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-implementation-2076</link>
      <guid>https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-implementation-2076</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article is a continuation of "&lt;a href="https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-introduction-4m78"&gt;[Event Sourcing] Trying out Sekiban DCB: Introduction&lt;/a&gt;". In the previous post, we covered setting up the DCB Native environment and its processing flow.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-introduction-4m78" class="crayons-story__hidden-navigation-link"&gt;[Event Sourcing] Trying out Sekiban DCB: Introduction&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/kary_0009" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4118744%2F056d5f37-da3f-4aab-bde8-1e7ce710249b.png" alt="kary_0009 profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/kary_0009" class="crayons-story__secondary fw-medium m:hidden"&gt;
              kairi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                kairi
                
                
              
              &lt;div id="story-author-preview-content-4662857" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/kary_0009" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4118744%2F056d5f37-da3f-4aab-bde8-1e7ce710249b.png" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;kairi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-introduction-4m78" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-introduction-4m78" id="article-link-4662857"&gt;
          [Event Sourcing] Trying out Sekiban DCB: Introduction
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/eventsourcing"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;eventsourcing&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dcb"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dcb&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-introduction-4m78#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Initially, features like creating a Student, fetching individual and list data, and enrolling/dropping classes are already implemented. The student's state is built from recorded events. The information a student holds includes Student ID, Name, Max Class Count, and a list of currently enrolled classes.&lt;/p&gt;

&lt;p&gt;In this post, we will add a feature to update a student's name and their maximum class limit. The Student ID and enrolled classes will remain unchanged. Decreasing the max limit below the current number of enrolled classes will be prohibited.&lt;/p&gt;

&lt;p&gt;When updating, we don't directly overwrite the current data. Instead, we record a &lt;code&gt;StudentProfileUpdated&lt;/code&gt; event. A Projector then processes this event to build the updated student state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update Feature Specifications
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Specification&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StudentId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specifies the student to update. It is not changed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required, 1 to 100 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MaxClassCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 to 10. Changing it to a value lower than the current enrollment count is not allowed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enrollment list&lt;/td&gt;
&lt;td&gt;Keeps the contents before the update.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-existing student&lt;/td&gt;
&lt;td&gt;The update is rejected.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The API receives JSON containing the student ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/students/update
Content-Type: application/json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9912a519-a6f6-41fe-92bf-07b99045efff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxClassCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will add the implementation in the order of Event, Decider, Command, Projector, and API.&lt;/p&gt;

&lt;p&gt;The sample code is available on &lt;a href="https://github.com/kdaigo/SekibanSamples/tree/main/DCBNativeProject" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Add an Event That Represents the Update
&lt;/h2&gt;

&lt;p&gt;We define the fact that the student information was updated as &lt;code&gt;StudentProfileUpdated&lt;/code&gt; event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Dcb.ImmutableModels.Tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Sekiban.Dcb.Events&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Dcb.ImmutableModels.Events.Student&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;StudentProfileUpdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;StudentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;MaxClassCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEventPayload&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;EventPayloadWithTags&lt;/span&gt; &lt;span class="nf"&gt;GetEventWithTags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StudentTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StudentId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event records the target student ID, the updated name, and the updated maximum number of classes. By adding &lt;code&gt;StudentTag&lt;/code&gt;, this event is associated with the target student.&lt;/p&gt;

&lt;p&gt;The enrollment list is not included in the event because it is not part of this update.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Implement Business Rules and State Updates in Deciders
&lt;/h2&gt;

&lt;p&gt;In Deciders, we implement validation based on the current state and the processing that applies the event to the state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Dcb.ImmutableModels.Events.Student&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Dcb.ImmutableModels.States.Student.Deciders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StudentProfileUpdatedDecider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StudentState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxClassCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxClassCount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnrolledClassRoomIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ApplicationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"MaxClassCount cannot be less than the current enrollment count."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;StudentState&lt;/span&gt; &lt;span class="nf"&gt;Evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;StudentState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;StudentProfileUpdated&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;MaxClassCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxClassCount&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;Validate&lt;/code&gt;, the updated maximum class count is compared with the current number of enrolled classes. For example, if a student is enrolled in three classes, the maximum class count cannot be changed to two.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Evolve&lt;/code&gt;, the state with the contents of the event applied is returned. Only the name and maximum class count can be changed, while the student ID and enrollment list are kept unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Add a Command to Handle Update Requests
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;UpdateStudent&lt;/code&gt; is responsible for defining the input values and handling the processing from retrieving the state to generating the event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Dcb.ImmutableModels.Events.Student&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Dcb.ImmutableModels.States.Student&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Dcb.ImmutableModels.States.Student.Deciders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Dcb.ImmutableModels.Tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Sekiban.Dcb.Commands&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Sekiban.Dcb.Events&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Dcb.EventSource.Student&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;UpdateStudent&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandWithHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UpdateStudent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;StudentId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Name is required"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StringLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MinimumLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Name must be between 1 and 100 characters"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MaxClassCount must be between 1 and 10"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;MaxClassCount&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;UpdateStudent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;studentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxClassCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;StudentId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;studentId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;MaxClassCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maxClassCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EventOrNone&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;UpdateStudent&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ICommandContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StudentTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StudentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tagState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetStateAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StudentProjector&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;StudentState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ApplicationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Student does not exist."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;StudentProfileUpdatedDecider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxClassCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StudentProfileUpdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StudentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxClassCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEventWithTags&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Validation&lt;/th&gt;
&lt;th&gt;Implementation location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name is required and has a valid length&lt;/td&gt;
&lt;td&gt;Command attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum class count is between 1 and 10&lt;/td&gt;
&lt;td&gt;Command attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The student to update exists&lt;/td&gt;
&lt;td&gt;Command handler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum class count is greater than or equal to the current enrollment count&lt;/td&gt;
&lt;td&gt;Decider&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After Sekiban validates the Command attributes, the handler retrieves the current state of the student. If the business rule validation passes, it generates and returns &lt;code&gt;StudentProfileUpdated&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sekiban handles event persistence, so there is no need to write database persistence logic in the handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Add Update Event Handling to the Projector
&lt;/h2&gt;

&lt;p&gt;Saving the event alone does not reflect the update in the student state. We add handling for the update event and &lt;code&gt;Evolve&lt;/code&gt; to the Projector that builds the state.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;StudentProjector&lt;/code&gt;, which handles the state of a single student, add the following branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StudentState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StudentProfileUpdated&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The processing for each event becomes as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ITagStatePayload&lt;/span&gt; &lt;span class="nf"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ITagStatePayload&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Event&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EmptyTagStatePayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StudentCreated&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StudentCreatedDecider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StudentState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StudentProfileUpdated&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StudentState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StudentEnrolledInClassRoom&lt;/span&gt; &lt;span class="n"&gt;enrolled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enrolled&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StudentState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StudentDroppedFromClassRoom&lt;/span&gt; &lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also add handling for the same event to &lt;code&gt;StudentListProjection&lt;/code&gt;, which builds the student list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;StudentCreated&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StudentCreatedDecider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;StudentProfileUpdated&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;StudentEnrolledInClassRoom&lt;/span&gt; &lt;span class="n"&gt;enrolled&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enrolled&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;StudentDroppedFromClassRoom&lt;/span&gt; &lt;span class="n"&gt;dropped&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, the updated name and maximum class count are reflected in both the state of a single student and the student list.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Add the Update API
&lt;/h2&gt;

&lt;p&gt;Add an endpoint to &lt;code&gt;StudentEndpoints&lt;/code&gt; that receives the update Command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UpdateStudentAsync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UpdateStudent"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;UpdateStudentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;UpdateStudent&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromServices&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;ISekibanExecutor&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;execution&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;studentId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StudentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;eventId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sortableUniqueId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SortableUniqueId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Student updated successfully"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API directly receives &lt;code&gt;UpdateStudent&lt;/code&gt; and asks Sekiban to execute it. The check for whether the student exists and the validation of the maximum class count are handled through the Command and Decider processing.&lt;/p&gt;

&lt;p&gt;The flow from the update request to event persistence is as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/students/update
    ↓
UpdateStudent
    ↓
Validate input values
    ↓
Retrieve the current student state
    ↓
Check the existence of the student
and the consistency of the maximum class count
    ↓
Generate StudentProfileUpdated
    ↓
Sekiban stores the event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When building or updating the state from the stored event, the Projector calls the corresponding &lt;code&gt;Evolve&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Events Stored in PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Let's check the events actually stored in the database. In &lt;code&gt;dcb_events&lt;/code&gt;, the following creation event and update event are stored. The data below was retrieved in ascending order of &lt;code&gt;SortableUniqueId&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ServiceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01a0840a-df7f-7c43-9665-0564c61c7718"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SortableUniqueId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"063924518500223460000021724853"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"EventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"StudentCreated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9912a519-a6f6-41fe-92bf-07b99045efff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Student update persistence test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maxClassCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Student:9912a519-a6f6-41fe-92bf-07b99045efff"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-09T02:41:40.235475+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CausationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01a0840a-df7f-7c43-9665-0564c61c7718"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CorrelationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CreateStudent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ExecutedUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GeneralSekibanExecutor"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ServiceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01a0840a-e817-7b00-8c14-4f869aa9a78d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SortableUniqueId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"063924518502423536500632865263"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"EventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"StudentProfileUpdated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9912a519-a6f6-41fe-92bf-07b99045efff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Student update persistence test updated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maxClassCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Student:9912a519-a6f6-41fe-92bf-07b99045efff"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-09T02:41:42.43232+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CausationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01a0840a-e817-7b00-8c14-4f869aa9a78d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CorrelationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UpdateStudent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ExecutedUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GeneralSekibanExecutor"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the same student ID, the name and maximum class count at the time of creation and the updated name and maximum class count remain as separate events.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;StudentProfileUpdated&lt;/code&gt; is applied to the state created by &lt;code&gt;StudentCreated&lt;/code&gt;, the name becomes &lt;code&gt;Student update persistence test updated&lt;/code&gt;, and the maximum class count becomes &lt;code&gt;3&lt;/code&gt;. The Projector branch and the Decider's &lt;code&gt;Evolve&lt;/code&gt; added this time reproduce this state change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;By implementing the student information update step by step, I felt that I gained a deeper understanding of the event sourcing flow of "validating a change, recording it as an event, and building the state from that history," as well as the role of each process. I also thought it was very useful that persistence can be left to Sekiban, allowing us to focus on business rules and state changes.&lt;/p&gt;

&lt;p&gt;This time, I used Codex for the implementation. Instead of asking it to implement the entire feature at once, I proceeded while checking the purpose and code of each step. Since the processing in this structure is separated by responsibility, it worked well with this kind of implementation through dialogue, and I was able to implement the feature while building up my understanding step by step.&lt;/p&gt;

</description>
      <category>eventsourcing</category>
      <category>tutorial</category>
      <category>dcb</category>
    </item>
    <item>
      <title>[Event Sourcing] Trying out Sekiban DCB: Introduction</title>
      <dc:creator>kairi</dc:creator>
      <pubDate>Wed, 16 Sep 2026 02:51:17 +0000</pubDate>
      <link>https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-introduction-4m78</link>
      <guid>https://dev.to/kary_0009/event-sourcing-trying-out-sekiban-dcb-introduction-4m78</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;While researching ways to ensure data consistency using NoSQL in systems with frequent reads and writes, I became interested in Event Sourcing and CQRS. During this process, I found a framework called &lt;a href="https://www.sekiban.dev/" rel="noopener noreferrer"&gt;Sekiban&lt;/a&gt;, which is designed for Event Sourcing/CQRS, and decided to try it out.&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.sekiban.dev/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.sekiban.dev%2Fog-image-en.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.sekiban.dev/" rel="noopener noreferrer" class="c-link"&gt;
            Sekiban - Event Sourcing / CQRS Framework
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Sekiban is an open-source Event Sourcing and CQRS framework for .NET using C#. Store events in Azure Cosmos DB, PostgreSQL, or AWS DynamoDB.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.sekiban.dev%2Ffavicon.ico" width="48" height="48"&gt;
          sekiban.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What is Sekiban DCB?
&lt;/h2&gt;

&lt;p&gt;According to the official website, Sekiban is defined as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"An open-source Event Sourcing and CQRS framework for .NET. It stores not just the current state, but all changes as immutable events."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within this framework, Sekiban DCB is an Event Sourcing framework that implements a Dynamic Consistency Boundary (DCB). It records all events in a single global stream.&lt;/p&gt;

&lt;p&gt;Looking at their documentation, they offer three approaches: DCB Native, DCB Wasm, and Sekiban Cloud (which seems to be still in development and not yet released). For this guide, I will run it using Native C#.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Environment &amp;amp; Setup
&lt;/h2&gt;

&lt;p&gt;Prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OS: Windows 11 Pro&lt;/li&gt;
&lt;li&gt;Environment: ASP.NET development environment ready&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your environment is set up, you can create a new project with just a few commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the Sekiban DCB templates&lt;/span&gt;
dotnet new &lt;span class="nb"&gt;install &lt;/span&gt;Sekiban.Dcb.Templates

&lt;span class="c"&gt;# Create project with Decider pattern&lt;/span&gt;
dotnet new sekiban-dcb-decider &lt;span class="nt"&gt;-n&lt;/span&gt; MyApp

&lt;span class="c"&gt;# Run with Aspire&lt;/span&gt;
dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; MyApp.AppHost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the last command will start the Aspire dashboard. The &lt;a href="https://dcb.events/#how-it-works" rel="noopener noreferrer"&gt;official DCB documentation&lt;/a&gt; uses a Student and Class relationship as an example. The default project automatically includes sample code for registering students and enrolling them in classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;Here is a quick overview of the created project structure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;DCBNativeProject.AppHost&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manages dependent services, connections, ports, and startup order.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;DCBNativeProject.ApiService&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Handles API routing and authentication.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;DCBNativeProject.EventSource&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Contains Commands, Handlers, Projectors, and data search processing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;DCBNativeProject.ImmutableModels&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Defines Student/Class events, Tags, State, and Deciders.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To give you an idea of how it works, here is the flow for creating and getting a student record.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg6iyofqiv1h3soqg2iks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg6iyofqiv1h3soqg2iks.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Student
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Read the incoming JSON and convert it to a &lt;code&gt;CreateStudent&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt; Execute the command using &lt;code&gt;ExecuteAsync&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt; Generate a "StudentCreated" event (in this sample, it only uses a &lt;code&gt;StudentTag&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; The framework saves the event to the database.&lt;/li&gt;
&lt;li&gt; Return the result to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Retrieving a Student
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Receive the Student ID and find the target student using the &lt;code&gt;StudentTag&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Define the expected data state using a &lt;code&gt;StudentProjector&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Call &lt;code&gt;GetTagStateAsync&lt;/code&gt; to restore the current state from the past events.&lt;/li&gt;
&lt;li&gt; Return the result to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As you can see, database saving operations are completely handled by the framework. I was able to save and load events without worrying about the database code at all.&lt;/p&gt;

&lt;p&gt;Next time, I plan to add more Entities and try out some different event registrations!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>eventsourcing</category>
      <category>dcb</category>
    </item>
  </channel>
</rss>
