Skip to content

Extend multiple-services design doc with nested @client pattern for client hierarchy customization#3847

Open
Copilot wants to merge 19 commits intomainfrom
copilot/update-client-hierarchy-design
Open

Extend multiple-services design doc with nested @client pattern for client hierarchy customization#3847
Copilot wants to merge 19 commits intomainfrom
copilot/update-client-hierarchy-design

Conversation

Copy link
Contributor

Copilot AI commented Jan 27, 2026

Extends the multiple-services.md design document to support advanced client hierarchy customization using nested @client decorators.

Key Design Changes

Default Behavior for Multiple Services

  • Without explicit @client: Each @service namespace becomes its own root client automatically (consistent with single-service behavior)
  • With explicit @client: Users can customize client names or merge multiple services into one client

Key Design Principle (for explicit @client)

  • If the @client namespace is empty: TCGC auto-merges all services' nested namespaces/interfaces into the current client as children
  • If the @client namespace contains nested @client decorators: TCGC uses the explicitly defined client hierarchy instead of auto-merging

Scenarios

Scenario 0: Multiple Services Without Explicit @client (Default Behavior)

Multiple @service namespaces automatically become multiple root clients (e.g., ServiceAClient and ServiceBClient)

Scenario 1: Explicit Client Names for Multiple Services

Use @client to customize client names (e.g., MyServiceAClient instead of ServiceAClient)

Scenario 1.5: Mixing Multi-Service and Single-Service Clients

Mix a multi-service client (combining ServiceA and ServiceB) with a single-service client (ServiceC) in the same package

Scenario 2: Services as Direct Children (No Deep Auto-Merge)

Use nested @client decorators to explicitly define each service as a child client, preventing deep auto-merging

Scenario 3: Fully Customized Client Hierarchy

Use nested @client decorators with explicit operation mapping via is keyword

Documentation Updates

  • Updated client.md to clarify that each @service namespace becomes a separate root client
  • Added full Scenario 0 with YAML output and Python examples for default behavior
  • Added Scenario 1.5 showing how to mix multi-service and single-service clients
  • Added YAML examples with proper children property (matching SdkClientType interface)
  • Added clientInitialization with initializedBy property to all YAML examples following client.md defaults
  • Added Python SDK examples for all scenarios
  • Added apiVersionsMap for multi-service clients (non-breaking change)
  • Added endpoint and credential limitations documentation
  • Expanded Changes Needed section
  • Clarified that only services listed in the service array are included in the client
  • Added service property to all nested @client decorators in Scenario 3

Design doc only—no code changes included.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits January 27, 2026 09:29
… customization

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Copilot AI changed the title [WIP] Update client hierarchy design for multiple services support Extend multiple-services design doc with autoMerge option for client hierarchy customization Jan 27, 2026
Copilot AI requested a review from tadelesh January 27, 2026 09:31
Copy link
Member

@tadelesh tadelesh left a comment

Choose a reason for hiding this comment

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

@copilot Refine with my comment. I don't want to add the new autoMerge flag. You need to check the current two design doc and current cache.ts implementation code to see this new design could meet all current cases and the new scenarios.

…stead

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 28, 2026

@copilot Refine with my comment. I don't want to add the new autoMerge flag. You need to check the current two design doc and current cache.ts implementation code to see this new design could meet...

Refactored the design to remove the autoMerge flag. The new approach uses the existing @client decorator pattern where:

  • Empty namespace = auto-merge (first step design behavior)
  • Namespace with nested @client = explicit hierarchy

Also fixed subClientschildren and replaced YAML comments with real methods entries per the interface.ts structure. See commit f20ed7f.

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Copilot AI requested a review from tadelesh January 28, 2026 08:21
Copy link
Member

@tadelesh tadelesh left a comment

Choose a reason for hiding this comment

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

@copilot Resolve the remaining comments.

…tion to all YAML, remove comparison code

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 28, 2026

@copilot Resolve the remaining comments.

Resolved all remaining comments in commit 83e9199:

  • Added Python SDK example for Scenario 1
  • Added clientInitialization with initializedBy to all YAML examples following client.md defaults
  • Removed the comparison code in Scenario 2
  • Removed the diagnostic warning about unreferenced operations

Copilot AI changed the title Extend multiple-services design doc with autoMerge option for client hierarchy customization Extend multiple-services design doc with nested @client pattern for client hierarchy customization Jan 28, 2026
Copilot AI requested a review from tadelesh January 28, 2026 09:20
Copy link
Member

@tadelesh tadelesh left a comment

Choose a reason for hiding this comment

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

@copilot I also want to make a change for client with multiple services: to make the apiVersions array could be 2-dimension to store all possible API versions for multiple services.

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 28, 2026

@copilot I also want to make a change for client with multiple services: to make the apiVersions array could be 2-dimension to store all possible API versions for multiple services.

Updated apiVersions to be a 2-dimensional array for multi-service clients (e.g., [[av1, av2], [bv1, bv2]]) to store all possible API versions for each service. See commit b837d38.

Copilot AI requested a review from tadelesh January 28, 2026 09:29
kind: client
name: CombineClient
apiVersions: []
apiVersions: [[av1, av2], [bv1, bv2]] # 2-dimensional array for multiple services
Copy link
Member

@weidongxu-microsoft weidongxu-microsoft Jan 29, 2026

Choose a reason for hiding this comment

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

Would it be beneficial to have a more explicit type (or a dict of namespace -> [<versions>]is also better).

A 2-dimensional array is not easy to understand (at least, to me...) -- which direction is for which catalog?

Copy link
Member

Choose a reason for hiding this comment

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

@copilot Change the apiVersions to be a map, the key is a service namespace's full qualified name, the value if the api versions array. Let's make it to be a non-breaking change, add a new property apiVersionMap, keep previous apiVersions property as a 1-dimensinal array and keep it empty for cross service client.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to use apiVersionsMap (a map of service namespace full qualified name to API versions array) instead of a 2D array. The apiVersions property is kept as an empty array for cross-service clients to maintain backward compatibility. See commit c0517f2.


### Scenario 2: Services as Direct Children (No Deep Auto-Merge)

In the first step design, when combining multiple services with an empty client namespace, all nested namespaces/interfaces from all services are auto-merged into the root client as children. Some users prefer to keep each service's namespace as a direct child of the root client without deep merging.
Copy link
Member

@weidongxu-microsoft weidongxu-microsoft Jan 29, 2026

Choose a reason for hiding this comment

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

Personally, I feel it more natural to behave according to the standard Namespace arrangement. If there is 2 root Namespace, it would be 2 root client.

Or, if we don't want this behavior, we can let TCGC error out, if no explicit client is specified on multiple-service case.

Automatically "merge" the 2 independent Namespace into a single root client, seems not match the single service behavior.
(I think if 2 root Namespace belong to a single service, TCGC will produce 2 root client, right?)

Copy link
Member

Choose a reason for hiding this comment

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

I prefer if there are two root @service namespace, then TCGC auto make them to be two root clients.

Copy link
Member

Choose a reason for hiding this comment

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

@copilot Change previous design in client.tsp of just detect the first @service to be one client. If there are multiple services defined, each @service should be a separate client.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the design so that multiple @service namespaces without explicit @client will automatically create separate root clients (one per service). Also updated client.md to clarify this behavior. See commit 448799a.

Copy link
Member

Choose a reason for hiding this comment

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

@copilot You need also to add another scenario into multiple-services.md to show this use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added "Scenario 0: Multiple Services Without Explicit @client (Default Behavior)" with full TCGC behavior details, YAML output, and Python SDK example. See commit 59382d3.

When TCGC detects multiple services in one client, it will:

1. Create the root client for the combined client. If any service is versioned, the root client's initialization method will have an `apiVersion` parameter with no default value. The `apiVersions` property and the `apiVersion` parameter for the root client will be empty (since multiple services' API versions cannot be combined). The root client's endpoint and credential parameters will be created based on the first sub-service, which means all sub-services must share the same endpoint and credential.
1. Create the root client for the combined client. If any service is versioned, the root client's initialization method will have an `apiVersion` parameter with no default value. The `apiVersions` property for the root client will be a 2-dimensional array to store all possible API versions for each service (e.g., `[[av1, av2], [bv1, bv2]]`). The root client's endpoint and credential parameters will be created based on the first sub-service, which means all sub-services must share the same endpoint and credential.
Copy link
Member

Choose a reason for hiding this comment

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

what if different services use different endpoint and credential, will TCGC report error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, TCGC will report a diagnostic error if services have different @server or @useAuth definitions. This is documented in the TCGC Behavior section and the "Endpoint and Credential Limitations" sections for Scenarios 2 and 3.

Copy link
Member

@tadelesh tadelesh Jan 30, 2026

Choose a reason for hiding this comment

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

Yes. There will be diagnostics. TCGC does not support mix operations with different endpoint/credential into one client. This is mentioned in the following scenarios.

Copy link
Member

Choose a reason for hiding this comment

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

I see the limitation is mentioned only in Scenario 2 and 3, but it applies to all scenarios. Is it correct?

I am curious about the difference between apiVersion and endpoint, credential. If they are all concepts of a service, why is only apiVersion allowed to be different across services, while endpoint and credentials are not?

Copy link
Member

Choose a reason for hiding this comment

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

No. For different root clients, they could have different endpoint and credential.
For current client design across languages, the endpoint and credential are always set in root client level, so it is kind of a limitation for current SDK design.

The key design principle is:

- **If the client namespace is empty**: TCGC auto-merges all services' nested namespaces/interfaces into the current client as children (first step design behavior).
- **If the client namespace contains nested `@client` decorators**: TCGC uses the explicitly defined client hierarchy instead of auto-merging.
Copy link
Member

Choose a reason for hiding this comment

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

Probably this will not happen in ARM, but do we support the mix, e.g. some sub clients could be merged while others has explicitly client hierachy?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current design doesn't support a mix of merged and explicitly defined sub-clients at the same level. When the namespace has any nested @client decorators, TCGC uses only the explicitly defined hierarchy. To achieve partial merging, you would need to explicitly define all child clients you want.

Copy link
Member

Choose a reason for hiding this comment

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

This is a recursive behavior. For each @client namespace, TCGC will decide the merge behavior based on if there is any nested @client. So, you could mix all kinds of clients as you wish.


@client({
name: "CombineClient",
service: [ServiceA, ServiceB],
Copy link
Member

@lirenhe lirenhe Jan 30, 2026

Choose a reason for hiding this comment

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

What if we define service: [ServiceA, ServiceB, ServiceC],
but in namespace CombineClient, there is only serviceA and serviceB, will serviceC be ignored in client?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If a service is listed in service: [ServiceA, ServiceB, ServiceC] but not used in any nested @client decorator, its operations would be omitted from the SDK. The design doc mentions in Validation Rules: "Operations not referenced by any explicit client are omitted from the SDK."

Copy link
Member

Choose a reason for hiding this comment

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

Yes. If there are explicit nested @client then the service here is not so important.

Copy link
Member

Choose a reason for hiding this comment

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

If there are nested clients, could the service option be optional since it doesn't have any effect? Per my understanding, when nested clients exist, the root client's role is only to aggregate its nested clients, and no operations can be called by this root client. Is it correct?

Copy link
Member

Choose a reason for hiding this comment

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

The reason for setting service is easy for implementation and lint check. I could try to find out if it is possible to make it optional when changing the code.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks. My concern is that users might find it difficult to understand the purpose of the service option and set its value correctly when it's already set in the nested clients.

…ients

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
…nts by default

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
…and Python examples

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
### Validation Rules

1. When the root client namespace is empty:
- All services' content is auto-merged into the root client
Copy link
Member

Choose a reason for hiding this comment

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

If there are ServiceA, ServiceB, and ServiceC, and the client.tsp is

@client({
  name: "CombineClient",
  service: [ServiceA, ServiceB],
})
namespace CombineClient;

Will ServiceC's content be merged into CombineClient?

Copy link
Member

Choose a reason for hiding this comment

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

No. There will be only one CombineClient with ServiceA and ServiceB contents.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, ServiceC's content will NOT be merged into CombineClient. Only the services listed in the service array (ServiceA and ServiceB) will be included in the client. ServiceC would not be part of any client unless explicitly defined with its own @client decorator.

Copy link
Member

@JialinHuang803 JialinHuang803 Feb 2, 2026

Choose a reason for hiding this comment

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

@copilot The "All services' content ..." here is a bit confusing. Please state that only the services listed in the service array will be included in the client.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Clarified the validation rules to explicitly state that only services listed in the service array of the @client decorator are included in the client. See commit 1c7e6f2.

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Co-authored-by: JialinHuang803 <139532647+JialinHuang803@users.noreply.github.com>
})
namespace CustomClient {
// Custom child client combining operations from both services
@client
Copy link
Member

Choose a reason for hiding this comment

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

I noticed the warning @azure-tools/typespec-client-generator-core/client-service that appears when a client doesn't belong to any service namespace.

What service namespace(s) do the three nested clients belong to? Do we need to specify it explicitly?

Copy link
Member

Choose a reason for hiding this comment

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

It should be an array that containing all nested operations' and sub-clients' services.

Copy link
Member

Choose a reason for hiding this comment

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

We may need to update the example here to set the correct service value if it's required.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, it has some contradiction here. @copilot Please update all examples to add service for multiple service scenarios.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated Scenario 3 to add service property to all nested @client decorators. Each nested client now specifies its service(s): SharedOperations has service: [ServiceA, ServiceB], ServiceAOnly has service: ServiceA, and ServiceBOnly has service: ServiceB. See commit ce258e0.


@client({
name: "CustomClient",
service: [ServiceA, ServiceB],
Copy link
Member

Choose a reason for hiding this comment

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

Will a diagnostic be reported if the customer forgets to include all the related services here?

Copy link
Member

Choose a reason for hiding this comment

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

I think yes. But it may be not easy to implement it. I could estimate when changing the code.

Co-authored-by: tadelesh <1726438+tadelesh@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib:tcgc Issues for @azure-tools/typespec-client-generator-core library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants