Skip to content
C.W.K.
Stream
Lesson 02 of 05 · published

The Typed Pointer

~12 min · contracts, generalization, indirection, api-design

Level 0Cold Workshop
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Two Fields, and Only One of Them Is Doing Work Today

What crosses the seam is almost nothing: a pipeline name, and a pointer shaped as a kind plus an identifier. That is the entire payload. The pointer says what sort of thing this is and which one, and it deliberately does not carry the thing itself.

Only one pipeline exists, and it only ever consumes one kind of material. So the kind field does nothing right now — an identifier alone would work perfectly. It is there anyway, and the reasoning behind that is the actual content of this lesson, because "we added a field we do not need yet" is usually a mistake and here it isn't.

Generalize the Contract, Not the Implementation

There are two things you could pre-build for a future you cannot see. You could generalize the implementation — build a pipeline framework, a plugin registry, an abstract stage interface, so that adding a second pipeline is easy. That is the expensive kind of guessing, and the workshop's own doctrine forbids it: build concretely for the one case that exists, because a framework designed against a hypothetical second case is fitted to a guess.

Or you could generalize the contract — the shape of what crosses the boundary. That costs one field. And unlike the implementation, the contract is the expensive thing to change later, because changing it means coordinating both sides at once. Pre-generalizing the cheap-to-build, expensive-to-change half while staying concrete in the expensive-to-build, cheap-to-change half is close to a general rule.

What the Type Field Buys

With a kind on the pointer, a future pipeline can point at a completely different sort of source material — a journal entry, an essay, a live application — and the seam does not change at all. The calling side keeps emitting a pointer. The receiving side dispatches on kind. Nobody renegotiates anything.

There is a second, quieter benefit: the type makes the payload self-describing in the record. Six months later, reading an old request, you can tell what it referred to without knowing which pipeline was current at the time. An untyped identifier in a log is a mystery whose meaning lived in code that has since changed.

Indirection is what lets the material outlive the tool. The pointer does not carry the conversation — it carries its address. So the material stays in the system that already stores it properly, gets fetched fresh at run time, and never exists as a stale copy inside a request that someone might replay next year.

Code

The whole payload that crosses the seam·json
{
  "pipeline": "pippalog-episode",
  "pointer": { "kind": "conversation", "id": "<opaque-identifier>" },
  "why": "the correction beat in the middle is the whole episode"
}

// Note what is NOT here: the conversation text, any rendering
// instruction, any duration, any format. The receiving side
// dereferences the pointer itself, at run time, from the system
// that owns that material.
Dispatch on kind — the only place the type is read·python
def dereference(pointer: dict) -> Material:
    """One place knows how to turn a pointer into material.

    Adding a pipeline adds a branch here. It does NOT change the
    contract, the emitter, or anything on the calling side.
    """
    kind = pointer["kind"]
    if kind == "conversation":
        return fetch_conversation(pointer["id"])
    raise ValueError(f"no fetcher registered for kind: {kind!r}")


# Deliberately NOT built: a plugin registry, an abstract Fetcher
# base class, a config-driven dispatch table. One kind exists.
# A raise on the unknown case is the honest amount of structure.

External links

Exercise

Look at a message, event, or job payload in your system that carries a bare identifier. Ask what a reader six months from now would need in order to interpret it without reading the consuming code. Then add the smallest type discriminator that would answer that, and make the consumer raise on unknown values rather than defaulting. Notice how much of the payload's meaning was previously stored only in the consumer.
Hint
The strongest tell is a queue where the consumer infers the type from which queue it arrived on. That works exactly until there are two producers or somebody replays a message into the wrong place — and then the payload cannot defend itself, because it never carried what it was.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.