Skip to content

M12, M16 — build only the handler that runs, and stop making everything public - #280

Open
gplanchat wants to merge 1 commit into
mainfrom
fix/conteneur-hygiene
Open

M12, M16 — build only the handler that runs, and stop making everything public#280
gplanchat wants to merge 1 commit into
mainfrom
fix/conteneur-hygiene

Conversation

@gplanchat

Copy link
Copy Markdown
Owner

Two container-hygiene findings from the audit. Both are in DurableExtension and ActivityHandlerPass.

M16 — running one activity built every handler

ActivityHandlerPass registered handlers on the executor as callables:

$executor->addMethodCall('register', [$activityName, [new Reference($invokerId), '__invoke']]);

To build that argument, the container has to resolve every Reference. So it instantiated every activity handler in the application — and their connections, HTTP clients and whatever else they inject — in order to call one. It does not show in development, where handlers are light. It shows on a worker that processes one activity per message, with twenty contracts declared.

A service locator replaces it, via ServiceLocatorTagPass::register() — the upstream mechanism for "many candidates, one called", the same one MessengerPass uses for message handlers.

RegistryActivityExecutor takes the locator as an optional argument and keeps register(). Hosts with no service container to offer — Laravel, Magento, the test helpers — keep registering directly. A direct registration wins over the locator, so a test can replace one handler without rebuilding the container.

The test counts real instantiations: getting the executor builds zero handlers, and executing premier.faire builds exactly one.

M12 — eleven internal services become private

Concrete implementations behind an alias — the DBAL journal, the Temporal catalogue — and the projection decorators have no reason to be container entry points. You reach them through their interface, which stays public and autowirable and returns the same instance.

A public service escapes inlining and RemoveUnusedDefinitionsPass, and becomes a compatibility promise nobody meant to make.

Now private Ask for this instead
durable.event_store.dbal, .temporal, .inner, .*.projecting EventStoreInterface
durable.workflow_metadata_store.inner, .*.projecting WorkflowMetadataStore
durable.run_catalog.dbal, .in_memory, .temporal WorkflowRunCatalogInterface

What this deliberately leaves alone

The audit counts 48 public services. This changes 11. The other 37 stay, and the reason is not laziness:

  • durable.temporal.activity_worker, durable.temporal.nexus_worker and durable.child_workflow_parent_link_store are fetched by id from a real container in this repository's and the sample app's tests.
  • DurableDataCollector, EventStoreInterface, WorkflowMetadataStore and WorkflowResumeDispatcher are what the shipped DurableBundleTestTrait resolves — that is the documented public surface.
  • The engine classes would need a survey of the three hosts (Sylius plugin, Magento module, Laravel package), which I cannot run here.

Narrowing what has been verified beats privatising on a hunch: a wrong call here breaks a consumer at runtime.

Breaking change

Behavioural, for an application that fetches one of those eleven ids with $container->get(). UPGRADE.md carries the mapping table and says why Rector cannot help — rewriting a get() into an injection needs to know where the object is used, which no rule can guess.

Verification

main this branch
unit suite 1073 tests, 4 errors 1087 tests, 4 errors
PHPStan 2 errors 2 errors
integration suite passes (78 skipped, gated on a Temporal server)

The four errors are pre-existing and identical (illuminate/cache is not installed on this machine).

Beyond the unit suite: the integration test that boots a real kernel passes, including its $container->get('durable.child_workflow_parent_link_store') — which is the check that matters for a change to service visibility.

…ut rendre public

**Un gestionnaire d'activité appelé en construisait vingt.** `ActivityHandlerPass` posait
sur l'exécuteur des callables `[new Reference($invoker), '__invoke']`. Pour bâtir cet
argument, le conteneur doit résoudre chaque référence : il instanciait donc tous les
gestionnaires de l'application — et leurs connexions, clients HTTP et autres dépendances —
pour en appeler un seul. Sur un worker qui traite une activité par message, c'est payé à
chaque message.

Un localisateur de services le remplace, par `ServiceLocatorTagPass::register()` : le
mécanisme amont pour « beaucoup de candidats, un seul appelé », celui qu'emploie
MessengerPass pour les gestionnaires de messages. `RegistryActivityExecutor` reçoit ce
localisateur en argument facultatif et garde `register()` : les hôtes qui n'ont pas de
conteneur à offrir — Laravel, Magento, les tests — continuent d'enregistrer directement,
et un enregistrement direct l'emporte sur le localisateur, ce qui laisse un test remplacer
un gestionnaire sans reconstruire le conteneur.

**Onze services internes passent en privé.** Les implémentations concrètes derrière un
alias — journal DBAL, catalogue Temporal — et les décorateurs de projection n'ont aucune
raison d'être tirés du conteneur : on les atteint par leur interface, qui reste publique
et autowirable et rend la même instance. Un service public échappe à l'inlining et à
`RemoveUnusedDefinitionsPass`, et devient une promesse de compatibilité que personne n'a
voulu prendre.

Ce lot ne touche pas les 37 autres publics. Les workers Temporal, le magasin de liens
parent/enfant et le collecteur de profil sont tirés du conteneur par des tests du dépôt et
du banc ; les classes du moteur demanderaient un relevé des trois hôtes que je ne peux pas
exécuter ici. Restreindre ce qu'on a vérifié vaut mieux que privatiser au jugé.

Vérifié au-delà des tests unitaires : la suite d'intégration qui démarre un vrai noyau
passe, y compris son `$container->get('durable.child_workflow_parent_link_store')`.

Suite unit : 1087 tests contre 1073 sur main, mêmes 4 erreurs d'environnement. PHPStan :
2 erreurs, la base de main.

Refs: M12 et M16 de documentation/audit/

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant