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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user