<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>振り返り on 怠惰技術ブログ</title>
    <link>/tags/%E6%8C%AF%E3%82%8A%E8%BF%94%E3%82%8A/</link>
    <description>Recent content in 振り返り on 怠惰技術ブログ</description>
    <generator>Hugo -- 0.147.7</generator>
    <language>ja</language>
    <lastBuildDate>Thu, 05 Mar 2026 23:00:00 +0900</lastBuildDate>
    <atom:link href="/tags/%E6%8C%AF%E3%82%8A%E8%BF%94%E3%82%8A/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>テスト前提で設計したWebアプリのハンズオン - 読書管理アプリ その6</title>
      <link>/posts/2026-03-05-test-driven-design-readmeter-6/</link>
      <pubDate>Thu, 05 Mar 2026 23:00:00 +0900</pubDate>
      <guid>/posts/2026-03-05-test-driven-design-readmeter-6/</guid>
      <description>&lt;h2 id=&#34;はじめに&#34;&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Part5でUIまで実装が終わった。機能として動くものはできた。テストも15件、全パス。&lt;/p&gt;
&lt;p&gt;ここで一度立ち止まって、&lt;strong&gt;なぜこの設計になったのか&lt;/strong&gt;を振り返る。&lt;/p&gt;
&lt;p&gt;正直に言う。最初の要件はこうだった。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「クリーンアーキテクチャっぽく、テスタブルにしたい」&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;それだけだった。クリーンアーキテクチャを理解して設計したわけじゃない。
&lt;strong&gt;結果的にクリーンアーキテクチャを遂行したのは生成AIだ。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;そしてPart3以降、自分はほとんど手を動かさなくなった。
設計ドキュメントをAIに渡したら、実装サイクルがほぼ自動で回るようになったからだ。&lt;/p&gt;
&lt;p&gt;途中でこう思った。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「俺いる必要なくね？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;この記事はその問いに向き合いながら、設計を改めて言語化したものだ。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;このシリーズ自体がtddだった&#34;&gt;このシリーズ自体がTDDだった&lt;/h2&gt;
&lt;p&gt;書き終えてから気づいたことがある。&lt;/p&gt;
&lt;p&gt;このシリーズのサイクルはこうだった。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;AIに実装させる
  ↓
動かす・読む・会話する（認識のズレを検出）
  ↓
ズレを言語化してAIにフィードバック
  ↓
納得したら記事にする
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ソフトウェアのTDDは「Red → Green → Refactor」だけど、
自分がやっていたのはこれだ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Red&lt;/strong&gt; → 認識がズレていると感じる&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Green&lt;/strong&gt; → 会話して納得する&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refactor&lt;/strong&gt; → 記事として言語化する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;人間がテストケースになっていた。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TDDが「仕様をテストで表現する」なら、自分がやっていたのは「理解をフィードバックループで検証する」だ。構造は同じだと思う。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;誤っていた認識たち&#34;&gt;誤っていた認識たち&lt;/h2&gt;
&lt;p&gt;振り返ると、理解がズレていた箇所がいくつかあった。&lt;/p&gt;
&lt;h3 id=&#34;policyはvoと1-1になると思っていた&#34;&gt;「PolicyはVOと1-1になる」と思っていた&lt;/h3&gt;
&lt;p&gt;最初、&lt;code&gt;ReadingStatusPolicy&lt;/code&gt;が&lt;code&gt;ReadingStatus&lt;/code&gt;に対応しているのを見て、PolicyはVOと対になるものだと思っていた。&lt;/p&gt;
&lt;p&gt;違う。今回たまたま1-1になっているだけだ。&lt;/p&gt;
&lt;p&gt;Policyの本質は&lt;strong&gt;複数のEntityやVOをまたいだ条件判定&lt;/strong&gt;だ。たとえば「同じ本に同じユーザーが2回レビューできない」というルールは、Book・User・Review[]をまたぐ。これをPolicyに出す。&lt;/p&gt;
&lt;p&gt;VOに閉じるルールならVOに書けばいい。Entityをまたぐ判定が必要になったときにPolicyの出番だ。&lt;/p&gt;
&lt;h3 id=&#34;domainは外部を知らないという捉え方が逆だった&#34;&gt;「Domainは外部を知らない」という捉え方が逆だった&lt;/h3&gt;
&lt;p&gt;「DomainはDBを知らない」という言い方をよくするが、正確には逆だ。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;外部がDomainを知っている。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;向きの問題だ。Domainが何かを避けているのではなく、依存の矢印がすべてDomainに向かって刺さっている。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Route Handler
  ↓
Service
  ↓
Repository interface
  ↓
Domain（Entity / ValueObject / Policy）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;この向きがわかって初めて、Serviceの役割も見えた。&lt;/p&gt;
&lt;h3 id=&#34;serviceが何をするのかわかっていなかった&#34;&gt;Serviceが何をするのかわかっていなかった&lt;/h3&gt;
&lt;p&gt;依存の向きが腑に落ちるまで、Serviceが何者なのかずっと曖昧だった。&lt;/p&gt;
&lt;p&gt;答えはシンプルだった。&lt;strong&gt;フローだけ持つ接着剤。&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;startReading&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Promise&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;Book&lt;/span&gt;&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;book&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;findById&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;);   &lt;span style=&#34;color:#75715e&#34;&gt;// 取得
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;book&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NotFoundError&lt;/span&gt;(...);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;book&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;changeStatus&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ReadingStatus&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Reading&lt;/span&gt;);     &lt;span style=&#34;color:#75715e&#34;&gt;// Entityのルールに従う
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;book&lt;/span&gt;);                  &lt;span style=&#34;color:#75715e&#34;&gt;// 保存
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;book&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ServiceはDomainのルールを&lt;strong&gt;自分で判定しない&lt;/strong&gt;。
&lt;code&gt;canTransition&lt;/code&gt;を自分で呼ばない。&lt;code&gt;book.changeStatus()&lt;/code&gt;に委ねるだけだ。
判定はEntity・VO・Policyが持っている。Serviceはその結果を使ってフローを組む。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
