Skip to content

some updates for dev documentation - #10065

Merged
ajtmccarty merged 3 commits into
release-1.11from
ajtm-07282026-dev-docs-update
Aug 10, 2026
Merged

some updates for dev documentation#10065
ajtmccarty merged 3 commits into
release-1.11from
ajtm-07282026-dev-docs-update

Conversation

@ajtmccarty

@ajtmccarty ajtmccarty commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

trying to distill the changes from #10052 into something more repeatable

truly not sure if some of this might be better as dev/guides/backend/creating-new-components.md


Summary by cubic

Improves backend dev docs on constructor-first wiring, interfaces/protocols, configuration invariants, and testing doubles. Extends the API backpressure example to show named-event observers, a single _notify, and how protocols keep third‑party deps out of core logic.

  • Documentation
    • Wiring: no late registration; pass deps to __init__ (0+ as required list[...]); build near entry points; factories resolve settings and accept adapters; configure at construction; process‑global registries build lazily in their own module.
    • Interfaces/Protocols: choose implicit Protocol or explicit interface module (prefer explicit); never put an ABC in the consumer; name methods in the consumer’s vocabulary; adapters live in their own module; only the wiring imports both; verify no third‑party imports under logic.
    • Configuration: tightly bound numeric fields with allow_inf_nan=False; enforce cross‑field rules with @model_validator and clear, greppable messages; test the validator and shipped defaults; use enums for values that leave the process and pass enum members.
    • Testing: two doubles for collaborators — a recording double for exact call sequences/values, and a failing double to prove failure containment; keep helpers shared and prefer adapters over patching.
    • API backpressure example: events are named methods; one _notify fans out with per‑observer failure containment; collaborators arrive via the constructor; wiring in server.py chooses sinks so api/admission stays decoupled behind Protocols.

Written for commit f7fbfbf. Summary will update on new commits.

Review in cubic

@ajtmccarty
ajtmccarty requested a review from a team as a code owner July 28, 2026 18:53

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would auto-approve. Updates only development documentation with structured guidelines for component design, configuration validation, testing doubles, and error handling. No behavioral, operational, or config changes.

Re-trigger cubic

@ogenstad ogenstad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me overall, but added two comments.


Anything that comes from outside the component's own domain — loaded settings, an external service client, a telemetry sink — is resolved at that entry point, never inside the component:

- **Settings resolve in the factory, not the component.** A component takes plain values (`window_seconds: float`, `max_retries: int`), never a `Settings` object and never a module-global read. The factory is then the only place that knows a value came from configuration, which is also what makes the component directly testable with hand-picked values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any examples of this? In situations where there's a high number of settings i thing a Settings object can be cleaner. An example would be the Config object for the SDK.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Config is a special case b/c it is a settings object. its only responsibility should be tracking many settings. any components that actually do things should have settings injected when they are constructed.

Comment thread dev/guidelines/backend/python.md Outdated
for observer in self._observers:
try:
observer.on_depth_changed(queued=queued, running=running)
except Exception:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should include except Exception as a good example. (https://docs.astral.sh/ruff/rules/blind-except/)

Instead we should have a specific exception even if it's made up so that the LLM doesn't pick up on this part and start to introduce additional broad exceptions. We also have #10002.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the case of an "observer" object (or any other "best-effort side-effect" components that should not block the primary logic during failure) we want to catch the bare Exception. we could move the bare Exception catch into on_depth_changed in this example and re-raise a different error class, but the end result would be the same

@ajtmccarty
ajtmccarty changed the base branch from develop to release-1.11 July 29, 2026 18:19

- **Use a `Protocol`, not an ABC, and declare it beside the component that depends on it.** Structural typing is what makes this work: the adapter satisfies the protocol by having matching signatures, so it never imports the protocol's module and the dependency does not run backwards. An ABC does not have that property — the adapter must subclass it, and therefore import whatever module it lives in. If you genuinely need an ABC, it goes in a module of its own that both sides import, never in the consumer's module.
- **Name the methods in the depending component's vocabulary**, not the adapter's — `on_depth_changed(*, queued: int, running: int)`, not `set_gauges(...)` — and pass the values as arguments rather than handing over `self`, so the adapter can never read back into the component. The component then depends on a shape it defined, has no idea what is on the other side, and stays free to change its internals.
- **Put the concrete adapter in a separate, purpose-named module** that is the only place importing the library, and **let only the wiring layer import both** (see "Build components near the application entry point").

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything worth saying about explicitly declaring the protocol in the class definition?

I mean this:

class MyClass(MyProtocol):
    # Protocol implemented

And not:

class MyClass():
    # Protocol implemented implicitly

The upside of the first option is the direct link between the implementation and the protocol it satisfies. The downside is that we would need to import the protocol. Another option would be to make sure these protocols live in a package that is safe to import from anywhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm always ready to say more about dependency injection. added some more detail to describe either using a Protocol without importing it or using a Protocol/ABC interface defined in a separate module

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would auto-approve. The PR adds only documentation (backend design, testing, configuration guidelines). No behavior, configuration, API, or operational change exists. The diff is bounded and clearly beneficial.

Re-trigger cubic

@polmichel polmichel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM besides the merge conflicts

@ajtmccarty
ajtmccarty force-pushed the ajtm-07282026-dev-docs-update branch from 380efeb to f7fbfbf Compare August 10, 2026 21:06
@ajtmccarty
ajtmccarty merged commit b1cd264 into release-1.11 Aug 10, 2026
43 checks passed
@ajtmccarty
ajtmccarty deleted the ajtm-07282026-dev-docs-update branch August 10, 2026 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants