# InterfaceMap 与 ServiceReferenceMap

声明一组具名 Interface 服务槽位，通过 ServiceReferenceMap 绑定同一 Runtime 中的多个目标服务。

`InterfaceMap` 用于声明“键到接口”的模块属性类型；对应的属性值必须是
`ServiceReferenceMap`。每个 `valueMap` 条目都是一个逻辑 `ServiceReference`，由调用模块自定义的稳定键
选择，同一个属性可以因此绑定多个符合接口契约的 Runtime 服务。

> **类型和值不能混用**
>
> `InterfaceMap` 只出现在 `propertyDefinition[].type` 中；`ServiceReferenceMap` 只出现在属性值、
> `defaultValue` 编译结果和 Runtime 配置中。协议名称区分大小写，不能写成 `interfaceMap`、
> `InterfaceMAP`，也不能把 `valueMap` 直接当作属性值顶层。

| 位置     | `@type`               | 主要字段             | 用途                          |
| ------ | --------------------- | ---------------- | --------------------------- |
| 模块属性定义 | `InterfaceMap`        | `valueInterface` | 约束每个映射值必须实现的接口              |
| 逻辑属性值  | `ServiceReferenceMap` | `valueMap`       | 保存键到目标服务完整 `businessId` 的绑定 |
| 生命周期回调 | `ServiceReferenceMap` | `valueMap`       | 保留映射结构，并把每个逻辑引用展开为可调用引用     |

## 在 module.json 中声明

下面的 `channels` 属性允许调用模块按 `primary`、`backup` 等业务键选择多个 Channel 服务。
`valueInterface` 与单值 `Interface` 的定义结构相同；其中的方法是调用方编译期的期望契约。

```json
{
  "propertyDefinition": [
    {
      "name": "channels",
      "type": {
        "@type": "InterfaceMap",
        "valueInterface": {
          "@type": "Interface",
          "name": "Channel",
          "methods": [
            {
              "name": "send",
              "returnType": {
                "@type": "DataType",
                "type": "object",
                "nullable": false
              },
              "paramDefinitions": [
                {
                  "name": "payload",
                  "type": {
                    "@type": "DataType",
                    "type": "object",
                    "nullable": false
                  },
                  "required": true
                }
              ]
            }
          ],
          "events": []
        },
        "nullable": false
      },
      "required": true,
      "defaultValue": "{}"
    }
  ]
}
```

- `valueInterface` 描述所有映射值共同遵守的接口，不为不同键分别声明不同接口。
- 映射键是调用模块自己的稳定业务键，不是模块名、Bundle 名、目标 `businessId` 或方法名。
- 空默认值写成 `"{}"`；模块版本编译后会形成空的 `ServiceReferenceMap`。不要在定义中加入
  Runtime 地址、token 或某个部署环境的可变服务 ID。
- 如果安装时就有明确且可移植的目标，可以在 `defaultValue` 的 JSON 对象中写入
  `ServiceReference` 条目；目标随安装结果变化时，应保持空默认值并在安装后配置。

需要复用公开 Type Definition 时，`valueInterface` 也可以使用受 Bundle
`typeDefinitionDependencies` 约束的 `RefType`。引用格式和版本范围见
[Type Definition](/development/bundle-development/type-definition-development/)。

## 配置 ServiceReferenceMap

安装后先从目标工作区的 Runtime 服务目录读取每个目标服务的完整 `businessId` 和当前方法定义，再创建
`properties.json`：

```json
{
  "channels": {
    "@type": "ServiceReferenceMap",
    "valueMap": {
      "primary": {
        "@type": "ServiceReference",
        "serviceId": "<primaryChannelBusinessId>"
      },
      "backup": {
        "@type": "ServiceReference",
        "serviceId": "<backupChannelBusinessId>"
      }
    }
  }
}
```

执行当前 CLI 的正式模块属性命令：

```bash
baijimu runtime app properties update \
  <callerBusinessId> \
  --workspace-id <workspaceId> \
  --properties @properties.json
```

- `--properties` 是模块属性的顶层部分更新；未出现的其他模块属性保持不变。
- `channels` 一旦出现，本次提交的整个 `ServiceReferenceMap` 就是它的新值。要保留旧条目，必须把这些条目
  一起放入本次 `valueMap`，不能只提交新增键并假设平台会做映射内合并。
- 清空映射时提交 `{"@type":"ServiceReferenceMap","valueMap":{}}`。不要提交裸 `{}`、数组、
  `null`，也不要添加 `empty`、`_editingEntries` 等非协议字段。
- 每个 `serviceId` 都必须是同一 Runtime 中可解析的完整 `businessId`。Bundle `dependencies` 只保证安装
  闭包和顺序，不替代这里的实际属性绑定。

属性写入时 Runtime 会校验所有嵌套引用并收敛相应访问关系。任一目标无法解析或引用无法授权时，本次配置
更新会明确失败，不会只保存可用的部分。

## 生命周期回调收到的结构

安装时的 `BEFORE_DEPLOY` 或安装后修改属性触发的 `ON_CONFIG_UPDATE` 会保持
`ServiceReferenceMap.valueMap` 结构，同时把每个逻辑 `ServiceReference` 展开为
`ExternalServiceReference`：

```json
{
  "channels": {
    "@type": "ServiceReferenceMap",
    "valueMap": {
      "primary": {
        "@type": "ExternalServiceReference",
        "url": "https://<runtime-service-base>/<primaryChannelBusinessId>",
        "serviceId": "<primaryChannelBusinessId>",
        "token": "<opaque-reference-token>"
      },
      "backup": {
        "@type": "ExternalServiceReference",
        "url": "https://<runtime-service-base>/<backupChannelBusinessId>",
        "serviceId": "<backupChannelBusinessId>",
        "token": "<opaque-reference-token>"
      }
    }
  }
}
```

模块后端必须按整组值原子替换本地敏感配置，并通过相应条目的 `url` 和 `token` 调用目标方法。不得自行
拼接 Runtime 网关地址、解析 token、绕过 Runtime 直连目标 Hosted Backend，也不得把展开后的值写回
模块属性。完整调用和请求级用户委托分别见
[模块服务间调用协议](/development/bundle-development/module-development/service-to-service-calls/)和
[Runtime 用户委托](/development/bundle-development/module-development/actor-delegation/)。

## 与 Interface 和 InterfaceList 的区别

| 属性类型            | 属性值                              | 选择方式              |
| --------------- | -------------------------------- | ----------------- |
| `Interface`     | `ServiceReference`               | 单个目标服务            |
| `InterfaceMap`  | `ServiceReferenceMap.valueMap`   | 由调用模块定义的稳定键选择目标服务 |
| `InterfaceList` | `ServiceReferenceList.valueList` | 按数组顺序遍历目标服务       |

不要因为当前只有一个目标就把 `ServiceReference` 塞进 `InterfaceMap`，也不要依赖 JSON 对象键的顺序来
表达优先级；需要有序选择时使用 `InterfaceList`。

## 验证清单

1. 当前不可变模块版本的 `propertyDefinition` 中存在 `InterfaceMap`，且字段名是 `valueInterface`。
2. 每个候选目标 Bundle 已安装到同一 Runtime，目标完整 `businessId`、方法和 `paramDefinitions` 已回读。
3. `properties.json` 使用 `ServiceReferenceMap.valueMap`，每个值都是只含目标 `serviceId` 的
   `ServiceReference`。
4. 属性更新命令成功；失败时保留原错误并修正全部无效条目，不拆成部分成功或添加本地兜底。
5. 生命周期 Endpoint 在 `BEFORE_DEPLOY` 或 `ON_CONFIG_UPDATE` 收到相同键集合，且每个值都已展开为
   `ExternalServiceReference`；验证时不得输出 token。
6. 模块后端按键选择引用并通过 Runtime 网关调用真实目标方法；删除或替换条目后停止使用旧引用并清除
   本地副本。
