Skip to main content

mitigate-breaking-changes

Patterns and techniques for mitigating breaking changes during Azure management-plane SDK migration from Swagger/AutoRest to TypeSpec. Covers SDK-side customizations (partial classes, CodeGenType, CodeGenSuppress) and TypeSpec decorator customizations (clientName, access, markAsPageable, alternateType, hierarchyBuilding).

소스 정보

저장소
Azure/azure-sdk-for-net
최근 소스 활동
2026년 7월 28일 23:28
감지된 SKILL.md 언어
영어
스타
6,034
포크
5,179

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
mitigate-breaking-changes
description
Patterns and techniques for mitigating breaking changes during Azure management-plane SDK migration from Swagger/AutoRest to TypeSpec. Covers SDK-side customizations (partial classes, CodeGenType, CodeGenSuppress) and TypeSpec decorator customizations (clientName, access, markAsPageable, alternateType, hierarchyBuilding).
# Skill: mitigate-breaking-changes Patterns and techniques for mitigating breaking changes when migrating or regenerating Azure management-plane .NET SDKs. Use these to preserve backward compatibility in the generated SDK surface. ## When Invoked Trigger phrases: "mitigate breaking changes", "fix breaking change", "customization patterns", "how to keep backward compat", "CodeGenType", "CodeGenSuppress", "markAsPageable", "hierarchyBuilding", "base type change". ## SDK-Side Customizations (in SDK repo) Use **Custom/*.cs** or **Customization/*.cs** partial classes (follow the package's existing structure) for .NET-side fixes. ### Custom code file organization Keep custom code types split into separate files: - Put each custom type/partial type in its own `.cs` file. - Name the file after the class it contains, for example `src/Custom/Models/MyModel.cs` for `public partial class MyModel`. - Align the custom code folder structure with the generated code structure, such as using `Models/` for model customizations and `Extensions/` for extension customizations when those folders exist. - Do not group multiple compatibility types in a single broad file such as `Compatibility.cs`; split them so each file name aligns with the class name. ### Partial class (add members, suppress generated members) ```csharp // src/Custom/Models/MyModel.cs (or src/Customization/Models/MyModel.cs — follow the package's existing convention) namespace Azure.ResourceManager.<Service>.Models { public partial class MyModel { // Add computed properties, rename via [CodeGenMember], etc. } } ``` ### `[CodeGenType]` — Override accessibility or rename a generated type When a generated type is `internal` and `@@access` in `client.tsp` doesn't work (common for nested/wrapper types), use `[CodeGenType]` in Custom code to make it public: ```csharp // src/Custom/Models/MyPublicModel.cs using Microsoft.TypeSpec.Generator.Customizations; namespace Azure.ResourceManager.<Service>.Models { [CodeGenType("OriginalTypeSpecModelName")] public partial class MyPublicModel { } } ``` The `[CodeGenType("...")]` attribute takes the **original TypeSpec model name** (not the C# renamed name). This links the Custom partial class to the generated internal type and overrides its accessibility to `public`. ## TypeSpec Decorator Customizations (in spec repo) These decorators are added to `client.tsp` in the spec repo. ```typespec // Rename parameter @@clientName(Operations.create::parameters.resource, "content"); // Rename path parameter @@Azure.ResourceManager.Legacy.renamePathParameter(Resources.list, "fooName", "name"); // Mark a non-pageable list operation as pageable (returns Pageable<T> instead of Response<ListType>) // Requires: using Azure.ClientGenerator.Core.Legacy; #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "migration" @@markAsPageable(InterfaceName.operationName, "csharp"); // Suppress warning #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "migration" ``` ### When to use `@@markAsPageable` When the old SDK returned `Pageable<T>` / `AsyncPageable<T>` for a list operation, but the TypeSpec spec defines the operation as non-pageable (returns a wrapper list type like `FooList`), use `@@markAsPageable` to make the generator produce pageable methods. This is **preferred over** writing custom `SinglePagePageable<T>` wrapper code because: - It reduces custom code that must be maintained - The generated pageable implementation handles diagnostics, cancellation, and error handling correctly - It keeps the SDK surface consistent with other generated methods **Do NOT use `@@markAsPageable` if the operation is already marked with `@list`** — the `@list` decorator already makes the operation pageable, and adding `@@markAsPageable` will cause a compile error. Check the spec's operation definition before adding the decorator. **Requirements:** 1. Add `using Azure.ClientGenerator.Core.Legacy;` to the `client.tsp` imports 2. Add `#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "migration"` before each `@@markAsPageable` call 3. After adding the decorator, regenerate and remove any custom `[CodeGenSuppress]` + `SinglePagePageable` wrapper code ### `@@alternateType` Decorator When the spec uses older common types that generate incorrect C# types (e.g., `string` instead of `ResourceIdentifier` for ID properties), use `@@alternateType`: ```typespec @@alternateType(MyModel.resourceId, Azure.ResourceManager.CommonTypes.ArmResourceIdentifier, "csharp"); ``` ### `@@hierarchyBuilding` Decorator — Legacy base-type override Do **not** use `@@hierarchyBuilding` for C# base-model/base-type compatibility during MPG migrations. Follow the `mpg-migration` skill instead: verify resource-hierarchy parity first, fix structural resource hierarchy issues in the TypeSpec resource shape, and use SDK-side custom code only for C# base-model/base-type compatibility after the generated surface is stable. `@@hierarchyBuilding` is a legacy escape hatch. Use it only when the migration owner explicitly approves it and no TypeSpec resource-shape fix or SDK-side customization is appropriate. **Syntax:** ```typespec // Requires: using Azure.ClientGenerator.Core.Legacy; #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "Change the base type back to <TargetBase> for backward compatibility" @@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(MyResource, Azure.ResourceManager.Foundations.TrackedResource, "csharp" ); ``` **Common target base types:** - `Azure.ResourceManager.Foundations.TrackedResource` — generates `TrackedResourceData` (for resources with location and tags) - `Azure.ResourceManager.Foundations.ProxyResource` — generates `ResourceData` (for proxy/child resources) - `Azure.ResourceManager.Foundations.Resource` — generates `ResourceData` (ARM resource base) **Legacy-only scenarios that require explicit approval:** - The old SDK had `MyData : ResourceData` or `MyData : TrackedResourceData`, the new TypeSpec-generated SDK produces `MyData : SomeOtherType` (e.g., a service-local `Resource` model), and the migration owner has explicitly rejected the normal MPG migration fix path. - The `CannotRemoveBaseTypeOrInterface` API compatibility violation remains after verifying resource-hierarchy parity and attempting the normal SDK-side customization approach. **Requirements:** 1. Add `using Azure.ClientGenerator.Core.Legacy;` to the `client.tsp` imports 2. Add `#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "..."` before each `@@hierarchyBuilding` call 3. After adding the decorator, regenerate the SDK code **Legacy-approved example** (from KeyVault migration): ```typespec import "@azure-tools/typespec-client-generator-core"; using Azure.ClientGenerator.Core.Legacy; // Fix Vault resource to generate VaultData : TrackedResourceData #suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "Change the base type back to TrackedResource for backward compatibility" @@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(Vault, Azure.ResourceManager.Foundations.TrackedResource, "csharp" ); ``` ## WirePathAttribute Breaking Changes [MPG only] When the previous SDK version included `WirePathAttribute` on model properties (used by Azure.Provisioning libraries), migrating to TypeSpec may produce ApiCompat `CannotRemoveAttribute` errors for the missing attribute — because the emitter defaults to **not** generating it. ### How to detect - ApiCompat `CannotRemoveAttribute` errors referencing `WirePathAttribute` on model properties - These errors appear during `dotnet pack --no-restore` when the previous SDK release had `WirePathAttribute` on properties but the new generation does not ### Fix Add `enable-wire-path-attribute: true` to the **mgmt emitter options** in `tspconfig.yaml` (in the spec repo): ```yaml options: "@azure-typespec/http-client-csharp-mgmt": emitter-output-dir: "{output-dir}/{service-dir}/{namespace}" namespace: "Azure.ResourceManager.<Service>" enable-wire-path-attribute: true ``` Then regenerate the SDK. If the remaining ApiCompat diff is only `WirePathAttribute` removal, it is acceptable to add targeted entries to the centralized baseline file under `eng/apicompatbaselines/<Project>.xml`. Do not add SDK custom code just to restore `WirePathAttribute`; the maintenance cost is not worth it for this compatibility diff. Do not create a local `ApiCompatBaseline.txt`, do not baseline unrelated ApiCompat errors, and do not disable ApiCompat. ## Extension Resources Extension resources (deployed onto parent resources from different providers) require special handling. ### Parameterized Scopes When the same resource type can be deployed onto multiple parent types (e.g., VM, HCRP, VMSS), use `OverrideResourceName` with parameterized scopes. Each scope generates a separate SDK resource type. The generator may produce duplicate `GetXxxResource()` methods in `MockableArmClient` when multiple entries exist for the same resource — this is a known generator bug requiring deduplication. ### Sub-Resource Operations: Avoid `Read<>` / `Extension.Read<>` for Non-Lifecycle Ops When a sub-resource operation (e.g., getting a report under an assignment) uses `Read<>` or `Extension.Read<>` templates, the ARM library treats it as a **lifecycle read** operation. This causes: - The resource's `resourceType` and `resourceIdPattern` to be set to the sub-resource path - Collections using the wrong REST client - Compile errors: CS1729 (wrong constructor), CS0029 (type mismatch), CS1503 (wrong argument) **Fix**: Change sub-resource Get operations from `Read<>` to `ActionSync<>` (or `Extension.ActionSync<>` for extension resources) with a `@get` decorator: ```typespec // WRONG — ARM library treats this as lifecycle Read reportGet is Ops.Read<Resource, Response = ArmResponse<Report>, ...>; // CORRECT — ActionSync with @get avoids lifecycle misclassification @get reportGet is Ops.ActionSync<Resource, void, Response = ArmResponse<Report>, ...>; ```
GitHub에서 보기