Skip to content

Plugin Seams: Where Architecture Becomes Extractable

A seam is a contract plus the modules that implement it. It is simultaneously the shape that makes a component both extractable and pluggable — the same property viewed in two directions.

The Core Insight

If a group of modules depends on one declared contract and little else, you can lift them out whole. Whatever you lift out plugs back in against that same contract. This is the link between the "decompose" and "recompose" halves of the project.

Extractable and pluggable are the same structural property: - When extracting, you take the contract + implementers and leave behind the consumers - When recomposing, the consumers depend on the contract from outside — the plug point is exactly that dependency

Cohesion Predicts Outcome

Seams are ranked by cohesion: how self-contained the unit is.

cohesion = |unit| / (|unit| + |escapes|)

Where:

  • unit — the contract module plus every module implementing it
  • escapes — dependencies of the unit that lie outside it, i.e. what would come along in an extraction
  • cohesion ranges from 0.0 (everything escapes) to 1.0 (nothing does)

Verdicts

Cohesion Verdict Meaning
≥ 0.9 clean Extract with confidence; almost nothing extra comes along
≥ 0.6 extractable Safe to extract, but some dependencies escape; assess their cost
< 0.6 entangled Declared as a plug point, but implementations are entangled with the rest of the codebase
0 declared-unused Contract with zero implementers; not a plug point

The score predicts the outcome. Evidence from this codebase:

  • langs/base.py scores 0.88 (extractable) with exactly one escaping dependency. This is the component that was successfully extracted into a standalone pip-installable package; its one escape was precisely the module that extraction pulled in.
  • extract/rewriters/base.py scores 1.0 (clean) with zero escapes — a perfect extraction candidate.

Implementers vs Consumers

The distinction that makes the score meaningful is between:

  • Implementers: Modules that declare a type deriving from the contract
  • Consumers: Modules that merely use the contract

Only implementers belong to the extractable unit. Consumers stay behind and keep depending on the contract from outside — which is exactly what a plug point is for.

This matters. Counting consumers as members would make every popular interface look like a huge, incohesive seam. Instead, the true picture emerges: a contract with 2 implementers and 15 consumers is a real plug point. A contract with 27 dependents but 45 escaping dependencies (a real Go project) has a declared interface that isn't actually decoupled.

Type Derivation

For languages with explicit inheritance syntax (class X(Base):), this is straightforward. For Go, which has no implements keyword:

  • A dependent that declares any type of its own (struct, interface, custom type) is treated as an implementer — imprecise, and deliberately so
  • Full type checking is out of scope; this is a structural signal that works well in practice

Matched on the final segment of each base, so base.LanguageParser and a bare LanguageParser both count.

Declared ≠ Honoured

A project may have a declared plugin interface with many implementers but still be entangled. This gap is worth reporting rather than smoothing over.

Example: A real Go project has plugin/plugin.go (the contract) with 27 dependents. Only 2 are true implementers; the rest are consumers. But 45 dependencies escape — the plugins are not actually decoupled from the rest of the codebase. The verdict is entangled, not clean, correctly flagging that the plug point exists on paper but not in practice.

This is more useful than pretending all dependencies are equal. Drydock reports: - The number of true implementers - The cohesion score (evidence of whether the seam is actually decoupled) - All escaping dependencies (what extraction would drag along)

How Contract Modules Are Detected

A module is treated as a contract if it:

  1. Declares abstract types — Protocol (Python), interface (Go, Java, C#, TypeScript), trait (Rust), ABC with @abstractmethod (Python)
  2. Is depended upon by other modules

The contract surface is every type the module declares, not only the abstract ones. This is because contract modules typically ship both a protocol and a convenience base:

# base.py (the contract)
class LanguageParser(Protocol):  # Abstract contract
    def parse(self, source: str) -> AST:
        ...

class BaseParser(LanguageParser):  # Concrete base for implementers
    def parse(self, source: str) -> AST:
        ...

Implementers typically derive from BaseParser, not the protocol itself. Matching only the protocol name found zero implementers in this real codebase; matching all declared types found all seven.

Practical Limits & Honesty

Seam detection is structural, not semantic. Real limitations:

Go Has No implements Keyword

A dependent that declares any type of its own is treated as an implementer. This is imprecise — a module could declare a type that has nothing to do with the interface — but it's the best structural signal available without full type checking. In practice, it works well because intentional plugin interfaces have many implementers in that package, making them stand out.

Type Counting is Approximate for Non-Python

Python uses AST for precise symbol extraction. Other languages use regex patterns. Class and type counts are approximate, but this doesn't affect cycle detection, dependency flow, or seam cohesion — only the abstractness metric in architecture analysis.

A Project Can Have Zero Seams

Some codebases have no declared contract with multiple implementers. This is a real finding: "0 of 10 most-depended-upon hubs define an abstract contract" means there is no plugin architecture to extract along. Zero seams is not a failure; it is an architectural choice.

Finding & Extracting Seams

List All Seams

python3 -m drydock.cli extract <project> --list-seams

Returns JSON ranked by cohesion (cleanest first), including: - Contract module and types it declares - Number of implementers - All escaping dependencies - Cohesion score and verdict

List Seams as Markdown with Extraction Commands

python3 -m drydock.cli extract <project> --list-seams --markdown

Shows each seam as a section with: - Cohesion and verdict - Implementer and escape counts - A ready-to-run extraction command

Extract a Seam as a Standalone Package

python3 -m drydock.cli extract <project> --seam path/to/contract.py

Plans the extraction (no writes). To execute:

python3 -m drydock.cli extract <project> --seam path/to/contract.py -o /output

Extraction includes: - The contract module - All modules that implement it - All their internal dependencies (closure) - Consumers are excluded intentionally — they stay behind and depend on the contract from outside

The Nautical Metaphor

Like finding the seams in a ship's hull where plates join perfectly, seams in code are places where modules fit together so cleanly they can be lifted out whole. The tighter the seam (higher cohesion), the safer the extraction.

See Also