ADR-065: Reduce the public service surface (ADR-028 follow-up)
- Status
-
Accepted
- Date
-
2026-07-15
- Supersedes
-
the count and Category 3 / tail rationale of ADR-028
- Authors
-
Netresearch DTT GmbH
Context
ADR-028 froze the public: true overrides in
Configuration/ at 45 and concluded "no reduction in
count": every entry was said to be load-bearing because removing it
would break either downstream consumers or the extension's own
functional tests. The functional-test half of that argument rested on
one premise:
"TYPO3
Functionaluses the Symfony container'sTest Case:: get () ->getlookup, which only resolves public services."()
That premise is wrong. The testing framework ships a fixture extension
typo3/ →
Resources/ whose
Private runs at TYPE_ and
registers every private service (and private alias) into a public
service locator. Functional falls back to that
locator:
public function get(string $id): mixed
{
if ($this->getContainer()->has($id)) {
return $this->getContainer()->get($id);
}
return $this->getPrivateContainer()->get($id); // private services
}
So $this->get resolves a private service in
functional and backend-E2E tests without any override. The repository in
fact already relied on this: Wizard was private
(public: false interface alias, no public concrete) yet resolved by
class name in green functional tests. ADR-028 Category 3 ("repositories
must be public for Functional") and the
class-name-resolution tail were therefore public for a reason that never
held.
Decision
Keep public: true only where it is genuinely required, and
privatise everything that was public solely for test resolution. A
service needs public: true if, and only if, it is:
- part of the documented downstream LLM-API contract that consuming
extensions resolve by class name or interface via
$container->get/ a DI type hint; or()
- a supporting-service interface alias consumers wire against (the concrete class is private); or
- a concrete-only documented surface with no interface (only
Prompt, ADR-031); orSnippet Composer - a specialized standalone consumer API (speech / image in isolation); or
- resolved outside DI via
General, which only reuses the container-built, dependency-injected instance for public services.Utility:: make Instance ()
Everything else — the repositories, the setup-wizard collaborators
Model / Configuration, the read-only
Usage, and the concrete supporting services behind a
public interface alias — is now private (autoregistered by the
Netresearch\ namespace block). Functional and backend-E2E
tests resolve them unchanged through the private container. No test code
and no runtime behaviour changed; only container visibility did.
The reduced public surface (27)
A. Documented downstream LLM-API contract — 7 concrete + 7 interface aliases = 14:
Service\(+Llm Service Manager Llm)Service Manager Interface Provider\(+Provider Adapter Registry Provider)Adapter Registry Interface Service\(+ Interface)Feature\ Completion Service Service\(+ Interface)Feature\ Vision Service Service\(+ Interface)Feature\ Embedding Service Service\(+ Interface)Feature\ Translation Service Service\(+ Interface, ADR-051)Feature\ Tool Calling Service
Added to Category A after this ADR (count superseded by ADR-071: Public keyword-search facade over the retrieval cascade):
Service\(alias; the concreteRetrieval\ Keyword Search Interface Keywordstays private, ADR-071)Search Service nr_(named index-backed-only variant, ADR-071)llm. keyword_ search. index_ backed
B. Supporting-service interface aliases (concrete classes now private) — 6:
Service\Cache Manager Interface Service\Usage Tracker Service Interface Service\Prompt Template Service Interface Service\Llm Configuration Service Interface Service\Budget Service Interface Specialized\Translation\ Translator Registry Interface
C. Concrete-only documented surface — 1:
Service\(ADR-031, no interface)Prompt\ Prompt Snippet Composer
D. Specialized standalone consumer API — 4:
Specialized\Speech\ Whisper Transcription Service Specialized\Speech\ Text To Speech Service Specialized\Image\ Dall EImage Service Specialized\Image\ Fal Image Service
E. Resolved outside DI via makeInstance() — 2:
Service\— TCATool\ Tool Registry itemsinProc Func Form\(ADR-042).Tca\ Tool Group Items Service\— the DataHandler hookSetup Wizard\ Provider Detector Hook\.Provider Endpoint Normalization Hook
Total: 14 + 6 + 1 + 4 + 2 = 27 (down from 45).
What became private
Removed from the public set (autoregistered private; injected via DI, resolved in tests via the private container):
- Repositories (8):
Llm,Configuration Repository Provider,Repository Model,Repository Task,Repository Prompt,Snippet Repository User,Budget Repository Skill,Repository Skill.Source Repository - Setup-wizard collaborators:
Model(concrete;Discovery Modelalias kept for autowiring, now private) andDiscovery Interface Configuration.Generator - Supporting concretes behind a public interface alias:
Cache,Manager Usage,Tracker Service Prompt,Template Service Llm,Configuration Service Budget,Service Specialized\.Translation\ Translator Registry Service\(read-only Analytics reporting service; its interface alias was already private).Usage Analytics Service
Consequences
- The audited public surface drops from 45 to 27. The
Publiccount constant and this ADR are the audit trail; ADR-028's "no reduction" conclusion is superseded.Services Policy Test - Consuming extensions that resolved a concrete supporting service by
class name (e.g.
$container->get) must switch to the interface ((Cache Manager:: class) Cache). This is a breaking change for those callers, acceptable pre-1.0 and consistent with the interface being the documented contract.Manager Interface - Tests are unchanged: the private container keeps
$this->geton a private service working. Any future test that needs a private service by class name works out of the box for the same reason.() - Adding a new
public: truestill requires the three-part change from ADR-028 (service definition, ADR entry,EXPECTED_bump) — but the bar is now "does a production, non-DI caller or a documented downstream consumer need it?", not "does a test resolve it?"PUBLIC_ TRUE_ COUNT
Relation to ADR-028
ADR-028 stays as the record of the original policy and the
public: true enforcement test. This ADR supersedes its count and
its Category 3 / tail rationale. The enforcement mechanism
(Tests/) is retained;
only the expected total changed (45 → 27).