docs: refresh explainers + adding-a-feature with full binder signature
di-explainer and data-flow-explainer narratives still showed the pre-ADR-014 1-arg bindProductionBlog(config) form. Updated both explainers to (config, tracer, logger, bus, queue), and adjusted the narrative arc so the production-swap step mentions the resolveEventsAndJobs* preamble. adding-a-feature's Step 16 sketch was the same 1-arg shape; replaced with the canonical 5-arg signature and a short note that the bus/queue params are accept-and-forward until gen event consume / gen job generators inject usage at the anchors. Pointer to a real feature's bind-production.ts for the complete reference. Final sweep is clean — no stale (container, config) or 1-arg bindProduction signatures remain in docs/.
This commit is contained in:
@@ -1664,7 +1664,7 @@ footer .colophon {
|
||||
</div>
|
||||
<div class="di-card">
|
||||
<h4>Two binding modes, one symbol.</h4>
|
||||
<p>The <code>BlogModule</code> binds <code>IArticlesRepository</code> to <code>MockArticlesRepository</code> by default — useful at dev/test time. At app boot, <code>bindProductionBlog(config)</code> unbinds the symbol and rebinds it to <code>new ArticlesRepository(config)</code>. Use cases and controllers don't notice — they get whatever the symbol currently resolves to.</p>
|
||||
<p>The <code>BlogModule</code> binds <code>IArticlesRepository</code> to <code>MockArticlesRepository</code> by default — useful at dev/test time. At app boot, <code>bindProductionBlog(config, tracer, logger, bus, queue)</code> unbinds the symbol and rebinds it to <code>new ArticlesRepository(config, tracer, logger)</code>. Use cases and controllers don't notice — they get whatever the symbol currently resolves to.</p>
|
||||
<p>This is also why the boundary stays clean: features don't import <code>core-cms</code>; the app passes the Payload config in.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1773,7 +1773,7 @@ footer .colophon {
|
||||
|
||||
<p style="margin-top: 32px;"><strong>The mock is reached from two directions.</strong> Both are legitimate, neither is "the test version":</p>
|
||||
<ol class="role-jobs">
|
||||
<li><strong>By the DI container at runtime.</strong> <code>BlogModule</code> binds <code>IArticlesRepository</code> to <code>MockArticlesRepository</code> at module-load time. Anything resolving that symbol — use cases, controllers, tRPC procedures, the dev server — gets the mock until <code>bindProductionBlog(config)</code> swaps it for the real Payload-backed one. See §03.</li>
|
||||
<li><strong>By the DI container at runtime.</strong> <code>BlogModule</code> binds <code>IArticlesRepository</code> to <code>MockArticlesRepository</code> at module-load time. Anything resolving that symbol — use cases, controllers, tRPC procedures, the dev server — gets the mock until <code>bindProductionBlog(config, tracer, logger, bus, queue)</code> swaps it for the real Payload-backed one. See §03.</li>
|
||||
<li><strong>By tests, via direct construction.</strong> Unit tests skip the container entirely. They construct the mock with <code>new MockArticlesRepository()</code> and pass it directly into the use-case factory function. Same class, different consumer — just a closure with a fake repo.</li>
|
||||
</ol>
|
||||
|
||||
@@ -1786,7 +1786,7 @@ footer .colophon {
|
||||
<details class="cf-detail">
|
||||
<summary>show: the mock as DI binding (in module.ts)</summary>
|
||||
<div class="detail-body">
|
||||
<p>This is from <code>packages/blog/src/di/module.ts</code> — the very first binding in the module is the mock. Everything downstream (use cases, controllers) resolves through this default. <code>bindProductionBlog(config)</code> later replaces only this one line at app boot — use case + controller bindings stay put.</p>
|
||||
<p>This is from <code>packages/blog/src/di/module.ts</code> — the very first binding in the module is the mock. Everything downstream (use cases, controllers) resolves through this default. <code>bindProductionBlog(config, tracer, logger, bus, queue)</code> later replaces only this one line at app boot — use case + controller bindings stay put.</p>
|
||||
<pre class="code" data-lang="typescript // packages/blog/src/di/module.ts"><span class="k">export const</span> <span class="n">BlogModule</span> = <span class="k">new</span> <span class="t">ContainerModule</span>((<span class="n">bind</span>) => {
|
||||
<span class="c">// 1) Mock is the DEFAULT binding for the repo symbol.</span>
|
||||
<span class="c">// Dev server, unit tests, storybook all resolve to this.</span>
|
||||
|
||||
@@ -756,7 +756,7 @@ footer .colophon {
|
||||
<div class="cast-tag">file 04 · prod swap</div>
|
||||
<h3>bind-production.ts</h3>
|
||||
<p class="role">Replaces the mock repository binding with a real Payload-backed one at app boot.</p>
|
||||
<p>Exports <code>bindProductionBlog(config: SanitizedConfig)</code>. Function body: <code>blogContainer.unbind(symbol)</code> if already bound, then <code>.bind(symbol).toConstantValue(new ArticlesRepository(config))</code>. Use cases and controllers stay bound to their factory bindings — they will fetch the new repo through the container automatically because their factories call <code>ctx.container.get(...)</code> at every resolution.</p>
|
||||
<p>Exports <code>bindProductionBlog(config, tracer, logger, bus, queue)</code>. Function body: <code>blogContainer.unbind(symbol)</code> if already bound, then <code>.bind(symbol).toConstantValue(new ArticlesRepository(config, tracer, logger))</code>. Use cases and controllers are wrapped via <code>withSpan(withCapture(factory(deps)))</code> at bind time so they inherit instrumentation without changing their factory bodies. The <code>bus</code> and <code>queue</code> params come from the app's <code>resolveEventsAndJobs*</code> step (ADR-015) and feed the <code>// <gen:event-handlers></code> / <code>// <gen:jobs></code> injection sites.</p>
|
||||
<div class="runs-when"><strong>When it runs:</strong> called from app boot (<code>apps/web-next/src/server/bind-production.ts</code>) when <code>USE_DEV_SEED ≠ "true"</code> AND Payload config is resolvable.</div>
|
||||
</div>
|
||||
|
||||
@@ -1302,8 +1302,8 @@ const MODES = {
|
||||
scenarioTag: 'when this happens',
|
||||
title: 'Production binder ran at app boot',
|
||||
narrative: [
|
||||
"App's bindAll() did not see USE_DEV_SEED=true, so it dispatched to bindAllProduction(config). That awaited the resolved Payload config and called bindProductionBlog(config).",
|
||||
"The binder unbound IArticlesRepository and rebound it with .toConstantValue(new ArticlesRepository(config)) — the real Payload-backed implementation that will hit Postgres on every method call.",
|
||||
"App's bindAll() did not see USE_DEV_SEED=true, so it dispatched to bindAllProduction(). That resolved instrumentation (Rule 0), resolved a Payload-backed bus + queue (ADR-015), awaited the Payload config, and called bindProductionBlog(config, tracer, logger, bus, queue).",
|
||||
"The binder unbound IArticlesRepository and rebound it with .toConstantValue(new ArticlesRepository(config, tracer, logger)) — the real Payload-backed implementation that will hit Postgres on every method call.",
|
||||
"Use cases and controllers still resolve through their .toDynamicValue closures, but the closures now fetch the real repo. Callers cannot tell the difference — same interface, different storage."
|
||||
],
|
||||
bindings: [
|
||||
|
||||
@@ -926,20 +926,44 @@ describe("CommentsRepository", () => {
|
||||
|
||||
### Step 16: `bind-production.ts`
|
||||
|
||||
The full canonical shape includes instrumentation (ADR-014) and the
|
||||
event-bus + job-queue parameters (ADR-015) — see any feature's
|
||||
`bind-production.ts` for a complete reference. Minimal sketch:
|
||||
|
||||
```typescript
|
||||
// src/di/bind-production.ts
|
||||
import type { SanitizedConfig } from "payload";
|
||||
import {
|
||||
withSpan,
|
||||
withCapture,
|
||||
type ITracer,
|
||||
type ILogger,
|
||||
} from "@repo/core-shared/instrumentation";
|
||||
import type { IEventBus } from "@repo/core-events";
|
||||
import type { IJobQueue } from "@repo/core-shared/jobs";
|
||||
import { commentsContainer } from "./container";
|
||||
import { COMMENTS_SYMBOLS } from "./symbols";
|
||||
import { CommentsRepository } from "../infrastructure/repositories/comments.repository";
|
||||
|
||||
export function bindProductionComments(config: SanitizedConfig): void {
|
||||
export function bindProductionComments(
|
||||
config: SanitizedConfig,
|
||||
tracer: ITracer,
|
||||
logger: ILogger,
|
||||
bus: IEventBus,
|
||||
queue: IJobQueue,
|
||||
): void {
|
||||
if (commentsContainer.isBound(COMMENTS_SYMBOLS.ICommentsRepository)) {
|
||||
commentsContainer.unbind(COMMENTS_SYMBOLS.ICommentsRepository);
|
||||
}
|
||||
const repo = new CommentsRepository(config, tracer, logger);
|
||||
commentsContainer
|
||||
.bind(COMMENTS_SYMBOLS.ICommentsRepository)
|
||||
.toConstantValue(new CommentsRepository(config));
|
||||
.toConstantValue(repo);
|
||||
// Use cases + controllers are wrapped with withSpan(withCapture(...))
|
||||
// at bind time. bus + queue are accept-and-forward until you add an
|
||||
// event handler or job — at which point the gen event consume / gen job
|
||||
// generators inject usage at the // <gen:event-handlers> / // <gen:jobs>
|
||||
// anchors at the end of the function body.
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user