docs(standards): align layering docs with the port seam; split runtime vs wiring diagrams
This commit is contained in:
@@ -662,12 +662,14 @@
|
||||
<span class="normal"><a href="#__codelineno-0-3">3</a></span>
|
||||
<span class="normal"><a href="#__codelineno-0-4">4</a></span>
|
||||
<span class="normal"><a href="#__codelineno-0-5">5</a></span>
|
||||
<span class="normal"><a href="#__codelineno-0-6">6</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a>interfaces/* -> domain/* (services, ports, models)
|
||||
<span class="normal"><a href="#__codelineno-0-6">6</a></span>
|
||||
<span class="normal"><a href="#__codelineno-0-7">7</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a>interfaces/* -> domain/* (services, ports, models)
|
||||
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a>interfaces/* -> infra/adapters/* (only read-only value objects)
|
||||
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3"></a>domain/* -> domain/* (same layer)
|
||||
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4"></a>domain/* -> pipelines/* (call a pipeline via a port)
|
||||
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5"></a>infra/adapters/* -> domain/ports/* (implement the interface)
|
||||
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6"></a>main.py/init.py -> everything (composition root only)
|
||||
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5"></a>domain/*/ports/* -> infra/* (bind adapters + libraries at the seam)
|
||||
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6"></a>infra/adapters/* -> domain/ports/* (implement the interface)
|
||||
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7"></a>main.py/init.py -> everything (composition root only)
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<h2 id="forbidden-imports">Forbidden imports</h2>
|
||||
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
|
||||
@@ -680,27 +682,32 @@
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<h2 id="why">Why</h2>
|
||||
<ul>
|
||||
<li>A pure domain is testable without a server, a database, or an event loop.</li>
|
||||
<li>The business logic is testable without a server or an event loop.</li>
|
||||
<li>Adapters can be swapped (in-memory fake, real database) without touching
|
||||
business logic.</li>
|
||||
<li>The HTTP edge stays thin and replaceable.</li>
|
||||
</ul>
|
||||
<h2 id="the-portadapter-contract">The port/adapter contract</h2>
|
||||
<p>A <strong>port</strong> is an abstract class or protocol declared in <code>domain/<x>/ports/</code>.
|
||||
An <strong>adapter</strong> implements it in <code>infra/adapters/</code>. The composition root binds
|
||||
the two.</p>
|
||||
<p>The <code>ports/</code> package is the <strong>seam</strong> where the domain meets infrastructure.
|
||||
Each <code>domain/<resource>/ports/</code> module declares the resource's models/document
|
||||
shape and exposes <strong>factory functions</strong> that bind a concrete adapter — so a port
|
||||
module <em>is</em> allowed to import from <code>infra/</code> and from the persistence libraries.
|
||||
Everything outside <code>ports/</code> in <code>domain/</code> stays framework- and driver-free.</p>
|
||||
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-2-1">1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-2">2</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-3">3</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-4">4</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a>domain/<resource>/ports/db.py -> repository port + document model
|
||||
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a>domain/<resource>/ports/service.py -> service + factory
|
||||
<span class="normal"><a href="#__codelineno-2-4">4</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a>domain/<resource>/ports/db.py -> document model + create_<resource>_repo()
|
||||
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a>domain/<resource>/ports/service.py -> service + get_<resource>_service()
|
||||
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a>infra/db/repos/<vendor>.py -> repository adapter factory
|
||||
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a>infra/adapters/service.py -> generic CRUD service base
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>The composition root calls the port factories; nothing else constructs an
|
||||
adapter.</p>
|
||||
<h2 id="enforcement">Enforcement</h2>
|
||||
<ul>
|
||||
<li>Keep every domain module importable with only the standard library and
|
||||
Pydantic. If a domain import pulls in <code>fastapi</code>, the layering is broken.</li>
|
||||
<li><code>domain/</code> must never import the web framework (<code>fastapi</code> / <code>starlette</code>) or
|
||||
<code>interfaces/</code>. Only the <code>ports/</code> seam, <code>infra/</code>, and the composition root
|
||||
touch drivers.</li>
|
||||
<li>Reviewers (and CI) reject <code>import fastapi</code> inside <code>domain/</code> or <code>infra/</code>.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -651,7 +651,7 @@ registry</strong> and exposes it to the HTTP layer and to scripts.</p>
|
||||
<li><code>progress_bus</code> — progress/event bus.</li>
|
||||
<li><code><vendor>_<kind></code> — fetchers, clients, stores.</li>
|
||||
</ul>
|
||||
<p>The API stores the dict on <code>app.state.resources</code> during lifespan startup.
|
||||
<p>The API stores the dict on <code>app.state.db_resources</code> during lifespan startup.
|
||||
Handlers read from it; nothing imports concrete adapters.</p>
|
||||
<h2 id="shape">Shape</h2>
|
||||
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-0-1"> 1</a></span>
|
||||
@@ -747,7 +747,7 @@ typing without importing concrete classes:</p>
|
||||
<ul>
|
||||
<li><code>init()</code> is <strong>idempotent</strong>.</li>
|
||||
<li>The composition root is the only place that imports concrete adapters.</li>
|
||||
<li>Handlers never call <code>init()</code>; they read <code>request.app.state.resources</code>.</li>
|
||||
<li>Handlers never call <code>init()</code>; they read <code>request.app.state.db_resources</code>.</li>
|
||||
<li>Scripts call <code>init()</code> / <code>shutdown()</code> around their work.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -711,7 +711,7 @@ Handlers are bound to it by name; there is no decorator router.</p>
|
||||
<span class="normal"><a href="#__codelineno-1-4">4</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-5">5</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a><span class="nd">@asynccontextmanager</span>
|
||||
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">lifespan</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3"></a> <span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">resources</span> <span class="o">=</span> <span class="k">await</span> <span class="n">init</span><span class="p">()</span>
|
||||
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3"></a> <span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">db_resources</span> <span class="o">=</span> <span class="k">await</span> <span class="n">init</span><span class="p">()</span>
|
||||
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4"></a> <span class="k">yield</span>
|
||||
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5"></a> <span class="k">await</span> <span class="n">shutdown</span><span class="p">()</span>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
@@ -729,7 +729,7 @@ can resolve <code>operationId</code> across the whole app:</p>
|
||||
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-3-1">1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-2">2</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-3">3</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_item</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">Request</span><span class="p">,</span> <span class="n">body</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
|
||||
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2"></a> <span class="n">svc</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">resources</span><span class="p">[</span><span class="s2">"item_svc"</span><span class="p">]</span>
|
||||
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2"></a> <span class="n">svc</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">db_resources</span><span class="p">[</span><span class="s2">"item_svc"</span><span class="p">]</span>
|
||||
</span><span id="__span-3-3"><a id="__codelineno-3-3" name="__codelineno-3-3"></a> <span class="k">return</span> <span class="k">await</span> <span class="n">svc</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">Item</span><span class="o">.</span><span class="n">model_validate</span><span class="p">(</span><span class="n">body</span><span class="p">))</span>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<h2 id="conventions">Conventions</h2>
|
||||
|
||||
@@ -729,7 +729,7 @@ swapped for a durable broker. Emit from the orchestrator at each stage:</p>
|
||||
<span class="normal"><a href="#__codelineno-4-13">13</a></span>
|
||||
<span class="normal"><a href="#__codelineno-4-14">14</a></span>
|
||||
<span class="normal"><a href="#__codelineno-4-15">15</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">stream_job_events</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||||
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2"></a> <span class="n">bus</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">resources</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"progress_bus"</span><span class="p">)</span>
|
||||
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2"></a> <span class="n">bus</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">db_resources</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"progress_bus"</span><span class="p">)</span>
|
||||
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3"></a> <span class="k">if</span> <span class="n">bus</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||||
</span><span id="__span-4-4"><a id="__codelineno-4-4" name="__codelineno-4-4"></a> <span class="k">return</span> <span class="p">{</span><span class="s2">"events"</span><span class="p">:</span> <span class="p">[]}</span>
|
||||
</span><span id="__span-4-5"><a id="__codelineno-4-5" name="__codelineno-4-5"></a>
|
||||
|
||||
@@ -734,7 +734,7 @@ injects it into the pipeline so that stage can skip re-raising:</p>
|
||||
<span class="normal"><a href="#__codelineno-4-12">12</a></span>
|
||||
<span class="normal"><a href="#__codelineno-4-13">13</a></span>
|
||||
<span class="normal"><a href="#__codelineno-4-14">14</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">resolve_question</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="nb">id</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
|
||||
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2"></a> <span class="n">svc</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">resources</span><span class="p">[</span><span class="s2">"question_svc"</span><span class="p">]</span>
|
||||
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2"></a> <span class="n">svc</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">db_resources</span><span class="p">[</span><span class="s2">"question_svc"</span><span class="p">]</span>
|
||||
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3"></a> <span class="n">q</span> <span class="o">=</span> <span class="k">await</span> <span class="n">svc</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||||
</span><span id="__span-4-4"><a id="__codelineno-4-4" name="__codelineno-4-4"></a> <span class="k">await</span> <span class="n">svc</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="nb">id</span><span class="p">,</span> <span class="p">{</span><span class="s2">"chosen"</span><span class="p">:</span> <span class="n">body</span><span class="p">[</span><span class="s2">"chosen"</span><span class="p">],</span> <span class="s2">"status"</span><span class="p">:</span> <span class="s2">"resolved"</span><span class="p">,</span>
|
||||
</span><span id="__span-4-5"><a id="__codelineno-4-5" name="__codelineno-4-5"></a> <span class="s2">"resolved_at"</span><span class="p">:</span> <span class="n">now</span><span class="p">()})</span>
|
||||
@@ -742,7 +742,7 @@ injects it into the pipeline so that stage can skip re-raising:</p>
|
||||
</span><span id="__span-4-7"><a id="__codelineno-4-7" name="__codelineno-4-7"></a> <span class="n">pending</span> <span class="o">=</span> <span class="k">await</span> <span class="n">svc</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="nb">filter</span><span class="o">=</span><span class="p">{</span><span class="s2">"job_id"</span><span class="p">:</span> <span class="n">q</span><span class="o">.</span><span class="n">job_id</span><span class="p">,</span> <span class="s2">"status"</span><span class="p">:</span> <span class="s2">"pending"</span><span class="p">},</span>
|
||||
</span><span id="__span-4-8"><a id="__codelineno-4-8" name="__codelineno-4-8"></a> <span class="n">limit</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
|
||||
</span><span id="__span-4-9"><a id="__codelineno-4-9" name="__codelineno-4-9"></a> <span class="k">if</span> <span class="ow">not</span> <span class="n">pending</span><span class="p">:</span>
|
||||
</span><span id="__span-4-10"><a id="__codelineno-4-10" name="__codelineno-4-10"></a> <span class="n">jobs</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">resources</span><span class="p">[</span><span class="s2">"job_svc"</span><span class="p">]</span>
|
||||
</span><span id="__span-4-10"><a id="__codelineno-4-10" name="__codelineno-4-10"></a> <span class="n">jobs</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">db_resources</span><span class="p">[</span><span class="s2">"job_svc"</span><span class="p">]</span>
|
||||
</span><span id="__span-4-11"><a id="__codelineno-4-11" name="__codelineno-4-11"></a> <span class="n">job</span> <span class="o">=</span> <span class="k">await</span> <span class="n">jobs</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">q</span><span class="o">.</span><span class="n">job_id</span><span class="p">)</span>
|
||||
</span><span id="__span-4-12"><a id="__codelineno-4-12" name="__codelineno-4-12"></a> <span class="k">if</span> <span class="n">job</span> <span class="ow">and</span> <span class="n">job</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="s2">"paused"</span><span class="p">:</span>
|
||||
</span><span id="__span-4-13"><a id="__codelineno-4-13" name="__codelineno-4-13"></a> <span class="k">await</span> <span class="n">jobs</span><span class="o">.</span><span class="n">update_status</span><span class="p">(</span><span class="n">q</span><span class="o">.</span><span class="n">job_id</span><span class="p">,</span> <span class="s2">"pending"</span><span class="p">)</span>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user