<?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: Takahiro Ikeuchi</title>
    <description>The latest articles on DEV Community by Takahiro Ikeuchi (@iktakahiro).</description>
    <link>https://dev.to/iktakahiro</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%2F58895%2F533fcd74-cf2d-45b4-8dc1-baefbcf5f2fb.jpeg</url>
      <title>DEV Community: Takahiro Ikeuchi</title>
      <link>https://dev.to/iktakahiro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iktakahiro"/>
    <language>en</language>
    <item>
      <title>I Built an Open-Source Local Google Calendar API and OAuth Server for Testing AI Agents and MCP Tools</title>
      <dc:creator>Takahiro Ikeuchi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 12:45:16 +0000</pubDate>
      <link>https://dev.to/iktakahiro/i-built-an-open-source-local-google-calendar-api-and-oauth-server-for-testing-ai-agents-and-mcp-4319</link>
      <guid>https://dev.to/iktakahiro/i-built-an-open-source-local-google-calendar-api-and-oauth-server-for-testing-ai-agents-and-mcp-4319</guid>
      <description>&lt;p&gt;Lately I have been building more Google Calendar integrations for AI agents and AI app plugins, such as MCP servers that check &lt;code&gt;freeBusy&lt;/code&gt;, list events, or create and update events. Testing them against the live Google Calendar API is painful.&lt;/p&gt;

&lt;p&gt;So I built Calendarpit.&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%2Fdsu06qw3bm3xlvm7hxo6.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%2Fdsu06qw3bm3xlvm7hxo6.png" alt="Admin UI API request history log" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Calendarpit's admin UI: every Calendar API and OAuth request your app sends, with status codes and masked credentials.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Live tests need a Google Cloud project, OAuth client credentials, and consent screens with dedicated test accounts. State drifts between runs, and a buggy agent could spam attendees or wipe out real events.&lt;/p&gt;

&lt;p&gt;I wanted a local emulator that works with the official Google SDK, can be inspected in a browser, and resets cleanly between runs.&lt;/p&gt;

&lt;p&gt;Calendarpit came from the needs of the development team behind &lt;a href="https://www.chronock.ai/" rel="noopener noreferrer"&gt;Chronock&lt;/a&gt;, an all-in-one calendar app that brings Google Calendar, Outlook calendars, scheduling links, and automatic event copying into one place. It stays a general-purpose tool, and none of Chronock's data or logic is in it.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Calendarpit Is and What It Is Not
&lt;/h2&gt;

&lt;p&gt;Calendarpit is an MIT-licensed open-source local server for development and testing. One process serves a Google Calendar API v3 subset, a local OAuth 2.0 server, and an admin UI on &lt;code&gt;http://127.0.0.1:4173&lt;/code&gt;. It needs no Google account, Google Cloud project, Google-issued credentials, or external database.&lt;/p&gt;

&lt;p&gt;The server never calls Google, and the only external request is the admin UI loading the Roboto Mono font from Google Fonts with a system-font fallback. The current version is &lt;code&gt;0.9.4&lt;/code&gt;, and &lt;code&gt;GET /__calendarpit/health&lt;/code&gt; still reports &lt;code&gt;"stage": "preview"&lt;/code&gt;. It is not a full clone: it covers calendars, events, recurrence, free/busy, incremental sync, and webhooks, and returns an explicit error for anything else.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try It in 30 Seconds
&lt;/h2&gt;

&lt;p&gt;It ships as a GHCR image, a Homebrew formula, and standalone executables on GitHub Releases for macOS, Linux glibc, and Windows.&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;# Run with Docker&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:4173:4173 ghcr.io/iktakahiro/calendarpit:latest

&lt;span class="c"&gt;# Or install with Homebrew and start the server&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;iktakahiro/tap/calendarpit
calendarpit serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI saves state to &lt;code&gt;.calendarpit/calendarpit.sqlite&lt;/code&gt; in the working directory and binds to loopback by default. Keep it that way, because the admin UI has no authentication.&lt;/p&gt;

&lt;p&gt;The Docker image stores state in &lt;code&gt;/data/calendarpit.sqlite&lt;/code&gt;, so the &lt;code&gt;--rm&lt;/code&gt; example above is disposable unless you mount a volume with &lt;code&gt;-v calendarpit-data:/data&lt;/code&gt;. For disposable E2E runs with the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;calendarpit serve &lt;span class="nt"&gt;--database&lt;/span&gt; :memory: &lt;span class="nt"&gt;--port&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--port 0&lt;/code&gt; picks a free port and prints it on startup. Parallel workers each need &lt;code&gt;:memory:&lt;/code&gt; or their own database file.&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%2F31xkvhum9gkhnz70uh6w.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%2F31xkvhum9gkhnz70uh6w.png" alt="Terminal output showing calendarpit serve startup and curl responses" width="800" height="437"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Startup output from &lt;code&gt;calendarpit serve&lt;/code&gt; and sample &lt;code&gt;curl&lt;/code&gt; responses.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Point Your Existing Google SDK at It
&lt;/h2&gt;

&lt;p&gt;Calendarpit does not intercept traffic. Instead, your app points five endpoints at the local origin: &lt;code&gt;/oauth/authorize&lt;/code&gt;, &lt;code&gt;/oauth/token&lt;/code&gt;, &lt;code&gt;/userinfo&lt;/code&gt;, &lt;code&gt;/oauth/revoke&lt;/code&gt;, and the Calendar API root. With &lt;code&gt;@googleapis/calendar&lt;/code&gt; and &lt;code&gt;google-auth-library&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:4173&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OAuth2Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sample-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local-sample-secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:4175/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;oauth2AuthBaseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/oauth/authorize`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;oauth2TokenUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/oauth/token`&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rootUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet covers authorization, token exchange, and the API root. &lt;a href="https://calendarpit.dev" rel="noopener noreferrer"&gt;The docs&lt;/a&gt; also show &lt;code&gt;oauth2RevokeUrl&lt;/code&gt;, the &lt;code&gt;/userinfo&lt;/code&gt; request, and the equivalent setup for Python, Go, Kotlin, PHP, C#, and &lt;code&gt;curl&lt;/code&gt;. Apart from endpoint configuration, your tests run through the same SDK code paths as production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local OAuth 2.0 with Test Users
&lt;/h2&gt;

&lt;p&gt;Many integration bugs live in OAuth: redirects, PKCE, token refresh, and revocation. Mocking the client leaves that path untested.&lt;/p&gt;

&lt;p&gt;Calendarpit implements the OAuth flow locally without passwords. On the consent page, you pick a test user from a dropdown and approve or deny the request.&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%2F9c24eohvz3dh4fkduovf.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%2F9c24eohvz3dh4fkduovf.png" alt="Local OAuth consent screen with test-user dropdown" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The local consent page: pick a test user, approve or deny.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It checks exact &lt;code&gt;redirect_uri&lt;/code&gt; matches, preserves &lt;code&gt;state&lt;/code&gt;, supports &lt;code&gt;S256&lt;/code&gt; PKCE, issues refresh tokens for &lt;code&gt;access_type=offline&lt;/code&gt;, and serves &lt;code&gt;/userinfo&lt;/code&gt; and &lt;code&gt;/oauth/revoke&lt;/code&gt;, without issuing &lt;code&gt;id_token&lt;/code&gt;s or full OIDC. The &lt;strong&gt;Connections&lt;/strong&gt; view shows each grant and lets you revoke it to test reauthorization. You can edit OAuth 2.0 clients under &lt;strong&gt;Settings â†’ OAuth clients&lt;/strong&gt; or in the fixture.&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%2Fok3dv484vnf8wgjva5ms.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%2Fok3dv484vnf8wgjva5ms.png" alt="Admin UI connections page showing OAuth clients and issued tokens" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each connection is an OAuth grant: test user, client, and scopes. Revoke invalidates its tokens.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fixtures and Deterministic Reset
&lt;/h2&gt;

&lt;p&gt;Every test should start from a known calendar state rather than data from a previous run. Calendarpit seeds its workspace from a JSON fixture via &lt;code&gt;--config ./calendarpit.fixture.json&lt;/code&gt; or the built-in default at &lt;code&gt;fixtures/default.json&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="nl"&gt;"users"&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;"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;"alex"&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;"Alex Morgan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alex@example.test"&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;"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;"sam"&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;"Sam Rivera"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sam@example.test"&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;"calendars"&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;"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;"alex-work"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Work calendar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"timeZone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia/Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"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;"alex-personal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Personal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"timeZone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia/Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;"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;"sam-work"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Work calendar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"timeZone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"events"&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;"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;"planning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"calendarId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alex-work"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Weekly planning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"start"&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;"dateTime"&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-10-19T10:00:00+09:00"&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;"end"&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;"dateTime"&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-10-19T11:00:00+09:00"&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="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clients"&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;"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;"sample-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local-sample-secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"redirectUris"&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;"http://127.0.0.1:4175/callback"&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="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;Between tests, one request resets everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:4173/__calendarpit/reset &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resetting restores the startup fixture with the same IDs and clears tokens, connections, watch channels, and history, so tests authorize again after each reset. &lt;strong&gt;Settings â†’ Test data&lt;/strong&gt; offers the same controls and can load another fixture to replace the current data. Reset still returns to the startup fixture.&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%2F94yhilvmujze3g8uhxkx.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%2F94yhilvmujze3g8uhxkx.png" alt="Admin UI settings page showing fixture configuration and workspace reset controls" width="799" height="560"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Settings â†’ Test data: storage mode, fixture loading, and workspace reset.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Test Webhooks Without a Tunnel
&lt;/h2&gt;

&lt;p&gt;Testing Google Calendar &lt;code&gt;events.watch&lt;/code&gt; and &lt;code&gt;calendarList.watch&lt;/code&gt; push notifications against the live API requires a public HTTPS callback. In local development, that usually means running a tunnel and waiting on deliveries you cannot control.&lt;/p&gt;

&lt;p&gt;With Calendarpit, you register a channel pointing at a local callback using an access token from the OAuth flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:4173/calendar/v3/calendars/primary/events/watch &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;access-token&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "id": "channel-1",
    "type": "web_hook",
    "address": "http://127.0.0.1:4175/webhooks/calendar",
    "token": "client-verification-token"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calendarpit sends a &lt;code&gt;sync&lt;/code&gt; notification when the channel is created and an &lt;code&gt;exists&lt;/code&gt; notification on each change until the channel expires or &lt;code&gt;channels.stop&lt;/code&gt; is called. Only loopback hosts are allowed by default, and &lt;code&gt;CALENDARPIT_WATCH_HOSTS&lt;/code&gt; adds other hosts such as a Docker Compose service. Each notification gets up to 3 delivery attempts, and tests can call &lt;code&gt;POST /__calendarpit/deliveries/drain&lt;/code&gt; instead of &lt;code&gt;sleep()&lt;/code&gt; to wait for pending deliveries.&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%2Flm5zik8lyddduciwr18e.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%2Flm5zik8lyddduciwr18e.png" alt="Admin UI watch channels and webhook delivery log" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Monitoring active watch channels and inspecting webhook delivery attempts in the admin UI.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How It Is Built: Architecture, Bun, and Storage
&lt;/h2&gt;

&lt;p&gt;Calendarpit is a Turborepo monorepo built on Bun &lt;code&gt;1.4.3&lt;/code&gt;: runtime, package manager, &lt;code&gt;bun:test&lt;/code&gt;, &lt;code&gt;bun check&lt;/code&gt; for type checking, HTTP server, and the standalone build (&lt;code&gt;Bun.build&lt;/code&gt; with &lt;code&gt;compile&lt;/code&gt;). Oxlint and Oxfmt handle linting and formatting.&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%2Fysdok82eoj8q9dw0ynzk.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%2Fysdok82eoj8q9dw0ynzk.png" alt="Calendarpit architecture diagram" width="800" height="407"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Workspace boundaries and request routing across the Calendarpit monorepo.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;packages/server&lt;/code&gt;: the framework-independent Calendar API, OAuth, and watch logic.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apps/calendarpit&lt;/code&gt;: the React admin UI, TanStack Start adapter, Bun CLI, and packaging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apps/sample&lt;/code&gt;: a separate Bun app that uses the official &lt;code&gt;@googleapis/calendar&lt;/code&gt; and &lt;code&gt;google-auth-library&lt;/code&gt; as an HTTP integration test.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apps/docs&lt;/code&gt;: the documentation and landing page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;createCalendarpit()&lt;/code&gt; exposes an async handler for Web &lt;code&gt;Request&lt;/code&gt; objects. It returns a response for API, OAuth, and management routes, or &lt;code&gt;null&lt;/code&gt; for UI routes. The TanStack Start entry in &lt;code&gt;apps/calendarpit/src/server.ts&lt;/code&gt; tries it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;handleStartRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server code is grouped by feature with role suffixes: &lt;code&gt;-routes.ts&lt;/code&gt; is the HTTP adapter, &lt;code&gt;-operations.ts&lt;/code&gt; changes workspace state as one unit of work, and plain rule modules hold validation and calculations. Dependencies point inward, enforced by &lt;code&gt;architecture.test.ts&lt;/code&gt; and Oxlint's cycle check. &lt;code&gt;AGENTS.md&lt;/code&gt; files at the root and in each workspace document these rules for contributors and coding agents.&lt;/p&gt;

&lt;p&gt;I skipped full DDD or onion layers. An emulator's domain model is the Google Calendar API v3 contract, so entity classes, DTO mappers, repository interfaces, and a DI container would only add indirection.&lt;/p&gt;

&lt;p&gt;State lives in memory and is checkpointed as a versioned JSON snapshot into one SQLite row via &lt;code&gt;bun:sqlite&lt;/code&gt;, with WAL, &lt;code&gt;FULL&lt;/code&gt; sync, and a lock file so two instances cannot overwrite the same database. &lt;code&gt;:memory:&lt;/code&gt; writes no files.&lt;/p&gt;

&lt;p&gt;Time math uses &lt;code&gt;Temporal&lt;/code&gt; and the &lt;code&gt;rrule&lt;/code&gt; library. The clock and webhook delivery are injected, so Calendarpit's own expiry and retry tests need no sleeps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Admin UI and the TanStack Ecosystem
&lt;/h2&gt;

&lt;p&gt;When an E2E run or an agent misbehaves, I want to see the calendar and the exact HTTP calls side by side. That is what the admin UI is for.&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%2Fdvfl7kagpyqypt8w7fgi.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%2Fdvfl7kagpyqypt8w7fgi.png" alt="Admin UI week schedule view" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The week schedule view in the admin UI showing events across local test calendars.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It runs on React 19, Vite 8 on Bun, Tailwind CSS 4, Gravity UI Icons, and TanStack Start, Router, Query, and Form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TanStack Start and Router&lt;/strong&gt;: SSR and file-based routes for &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;/users&lt;/code&gt;, &lt;code&gt;/calendars&lt;/code&gt;, &lt;code&gt;/connections&lt;/code&gt;, &lt;code&gt;/watch&lt;/code&gt;, &lt;code&gt;/history&lt;/code&gt;, and &lt;code&gt;/settings&lt;/code&gt;. The selected user, calendar, date, and view live in search params, so a reload or a shared link restores the same view.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TanStack Query&lt;/strong&gt;: reads the management API and polls every 2 seconds, so events your app or agent writes through the Calendar API appear without a reload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TanStack Form&lt;/strong&gt;: the event, user, calendar, and OAuth client editors, each with one form-level validator.&lt;/li&gt;
&lt;/ul&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%2F1ky4x3q2xy8wkdi9hel3.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%2F1ky4x3q2xy8wkdi9hel3.png" alt="Admin UI event editor built with TanStack Form" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Creating and editing calendar events in the admin UI using TanStack Form.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The UI has limits too. Dragging events to move or resize them in the calendar grid is not implemented yet, so time changes go through the event editor. The editor covers basic fields, while recurrence, attendees, and conference data are set through the API or fixtures.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building the Interface with Cloudflare Kumo
&lt;/h2&gt;

&lt;p&gt;For UI components, the admin UI uses &lt;code&gt;@cloudflare/kumo&lt;/code&gt; &lt;code&gt;2.14.0&lt;/code&gt;, Cloudflare's MIT-licensed open-source React component library. The community loves shadcn/ui and it is great, but I prefer components I can use as a library rather than copy and paste.&lt;/p&gt;

&lt;p&gt;My usual pick is HeroUI, and I kept reaching for it every time. So I looked for a component kit built on a headless UI library and found Kumo, made by Cloudflare and built on Base UI. &lt;em&gt;Kumo&lt;/em&gt; (é›²) means "cloud" in Japanese, and as a Japanese developer I felt an affinity for it.&lt;/p&gt;

&lt;p&gt;Kumo shows up in 24 source files, from &lt;code&gt;Button&lt;/code&gt; and &lt;code&gt;Select&lt;/code&gt; to &lt;code&gt;DatePicker&lt;/code&gt;, &lt;code&gt;Table&lt;/code&gt;, &lt;code&gt;Dialog&lt;/code&gt;, &lt;code&gt;Toast&lt;/code&gt;, and &lt;code&gt;CommandPalette&lt;/code&gt;. Wiring it into Tailwind CSS 4 takes three lines in &lt;code&gt;app.css&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'@cloudflare/kumo/styles/tailwind'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'tailwindcss'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../node_modules/@cloudflare/kumo/dist/**/*.{js,jsx,ts,tsx}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kumo has no time picker, so the event editor pairs Kumo's &lt;code&gt;DatePicker&lt;/code&gt; and &lt;code&gt;Popover&lt;/code&gt; with a native &lt;code&gt;&amp;lt;input type="time"&amp;gt;&lt;/code&gt; themed by Kumo's &lt;code&gt;Input&lt;/code&gt;. Phosphor Icons is installed only as a Kumo peer dependency, while the app's own icons come from Gravity UI Icons.&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%2Fgo9y3dno5ty9tr7v9cye.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%2Fgo9y3dno5ty9tr7v9cye.png" alt="Event editor showing Kumo DatePicker, Popover, Input, and Select components" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Kumo DatePicker, Popover, Input, and Select components working together inside the event editor.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cmd+K&lt;/code&gt; / &lt;code&gt;Ctrl+K&lt;/code&gt; opens Kumo's &lt;code&gt;CommandPalette&lt;/code&gt; to jump between sections, switch test users or calendars, or go to a date.&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%2F13hgrwwj23svwfad4hs3.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%2F13hgrwwj23svwfad4hs3.png" alt="Command palette built with Kumo CommandPalette" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Navigating views and actions with Kumo's CommandPalette.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On top of Kumo, I added a "timing-bench" theme with warm paper surfaces, near-black ink, monospace readouts, and one signal color for selection and the "now" line. A dark theme follows your system setting by default.&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%2Fwaqp6gwatypij76if7no.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%2Fwaqp6gwatypij76if7no.png" alt="Admin UI week schedule view in dark theme" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The week schedule view rendered in the dark timing-bench theme.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing Strategy, Supply-Chain Precautions, and Releases
&lt;/h2&gt;

&lt;p&gt;If your tests rely on Calendarpit, Calendarpit itself has to be well tested. The repository has 61 &lt;code&gt;bun:test&lt;/code&gt; files and 5 Playwright E2E files, organized in layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Runner &amp;amp; Scope&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rules &amp;amp; Operations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bun:test&lt;/code&gt; with &lt;code&gt;test.each&lt;/code&gt; on pure validation/recurrence functions, plus in-memory workspace operations using a fixed clock.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP Contract &amp;amp; Lifecycle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Direct &lt;code&gt;app.handle(Request)&lt;/code&gt; contract assertions and temporary SQLite file tests verifying server restarts, tokens, and watch persistence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Official SDK (&lt;code&gt;apps/sample&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Integration tests using the official &lt;code&gt;@googleapis/calendar&lt;/code&gt; and &lt;code&gt;google-auth-library&lt;/code&gt; clients over HTTP.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser E2E (Playwright)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs against the built CLI (&lt;code&gt;--database :memory:&lt;/code&gt;), resetting fixtures before each test and submitting OAuth consent in a real browser.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Binary &amp;amp; Docker Smoke&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tests the compiled executable in an isolated directory, plus the container's non-root runtime, HTTP/UI, volume persistence, reset, and memory isolation.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Dependencies are pinned to exact versions. As a supply-chain precaution, &lt;code&gt;bunfig.toml&lt;/code&gt; only resolves package versions published at least 48 hours ago, with a matching 2-day cooldown for &lt;code&gt;npm-check-updates&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;CI runs &lt;code&gt;bun run validate&lt;/code&gt;, builds and smoke-tests the binary, and runs the Playwright suite. The release workflow validates native executables per OS and CPU, publishes checksummed archives to GitHub Releases, smoke-tests and pushes the GHCR image, and updates the Homebrew formula.&lt;/p&gt;
&lt;h2&gt;
  
  
  Give It a Try
&lt;/h2&gt;

&lt;p&gt;If you are building a Google Calendar integration, an MCP server, or an AI agent that works with calendar events, give Calendarpit a try locally or in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:4173:4173 ghcr.io/iktakahiro/calendarpit:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/iktakahiro/calendarpit" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/calendarpit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation (English)&lt;/strong&gt;: &lt;a href="https://calendarpit.dev" rel="noopener noreferrer"&gt;https://calendarpit.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation (Japanese)&lt;/strong&gt;: &lt;a href="https://calendarpit.dev/ja/" rel="noopener noreferrer"&gt;https://calendarpit.dev/ja/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it saves you time, a GitHub star helps others find it. Issues and feedback are welcome, especially on which endpoints, query parameters, or recurrence patterns you need next.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>testing</category>
      <category>googlecalendar</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Rethinking Architecture in the AI Era — Part 1: Repository Management</title>
      <dc:creator>Takahiro Ikeuchi</dc:creator>
      <pubDate>Mon, 16 Mar 2026 06:24:31 +0000</pubDate>
      <link>https://dev.to/iktakahiro/rethinking-architecture-in-the-ai-era-part-1-repository-management-2ia4</link>
      <guid>https://dev.to/iktakahiro/rethinking-architecture-in-the-ai-era-part-1-repository-management-2ia4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first installment of a series on software product architecture for the AI age. There's no shortage of content about how fast AI can accelerate prototyping — but I believe medium-term maintainability deserves equal attention. (If this ends up being a one-off article, my apologies in advance.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today's topic: repository management. My conclusion upfront: &lt;strong&gt;go with a monorepo&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is a Monorepo?
&lt;/h2&gt;

&lt;p&gt;For those new to the concept, a monorepo is the practice of managing multiple applications within a single repository. "Multiple apps" can mean any of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A frontend and a backend living together&lt;/li&gt;
&lt;li&gt;Full-stack apps for Service A and Service B side by side&lt;/li&gt;
&lt;li&gt;Application code alongside shared libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick aside on microservices: while monorepos are sometimes discussed in that context, I don't have hands-on experience combining the two, so I'll leave that out of scope here. My instinct is that getting the most out of a monorepo requires keeping your tech stack reasonably unified — which arguably conflicts with what microservices are trying to achieve. The monorepo style I'm describing here probably isn't a great fit for microservices.&lt;/p&gt;

&lt;p&gt;All that said, rather than dwelling on definitions, the practical answer is simple: &lt;strong&gt;use Turborepo&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Turborepo?
&lt;/h2&gt;

&lt;p&gt;Turborepo has become the de facto standard for monorepo setups in TypeScript projects. While it's technically positioned as a general-purpose build tool, in practice you reach for it when adopting a monorepo (or when you want smarter build caching).&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://turborepo.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%2Fturborepo.dev%2Fapi%2Fog%3Ftitle%3D%26sig%3D54b3733c98266d96900a8dc5ccddbd185b2fdd3105f712af4809ede9bc0a7b10" height="630" class="m-0" width="1200"&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://turborepo.dev/" rel="noopener noreferrer" class="c-link"&gt;
            Turborepo
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Turborepo is a build system optimized for JavaScript and TypeScript, written in Rust.
          &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%2Fturborepo.dev%2F_next%2Fstatic%2Fmedia%2Frepo-light-32x32.0nqkv~~6o4~vl.png" width="32" height="32"&gt;
          turborepo.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Here's a representative directory structure for a web frontend + backend project using Turborepo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-turborepo/
├── apps/
│   ├── web/                  # Frontend app (e.g. Next.js)
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── api/                  # Backend app (e.g. Hono, Express)
│       ├── src/
│       ├── package.json
│       └── tsconfig.json
├── packages/
│   ├── ui/                   # Shared UI component library
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── tsconfig/             # Shared TypeScript config
│   │   ├── base.json
│   │   ├── nextjs.json
│   │   └── package.json
│   └── utils/                # Shared utilities
│       ├── src/
│       ├── package.json
│       └── tsconfig.json
├── .claude/
│   ├── CLAUDE.md
│   └── skills/
├── .codex/
│   └── skills/
├── turbo.json
├── biome.json
├── package.json
├── pnpm-workspace.yaml
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is straightforward: &lt;code&gt;apps/&lt;/code&gt; holds your individual applications; &lt;code&gt;packages/&lt;/code&gt; holds shared code — utilities, UI components, and config files like &lt;code&gt;tsconfig&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You could technically replicate this directory structure without Turborepo, but Turborepo adds real value by reading each &lt;code&gt;package.json&lt;/code&gt;, running builds and tests in bulk or in parallel, and handling build cache intelligently — a significant advantage for larger projects where build times start to hurt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a Monorepo in the AI Era?
&lt;/h2&gt;

&lt;p&gt;Knowledge base management is increasingly recognized as critical in AI-era product development — and that knowledge base naturally includes your codebase. A knowledge base only has value if it's actually referenced. And to be referenced easily, knowledge needs to be &lt;em&gt;close together&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;With a monorepo, the moment you clone a single GitHub repository, you have everything in one place: application code, development documentation, agent skills — the full knowledge base for your product. You &lt;em&gt;could&lt;/em&gt; devise ways to help AI recognize multiple repositories as belonging to the same project, but why bother? A monorepo gives you that coherence for free.&lt;/p&gt;

&lt;p&gt;A common objection goes something like: &lt;em&gt;"Frontend and backend only need to communicate via API contracts — mixing their contexts is unnecessary."&lt;/em&gt; I'd argue this mental model is worth updating. In the age of AI coding, the leverage comes from architectures that let you see the full picture and move fast — not from strict context separation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conditions for Monorepo Success
&lt;/h2&gt;

&lt;p&gt;Monorepos pair well with AI-era development, but a few conditions make them work significantly better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Your backend is written in TypeScript&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A mixed stack — TypeScript frontend, Python backend — can technically be a monorepo, but the shared &lt;code&gt;packages/&lt;/code&gt; become frontend-only, narrowing their value. You also end up managing linting, formatting, and tooling for two different languages.&lt;/p&gt;

&lt;p&gt;Worth noting: Turborepo is built around TypeScript (and &lt;code&gt;package.json&lt;/code&gt; as the primary unit of configuration), so aligning your stack on TypeScript is the path of least resistance here too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Your review process is adapted for AI-assisted development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a monorepo with AI coding, implementing a single feature often means the AI writes frontend &lt;em&gt;and&lt;/em&gt; backend code in one go. Pull requests naturally reflect that end-to-end implementation. You could split them up — but if you're still enforcing pre-AI-era review rituals, humans become the bottleneck. At minimum, integrating AI-assisted code review is necessary; a broader rethink of your PR workflow is worth considering.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Example: Chronock
&lt;/h2&gt;

&lt;p&gt;My own app, &lt;a href="https://www.chronock.ai/ja-JP/" rel="noopener noreferrer"&gt;Chronock&lt;/a&gt;, is built as a monorepo with TypeScript across the full stack. Here's the structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chronock/
├── apps/
│   ├── chronock-app/              # Frontend (React + TanStack Start)
│   ├── chronock-backend/          # Backend (Bun + Connect RPC)
│   ├── chronock-book/             # Booking page (React + TanStack Start)
│   └── chronock-www/              # Marketing site (Astro)
│
├── packages/
│   ├── contract/                  # Proto definitions &amp;amp; generated code
│   ├── i18n/                      # Internationalization utilities
│   └── typescript-config/         # Shared TypeScript config
│
├── specs/                         # Product specifications
│
├── .claude/
│   ├── CLAUDE.md
│   └── skills/
├── .codex/
│   └── skills/
├── biome.jsonc                    # Linter / Formatter config
├── bunfig.toml                    # Bun config
├── docker-compose.local.yml
├── package.json
└── turbo.json                     # Turborepo config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth highlighting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;marketing site&lt;/strong&gt; (Astro) lives in the monorepo alongside the app. This makes tasks like "reflect new feature messaging on the website" or "check for inconsistencies between the latest implementation and the landing page copy" natural things to hand off to an AI agent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontend–backend communication&lt;/strong&gt; uses Connect RPC (Protocol Buffers). The &lt;code&gt;.proto&lt;/code&gt; definition files live in &lt;code&gt;packages/contract&lt;/code&gt;, making it seamless to manage the interface definition and run codegen for both frontend and backend from one place.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Product specs and agent skills&lt;/strong&gt; live in the same repository, which makes coding agents noticeably more effective — they have the context they need without jumping across repos.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Architecture choices for the AI era:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Centralize context in a monorepo&lt;/strong&gt; and treat it as a living knowledge base&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aligning your stack on TypeScript&lt;/strong&gt; pays off in multiple ways&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The pull request problem doesn't solve itself&lt;/strong&gt; — it needs its own dedicated solution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a wrap for Part 1.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to get started DDD &amp; Onion-Architecture in Python web application</title>
      <dc:creator>Takahiro Ikeuchi</dc:creator>
      <pubDate>Fri, 22 Apr 2022 11:29:23 +0000</pubDate>
      <link>https://dev.to/iktakahiro/how-to-get-started-ddd-onion-architecture-in-python-web-application-3912</link>
      <guid>https://dev.to/iktakahiro/how-to-get-started-ddd-onion-architecture-in-python-web-application-3912</guid>
      <description>&lt;p&gt;I published the following code for sharing knowledge of DDD &amp;amp; Onion Architecture in Python web applications.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/iktakahiro" rel="noopener noreferrer"&gt;
        iktakahiro
      &lt;/a&gt; / &lt;a href="https://github.com/iktakahiro/dddpy" rel="noopener noreferrer"&gt;
        dddpy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Python DDD &amp;amp; Onion Architecture Example and Techniques
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Python DDD &amp;amp; Onion-Architecture Example and Techniques&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/iktakahiro/dddpy/actions/workflows/test.yaml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/iktakahiro/dddpy/actions/workflows/test.yaml/badge.svg" alt="A workflow to run test"&gt;&lt;/a&gt;
&lt;a href="https://deepwiki.com/iktakahiro/dddpy" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0f5ae213ac378635adeb5d7f13cef055ad2f7d9a47b36de7b1c67dbe09f609ca/68747470733a2f2f6465657077696b692e636f6d2f62616467652e737667" alt="Ask DeepWiki"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;English | &lt;a href="https://github.com/iktakahiro/dddpy/README.ja_JP.md" rel="noopener noreferrer"&gt;日本語&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go implementation: &lt;a href="https://github.com/iktakahiro/oniongo" rel="noopener noreferrer"&gt;oniongo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: This repository is an example to demonstrate "how to implement DDD architecture in a Python web application." If you use this as a reference, ensure to implement authentication and security before deploying it to a real-world environment!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DeepWiki powered by Devin: &lt;a href="https://deepwiki.com/iktakahiro/dddpy" rel="nofollow noopener noreferrer"&gt;https://deepwiki.com/iktakahiro/dddpy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;My blog post: &lt;a href="https://iktakahiro.dev/python-ddd-onion-architecture" rel="nofollow noopener noreferrer"&gt;https://iktakahiro.dev/python-ddd-onion-architecture&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fastapi.tiangolo.com/" rel="nofollow noopener noreferrer"&gt;FastAPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.sqlalchemy.org/" rel="nofollow noopener noreferrer"&gt;SQLAlchemy&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.sqlite.org/index.html" rel="nofollow noopener noreferrer"&gt;SQLite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/" rel="nofollow noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.13 or higher&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt; - Python package installer and resolver&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Project Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Install dependencies using uv:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;make install&lt;/pre&gt;

&lt;/div&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Run the web app&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;make dev&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Code Architecture&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The directory structure is based on &lt;a href="https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/" rel="nofollow noopener noreferrer"&gt;Onion Architecture&lt;/a&gt;:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;├── main.py
├── dddpy
│   ├── domain
│   │   └── todo
│   │       ├── entities
│   │       │   └── todo.py
│   │       ├── value_objects
│   │       │   ├── todo_title.py
│   │       │   ├── todo_description.py
│   │       │   ├── todo_id.py
│   │       │   └── todo_status.py
│&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/iktakahiro/dddpy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I've also written a git of it on README. But in some ways, DDD is too difficult for us to understand; I would like to explain this architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;My day job is to develop apps for smartphones using Flutter. In this development, I've adopted DDD with Onion architecture. This approach has worked well so far. On the other hand, the requirements of a native application are more complex than for typical web apps. So, I wanted to see what would happen if I applied DDD to a thin web app.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;DDD can be adopted in the Python world as well.&lt;/li&gt;
&lt;li&gt;For users familiar with MVC through prominent frameworks such as Django, this codebase may seem bizarre. But I believe it will help in many ways, especially to ensure maintainability and testability.&lt;/li&gt;
&lt;li&gt;The variety of interfaces that make up DDD is not necessarily compatible with the dynamically typed language. However, it is achievable using the standard features provided by Python.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Code Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DDD &amp;amp; Onion Architecture
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── main.py
├── dddpy
│   ├── domain
│   │   └── book
│   │       ├── book.py  # Entity
│   │       ├── book_exception.py  # Exception definitions
│   │       ├── book_repository.py  # Repository interface
│   │       └── isbn.py  # Value Object
│   ├── infrastructure
│   │   └── sqlite
│   │       ├── book
│   │       │   ├── book_dto.py  # DTO using SQLAlchemy
│   │       │   ├── book_query_service.py  # Query service implementation
│   │       │   └── book_repository.py  # Repository implementation
│   │       └── database.py
│   ├── presentation
│   │   └── schema
│   │       └── book
│   │           └── book_error_message.py
│   └── usecase
│       └── book
│           ├── book_command_model.py  # Write models including schemas of the RESTFul API
│           ├── book_command_usecase.py
│           ├── book_query_model.py  # Read models including schemas
│           ├── book_query_service.py  # Query service interface
│           └── book_query_usecase.py
└── tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&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__body flex items-center justify-between"&gt;
        &lt;a href="https://fastapi.tiangolo.com/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;fastapi.tiangolo.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&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.sqlalchemy.org/" 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.sqlalchemy.org%2Fimg%2Fsqla_og.png" height="421" class="m-0" width="799"&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.sqlalchemy.org/" rel="noopener noreferrer" class="c-link"&gt;
            
        
        SQLAlchemy - The Database Toolkit for Python
        
    
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            The Database Toolkit for Python
          &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.sqlalchemy.org%2Ffavicon.ico" width="16" height="16"&gt;
          sqlalchemy.org
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Techniques
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Repository pattern&lt;/li&gt;
&lt;li&gt;Data Transfer Object&lt;/li&gt;
&lt;li&gt;CQRS pattern&lt;/li&gt;
&lt;li&gt;Unit of Work pattern&lt;/li&gt;
&lt;li&gt;Dependency Injection (using FastAPI feature)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to implement components of&amp;nbsp;DDD
&lt;/h2&gt;

&lt;p&gt;The assumption is that you are familiar with Eric Evans' book and Onion architecture, so I will not explain them. In the following, I will explain how you can implement each DDD component in the Python world.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entity
&lt;/h3&gt;

&lt;p&gt;To represent an &lt;strong&gt;Entity&lt;/strong&gt; in Python, use &lt;code&gt;__eq__()&lt;/code&gt; method to ensure the object's identity.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/domain/book/book.py" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/domain/book/book.py&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__eq__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Book&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, it's anemic. You have to implement more behaviors. I would add that the use of getter and setter is not Pythonic code. On the other hand, I think we can use it if necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Value Object
&lt;/h3&gt;

&lt;p&gt;As I understand it, the requirements for &lt;strong&gt;Value Object&lt;/strong&gt; are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's not an Entity&lt;/li&gt;
&lt;li&gt;If all the properties are the same, The objects are identical.&lt;/li&gt;
&lt;li&gt;As a result, they are interchangeable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To represent a Value Object, use &lt;code&gt;@dataclass&lt;/code&gt; decorator with &lt;code&gt;eq=True&lt;/code&gt; and &lt;code&gt;frozen=True&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/domain/book/isbn.py" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/domain/book/isbn.py&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Isbn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Isbn represents an ISBN code as a value object&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;isbn should be a valid format.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__setattr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Value Objects do not necessarily have to be immutable, but it is better to protect robustness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interface (abstract Class)
&lt;/h3&gt;

&lt;p&gt;On a duck-typing language, IMO, it does not go well with defining interfaces. But &lt;em&gt;Abstract Base class&lt;/em&gt; helps us to prepare them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/domain/book/book_repository.py" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/domain/book/book_repository.py&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&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;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Program to an interface, not an implementation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Data Transfer Object and a factory&amp;nbsp;method
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DTO&lt;/strong&gt; (Data Transfer Object) is a good practice to isolate domain objects from the infrastructure layer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/infrastructure/sqlite/book/book_dto.py" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/infrastructure/sqlite/book/book_dto.py&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;book&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;autoincrement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a minimum MVC architecture, models often inherit a base class provided by an O/R Mapper. But in that case, the domain layer would be dependent on the outer layer.&lt;/p&gt;

&lt;p&gt;To solve this problem, we can set two rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Domain layer classes (such as an Entity or a Value Object) DO NOT extend the SQLAlchemy Base class.&lt;/li&gt;
&lt;li&gt;Data transfer Objects extend the O/R mapper class.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those, including my friend, who assert a DTO and a repository pattern are an overzealous approach.&lt;/p&gt;

&lt;p&gt;I'm trying to tell you that you are not using the repository pattern to replace RDBs in the future. You have to take it to ensure testability today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read/Write models and request validation
&lt;/h2&gt;

&lt;p&gt;It is helpful to separate a read-only model from a write-only model to implement the &lt;strong&gt;CQRS&lt;/strong&gt; pattern.&lt;/p&gt;

&lt;p&gt;I hadn't anticipated this, but this approach also worked well for managing input/output validation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/usecase/book/book_query_model.py" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/usecase/book/book_query_model.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/usecase/book/book_command_model.py" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/usecase/book/book_command_model.py&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookReadModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vytxeTZskVKR7C7WgdSP3d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;978-0321125217&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FastAPI can set &lt;em&gt;response_model&lt;/em&gt; and &lt;em&gt;request_model&lt;/em&gt;. Amazingly, by associating the Read model with the Response model and the Write model with the Request model, API documentation can be generated and validated.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/main.py#L66" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/main.py#L66&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BookReadModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_201_CREATED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_409_CONFLICT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ErrorMessageBookIsbnAlreadyExists&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookCreateModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;book_command_usecase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookCommandUseCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book_command_usecase&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;book_command_usecase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;BookIsbnAlreadyExistsError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_409_CONFLICT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&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="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_500_INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class="p"&gt;,&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;book&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amazingly, by associating read models with the response_model and write models with the request model, the framework can generate API documentation and validate the request body.&lt;/p&gt;

&lt;h3&gt;
  
  
  CQRS pattern
&lt;/h3&gt;

&lt;p&gt;I will skip a general explanation of &lt;strong&gt;CQRS&lt;/strong&gt;. Instead, I would like to show the specific sequence in the figure below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/infrastructure/sqlite/book/book_query_service.py" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/infrastructure/sqlite/book/book_query_service.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/infrastructure/sqlite/book/book_repository.py" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/infrastructure/sqlite/book/book_repository.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fu5yg8zwfvfqfdpq6sfcd.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fu5yg8zwfvfqfdpq6sfcd.jpeg" alt="1_gf-EW5sNS46NZcPxdVFBHw.jpeg" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By splitting the Read model and Write model, we can flexibly respond to requests' input and output. My dear seniors educated me about DRY for a long time, but I've come to realize that it's not always absolute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit of Work&amp;nbsp;pattern
&lt;/h3&gt;

&lt;p&gt;In this short project, the missing piece is transaction management. For the gap, the &lt;strong&gt;Unit of Work&lt;/strong&gt; pattern fits almost entirely. But first, I would like to say that this is something that I would not strongly recommend to others, including you, because it is redundant, even for me, who suggested the repository pattern so much. However, I can't think of any other way to do transaction management without the assistance of a framework and its middleware.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/usecase/book/book_command_usecase.py#L18" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/usecase/book/book_command_usecase.py#L18&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookCommandUseCaseUnitOfWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;book_repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookRepository&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/dddpy/infrastructure/sqlite/book/book_repository.py#L63" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/dddpy/infrastructure/sqlite/book/book_repository.py#L63&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookCommandUseCaseUnitOfWorkImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BookCommandUseCaseUnitOfWork&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;book_repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;book_repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;book_repository&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dependency Injection
&lt;/h3&gt;

&lt;p&gt;This is a last topic, &lt;strong&gt;Dependency Injection&lt;/strong&gt; sounds like an exaggeration, but it essentially means assigning a class instance to a property of other classes. One of the reasons I like FastAPI is that it provides a DI mechanism by default.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/iktakahiro/dddpy/blob/main/main.py#L53" rel="noopener noreferrer"&gt;https://github.com/iktakahiro/dddpy/blob/main/main.py#L53&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;book_query_usecase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BookQueryUseCase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;book_query_service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookQueryService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BookQueryServiceImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;BookQueryUseCaseImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book_query_service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;book_command_usecase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BookCommandUseCase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;book_repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BookRepositoryImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;uow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookCommandUseCaseUnitOfWork&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BookCommandUseCaseUnitOfWorkImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book_repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;book_repository&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;BookCommandUseCaseImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;I don't think there is a widely known methodology for practicing DDD in Python. If you are going to try it, please refer to it.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ddd</category>
      <category>architecture</category>
      <category>fastapi</category>
    </item>
  </channel>
</rss>
