merge-graphs prefixes every node id with its repo tag, so a contract type that two services both declare becomes two unconnected nodes. On a message bus that is the one hop worth having: the producer names the message type in one repo, the consumer implements IConsumer<T> on the same type in the other, and nothing in the merged graph gets you from one to the other.
Concretely, from a merged graph of two .NET services:
p_CatalogService::...syncproductupserttosearchevent <- .UpdateProductOnSearchAppAsync() references it
p_ElasticsearchService::...syncproductupserttosearchevent <- ProductUpsertConsumer references it
Same namespace, same name, no edge between them. path from the producing method to the consumer returns nothing, so the flow reads as two unrelated halves.
Why name plus namespace is a usable signal here
I was expecting to need something structural and measured it first. Between the two services, 1440 and 262 sourced type declarations:
- 7 pairs agree on both namespace and name
- all 7 are the shared message contracts,
EventManager.Models.ClearCacheEvent, IndexCompletedEvent, PrepareElasticDataEvent, SyncPriceToSearchEvent, SyncProductDeleteToSearchEvent, SyncProductUpsertToSearchEvent, UpdateVariantElasticPublishedEvent
- nothing else matches, so no false pair to weigh against them
Dropping the namespace from the key is what makes this dangerous, and it fails immediately: both services carry their own Settings, Configuration and similar short names. Namespace agreement is doing the work.
Proposed shape
A pass after compose in the merge-graphs path that adds an edge between sourced type declarations agreeing on namespace and name and coming from different repos:
p_CatalogService::...event --same_type_as--> p_ElasticsearchService::...event
Edges, not node merging. Two repos can hold copies of a contract that have drifted, and collapsing them hides that, while a link lets a traversal cross with each side keeping its own members, file and provenance. That also keeps this out of #296 and #207's territory, which are about merging duplicates rather than relating them.
On the pair above it adds 7 edges and the producer-to-consumer walk becomes three hops:
.UpdateProductOnSearchAppAsync() (CatalogService)
-> SyncProductUpsertToSearchEvent (CatalogService)
-> SyncProductUpsertToSearchEvent (ElasticsearchService) [same_type_as]
-> ProductUpsertConsumer (ElasticsearchService)
One caveat worth stating: the last hop is a reverse traversal, because the consumer references the event rather than the other way round, so this shows up under an undirected walk. That direction is inherent to references and not something this pass changes.
How this differs from #2134
Cluster graphs there model cross-repo links declared by hand in cluster.json, file to file. That is the right tool when the connection is an API call with no shared symbol. A shared contract type needs no declaration: both repos already name it, so the join can be derived. The two compose rather than compete, and if cluster graphs land, the same pass belongs in cluster build too.
Related
Patch with tests coming against v8.
merge-graphsprefixes every node id with its repo tag, so a contract type that two services both declare becomes two unconnected nodes. On a message bus that is the one hop worth having: the producer names the message type in one repo, the consumer implementsIConsumer<T>on the same type in the other, and nothing in the merged graph gets you from one to the other.Concretely, from a merged graph of two .NET services:
Same namespace, same name, no edge between them.
pathfrom the producing method to the consumer returns nothing, so the flow reads as two unrelated halves.Why name plus namespace is a usable signal here
I was expecting to need something structural and measured it first. Between the two services, 1440 and 262 sourced type declarations:
EventManager.Models.ClearCacheEvent,IndexCompletedEvent,PrepareElasticDataEvent,SyncPriceToSearchEvent,SyncProductDeleteToSearchEvent,SyncProductUpsertToSearchEvent,UpdateVariantElasticPublishedEventDropping the namespace from the key is what makes this dangerous, and it fails immediately: both services carry their own
Settings,Configurationand similar short names. Namespace agreement is doing the work.Proposed shape
A pass after
composein themerge-graphspath that adds an edge between sourced type declarations agreeing on namespace and name and coming from different repos:Edges, not node merging. Two repos can hold copies of a contract that have drifted, and collapsing them hides that, while a link lets a traversal cross with each side keeping its own members, file and provenance. That also keeps this out of #296 and #207's territory, which are about merging duplicates rather than relating them.
On the pair above it adds 7 edges and the producer-to-consumer walk becomes three hops:
One caveat worth stating: the last hop is a reverse traversal, because the consumer references the event rather than the other way round, so this shows up under an undirected walk. That direction is inherent to
referencesand not something this pass changes.How this differs from #2134
Cluster graphs there model cross-repo links declared by hand in
cluster.json, file to file. That is the right tool when the connection is an API call with no shared symbol. A shared contract type needs no declaration: both repos already name it, so the join can be derived. The two compose rather than compete, and if cluster graphs land, the same pass belongs incluster buildtoo.Related
new Foo(...)produces no edge, so message publishers and locally built types look unused #2997 is what gives the producer side its edge to the message type in the first place. Without it the CatalogService half of that walk does not exist.Patch with tests coming against
v8.