[FEATURE REQ] Store subscription ownerId relative to the service, the way scope already is
- 主要言語
- C#
- スター
- 448
- フォーク
- 247
- PR マージ指標
- 30日以内にマージされた PR はありません
説明
v7 relativizes cross-resource references so an artifact can be published to any instance, which is a great improvement. Subscription `scope` is covered, but `ownerId` is not, so subscription artifacts remain pinned to the instance they were extracted from.
### What the extractor writes today
Extracting a subscription produces:
```json
{
"properties": {
"displayName": "Unlimited",
"scope": "/products/unlimited",
"ownerId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-dev/providers/Microsoft.ApiManagement/service/apim-dev/users/1",
"state": "active"
}
}
```
`scope` is relative and publishes anywhere. `ownerId` still carries the source instance's subscription id, resource group and service name, so publishing this artifact to another instance requires a configuration override for every subscription:
```yaml
subscriptions:
- name: unlimited
properties:
ownerId: "/subscriptions/{#SUB#}/resourceGroups/{#RG#}/providers/Microsoft.ApiManagement/service/{#SVC#}/users/1"
```
That override is pure boilerplate. It says nothing except "the same user id, on whichever instance this is", which is exactly what a relative id already expresses.
### The relative form is the documented contract
This is not a request to invent a new format. Microsoft documents `ownerId` as relative:
> **ownerId** — The user resource identifier of the subscription owner. The value is a valid relative URL in the format of `/users/{userId}` where `{userId}` is a user identifier.
[SubscriptionContract.ownerId](https://learn.microsoft.com/javascript/api/@azure/arm-apimanagement/subscriptioncontract)
APIM returns the expanded absolute form on GET, the same as it does for `scope`, but the documented canonical format is relative. So the extractor is persisting a shape that is less portable than the contract allows.
### Why the existing mechanism cannot simply be extended
The obvious change is to add `ownerId` to `OptionalReferencedResourceDtoProperties` in `SubscriptionResource`, next to the two `Scope` entries. That does not work, for two reasons:
1. That dictionary is keyed by `IResource`, and there is no `UserResource`. Introducing one would mean APIOps starts modelling users as an extractable and publishable resource type, which is a much larger change than this problem warrants.
2. The same dictionaries feed the publisher's relationship graph in `Relationships.cs` (`getReferences`). A `UserResource` key would create a subscription-to-user dependency edge, so the publisher would expect the user in source and `STRICT_VALIDATION=true` would report a missing predecessor. Users are typically not managed as artifacts, so that would be a false failure.
The two concerns are currently coupled: "this property holds an absolute id that should be stored relative" and "this property references a resource we manage". `ownerId` is the first case without the second.
### Proposed change
Separate the two by adding a relativization-only declaration.
**`common/Resource.cs`**, on `IResourceWithReference`:
```csharp
///
/// DTO properties holding an absolute resource id that should be persisted relative to the
/// service, but whose target is not a resource APIOps manages. These take part in id
/// relativization only; they never contribute to the relationship graph.
///
ImmutableHashSet RelativeIdDtoProperties => [];
```
**`common/Resource.FileSystem.cs`**, include them when formatting the information file:
```csharp
private static JsonObject FormatInformationFileDto(this IResourceWithReference resource, JsonObject dto) =>
resource.MandatoryReferencedResourceDtoProperties
.Union(resource.OptionalReferencedResourceDtoProperties)
.Select(kvp => kvp.Value)
.Union(resource.RelativeIdDtoProperties)
.Aggregate(dto, SetAbsoluteToRelativeId);
```
`Union` on the projected property names also removes the need for the current `DistinctBy(kvp => kvp.Value)`, which exists because `Scope` is declared twice.
**`common/Subscription.cs`** and **`common/WorkspaceSubscription.cs`**:
```csharp
public ImmutableHashSet RelativeIdDtoProperties { get; } =
[nameof(SubscriptionDto.Properties.OwnerId)];
```
The interface member defaults to empty, so nothing else changes and no existing resource is affected. `nameof` follows the convention already used for `Scope` and `LoggerId`, which works because DTOs are parsed with `JsonSerializerOptions.Web`.
### Scope
- `SubscriptionResource.OwnerId`
- `WorkspaceSubscriptionResource.OwnerId`
I looked for other properties in the same category and did not find an obvious one, but the new member gives a place for them if they turn up.
### Effect
Subscription artifacts become fully instance-agnostic, and the per-subscription `ownerId` override disappears from every environment configuration file. In our own repo that is 21 override lines in one operator repository and 3 in each of two environment files in another.
### Happy to implement
I am glad to send the PR with tests if you agree with the shape. The main open question is naming and whether you would rather see this as a separate member as proposed, or handled some other way, for example a marker on the DTO property or widening the existing dictionaries to accept a null resource key.
コントリビューションガイド
調査の方向性
common/Resource.cs の IResourceWithReference と common/Resource.FileSystem.cs の DTO フォーマット処理から始め、次に common/Subscription.cs と common/WorkspaceSubscription.cs の参照宣言を比較します。これらの宣言が FormatInformationFileDto と Relationships.cs getReferences にどのように渡されるかを追跡します。完了条件は、両方のサブスクリプションリソースで ownerId が相対形式で永続化され、ユーザーリレーションシップの依存関係を作成せず、抽出と公開の動作をテストでカバーすることです。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- csharp
- 領域
- api, backend
- issue の種類
- 機能追加
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 活発
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 55/100