Microsoft Graph Operations 设计模式:用 Functions 与 Actions 建模复杂业务操作

原创2026-09-30 22:21:151,745 阅读
文章标签:API设计

Microsoft Graph Operations 设计模式:用 Functions 与 Actions 建模复杂业务操作

导读:本文讲解 Microsoft Graph API 设计模式库中的 Operations 模式(见 graph/patterns/operations.md)。当一次业务变更涉及多个资源、或带有无法用标准 HTTP 方法表达的业务语义时,API 设计者需要引入"操作资源"——即 OData Functions(函数)与 Actions(动作)。读完本文,你将掌握:在何种场景下选择函数还是动作、Microsoft Graph 对绑定操作(bound operation)的硬性约束(isBound="true"、绑定参数、重载与可选参数规则)、动作必须用 POST 而函数必须用 GET 调用等设计红线,以及 forward、recent、getBrowserUserCounts 三个可复用的真实示例与对应 CSDL 元数据定义。

模式要解决的问题:HTTP 方法表达不了的业务操作

在建模复杂业务域时,API 设计者常常需要建模这样一类业务操作:它作用于一个或多个资源,并且带有无法由标准 HTTP 方法表达的额外语义。如果在每个资源上分别用 HTTP 方法建模,要么低效(需要多次往返),要么会暴露服务端内部实现细节。

例如"转发一封邮件":forward 不是简单的 PATCH(修改邮件字段)或 DELETE,它涉及创建收件人可见的新消息副本、记录转发行为等一系列服务端业务逻辑。同样,"查看最近应用活动"、"生成某段时间内的活跃用户统计报告"也都不适合映射为 CRUD。

针对这类场景,Microsoft Graph REST API Guidelines 在 graph/GuidelinesGraph.md 的 "Behavior modeling" 一节中给出了总原则:API 行为由 HTTP 方法表达,必要时可用操作资源(functions 与 actions)补充,且只要可能,应优先采用基于资源的标准 HTTP 方法设计,而不是操作资源。操作资源是 API 行为建模的"补充手段",并非默认首选。

解决方案:Function 与 Action 的选型规则

Operations 模式给出的核心方案是引入操作资源(operational resources),即 OData 的 functions(函数) 和 actions(动作)。二者的本质区别(也见 graph/GuidelinesGraph.md 对 OData 标准的引用):

维度 OData Function(函数) OData Action(动作)
副作用 无可观测副作用(不修改服务端数据) 可能有副作用
返回值 必须返回单个类型实例或实例集合 可返回单个实体或任意类型的集合,也可不返回
调用动词 MUST 使用 GET MUST 使用 POST
典型场景 查询、报表、计算 转发、移动、触发异步任务等改变数据的操作

选型判据一句话总结:如果操作没有任何副作用,并且必须返回单个类型实例或实例集合,设计者 SHOULD 使用 OData 函数;否则,可以将操作建模为动作。

何时使用该模式:四个典型判定特征

Operations 模式在以下一种或多种情况同时成立时是合理的:

  1. 表达"对资源的变化"而非"资源的状态":例如"把某个属性的值自增",而不是"该属性的最终值"。前者是操作语义,后者是状态语义。
  2. 存在不应暴露给客户端的复杂处理逻辑:把服务端复杂流程封装为操作资源,客户端只关心输入与结果。
  3. 操作参数传达受限制的选项集合:例如报表必须指定一个日期范围(如 period 只能取 7/30/90/180 天),用参数化操作比暴露原始资源更安全可控。
  4. 操作依赖某些服务端数据:这些数据不对用户暴露,或在用户上下文中不易获取(例如聚合统计所需的内部遥测数据)。

判定后还需评估相邻模式:如果操作执行时间很长,应考虑 Long running operations 模式(异步化、可轮询、可取消);如果客户端需要增量获取数据变更,则应参考 Change tracking 模式(delta 函数 + deltaLink/nextLink 机制)。

关键约束与设计考量

以下约束是 Microsoft Graph 对操作资源的硬性规定,API 设计者在元数据(CSDL)和运行时都必须遵守。

1. Microsoft Graph 不支持未绑定操作

Microsoft Graph 不支持 unbound(未绑定)的 actions 或 functions。所有绑定操作(bound operations)必须满足:

  • 必须带有 isBound="true" 属性;
  • 必须带有一个绑定参数(binding parameter);
  • 绑定操作在类型匹配绑定参数类型的资源上被调用;
  • 绑定操作的第一个参数永远是绑定参数;
  • 绑定参数可以是任何类型,且其参数值 MAY(可以)为 Nullable。

该约束与 graph/GuidelinesGraph.md 中"操作资源必须具有与绑定资源类型匹配的绑定参数"的规则相互印证。

2. 动作与函数都支持重载(Overloading)

一个 schema 中可以存在多个同名(同签名族)的 actions 或 functions。当为 action 或 function 添加参数时,必须遵循 OData CSDL 标准中的函数重载规则(函数名相同、参数列表不同的多个声明合法)。

3. 参数的 MUST 规则

由于 Microsoft Graph 只支持绑定操作,所有操作都至少有一个参数(第一个即绑定参数)。每个参数必须满足:

  • 每个参数必须具有简单的标识符名称(simple identifier name);
  • 参数名在同一个重载内必须唯一;
  • 参数必须指定类型。

4. 可选参数优于"为缺省参数建重载"

Microsoft Graph 支持可选参数(optional parameters)。当需要为某些参数提供默认行为时,可以使用可选参数注解(OptionalParameter annotation)来代替创建多余的函数/动作重载,从而避免元数据膨胀。

5. HTTP 动词强约束

  • API 设计者 MUST 使用 POST 调用动作(actions);
  • API 设计者 MUST 使用 GET 调用函数(functions)。

这一对约束保证客户端对 URL 的语义有稳定预期:POST 表示"执行会产生副作用的行为",GET 表示"无副作用的查询/计算"。

6. 新增必填参数属于破坏性变更

给现有 action 或 function 添加一个新的必填、不可空(not-nullable)参数属于破坏性变更(breaking change),未经符合版本化与弃用规范的过程不允许直接进行。这与 graph/GuidelinesGraph.md 的破坏性变更清单一致——其中明确将"给现有 action 添加未标记 Nullable 的参数"和"给现有 function 添加未标记 Optional 的参数"列为破坏性变更。

破坏性变更的处理须遵循仓库中的 Deprecation guidelines:在 CSDL 中为被弃用元素添加 Org.OData.Core.V1.Revisions 注解(包含 Date、Version、Kind、Description、RemovalDate 字段),网关会在响应中返回 Deprecation 头与 Sunset 头,引导客户端迁移到新版本元素。

实战示例一:转发邮件(Action 的典型用法)

用户想转发一封邮件。这是典型的有副作用、包含复杂服务端业务逻辑的操作,因此建模为异步 action(动作),绑定到 Graph 的 message 实体类型。

POST https://graph.microsoft.com/v1.0/me/messages/AQMkADNkMmMxYzIwLWJkOTItNDczZC1hNmYyLWUwZjk2ZTljMDQyNQBGAAAD1dY5iRo4x0_pEqop6hOrQAcAeGCrbYV1-kiG-z9Rv6yHMgAAAgEJAAAAeGCrbYV1-kiG-z9Rv6yHMgABRxeUKgAAAA==/forward

{
    "comment": "FYI",
    "toRecipients": [
        {
            "emailAddress": {
                "address": "alex.darrow@microsoft.com",
                "name": "Alex Darrow"
            }
        }
    ]
}

响应:

HTTP/1.1 202 Accepted

    "cache-control": "private",
    "client-request-id": "ca2d0416-a2c1-05af-df60-0921547a86e9",
    "content-length": "0",
    "request-id": "8b53016f-cc2b-4d9f-9818-bd6f0a5e3cd0"

注意响应为 202 Accepted:forward 是一个异步 action,请求被接受但执行尚未完成。这正是它与 Long running operations 模式 的结合点——长耗时操作不应阻塞客户端,可返回 202 并配合 Location 头提供可轮询的操作资源。

对应的 CSDL 元数据定义(<Action> 元素):

<Action Name="forward" IsBound="true">
        <Parameter Name="bindingParameter" Type="graph.message" />
        <Parameter Name="ToRecipients" Type="Collection(graph.recipient)" />
        <Parameter Name="Message" Type="graph.message" />
        <Parameter Name="Comment" Type="Edm.String" Unicode="false" />
</Action>

要点解析:

  • IsBound="true" 声明这是绑定操作;
  • 第一个参数 bindingParameter 类型为 graph.message,表明该动作只能被调用在 message 实体上——这正是"绑定操作的第一个参数永远是绑定参数"的落地形态;
  • 其余参数分别对应请求体中的 toRecipients、comment 等,Comment 声明为 Edm.String 且 Unicode="false"。

实战示例二:查看最近应用活动(Function 的典型用法)

用户想查看最近的应用活动。recent 操作会查询最近的 historyItems,再拉取关联的活动记录——服务端存在复杂处理逻辑,但该操作不改变任何服务端数据,因此非常适合建模为函数(function),绑定到 userActivity 实体类型的集合上。

GET https://graph.microsoft.com/v1.0/me/activities/recent

响应:

HTTP/1.1 200 OK

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(userActivity)",
    "value": []
}

对应的 CSDL 元数据定义(<Function> 元素):

<Function Name="recent" EntitySetPath="activities" IsBound="true">
        <Parameter Name="bindingParameter" Type="Collection(graph.userActivity)" />
        <ReturnType Type="Collection(graph.userActivity)" />
</Function>

要点解析:

  • 调用动词是 GET,符合"MUST 使用 GET 调用函数"的规则;
  • IsBound="true" + 绑定参数类型 Collection(graph.userActivity),表明该函数绑定在 userActivity 集合上(而非单个实体),因此 URL 是 /me/activities/recent;
  • EntitySetPath="activities" 声明返回实体集路径,帮助客户端推导返回集合的语义;
  • <ReturnType> 声明返回 userActivity 类型的集合,与"函数必须返回单个实例或实例集合"的要求一致;
  • 对比动作示例可见:函数强调只读、有明确返回值;动作强调副作用、可异步。

实战示例三:获取浏览器活跃用户统计报表(受限参数 + 流式返回)

获取一份报告,统计使用 Microsoft Edge 的活跃用户数。getBrowserUserCounts 操作不改变任何服务端数据,适合建模为函数;同时它的 period 参数传达受限制的选项集合——报表聚合的天数仅支持 7、30、90、180 天。此外,该函数不返回 Graph 资源,而是以 JSON 或 CSV 格式流式返回响应数据。

https://graph.microsoft.com/beta/reports/getBrowserUserCounts(period='D7')

响应:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 205

{
   "value":[
      {
         "reportRefreshDate":"2021-04-17",
         "reportPeriod":7,
         "userCounts":[
            {
               "reportDate":"2021-04-17",
               "edge":413
            },
            {
               "reportDate":"2021-04-16",
               "edge":883
            }
         ]
      }
   ]
}

对应的 CSDL 元数据定义:

<Function Name="getBrowserUserCounts" IsBound="true">
        <Parameter Name="reportRoot" Type="graph.reportRoot" />
        <Parameter Name="period" Type="Edm.String" Nullable="false" Unicode="false" />
        <ReturnType Type="Edm.Stream" Nullable="false" />
</Function>

要点解析:

  • 绑定参数类型为 graph.reportRoot(报表根实体),因此该函数以 https://graph.microsoft.com/beta/reports/... 为基路径,通过括号语法 (period='D7') 传入参数;
  • period 声明为 Nullable="false" 的 Edm.String,服务端仅接受 7/30/90/180 四个取值——这正是"操作参数传达受限选项集合"的元数据体现;
  • ReturnType 为 Edm.Stream 且 Nullable="false":返回值不是 Graph 实体,而是字节流(JSON 或 CSV),客户端需按流式数据消费;
  • 该示例同时展示了函数绑定到非集合实体(单个 reportRoot)的写法,与示例二绑定到集合的写法互为补充。

与仓库其他设计模式的关联与边界

Operations 模式并非孤立存在,设计时应与模式库中的相邻模式协同判断:

  • Long running operations 模式:当动作是异步的(如示例一返回 202),其执行状态追踪可交给 LRO 模式——返回 Location 头指向操作资源(stepwise operation),客户端轮询直到 succeeded 或 failed,还可用 Retry-After 头建议等待时长。Microsoft Graph 不允许租户级操作资源,stepwise 操作通常建模为目标资源上的导航属性。
  • Change tracking 模式:delta 能力本身就是一个绑定函数——例如 <Function Name="delta" IsBound="true"> 绑定到 Collection(graph.user) 并配合 Org.OData.Capabilities.V1.ChangeTracking 注解。这与本模式的"函数必须是绑定操作"规则完全一致,可作为复杂函数的进阶示例。
  • Upsert 模式 与 Alternate key 模式:当操作需要以客户端提供的键(uniqueName 等替代键)定位资源时,可参考这两个模式补充幂等创建与按替代键寻址的能力。
  • Namespace 模式:CSDL 示例中的 graph.message、graph.userActivity 中的 graph 即命名空间别名——所有公开命名空间必须带 microsoft.graph. 前缀,类型用"别名 + . + 类型名"的全限定方式引用。

设计检查清单

设计任何新的 Microsoft Graph 操作资源时,可用以下清单自检(全部源自 graph/patterns/operations.md 与 graph/GuidelinesGraph.md):

  1. 能不用就不用:若能用资源 + 标准 HTTP 方法表达,优先资源化设计;操作资源仅用于 HTTP 方法无法表达语义的场景;
  2. 选型:无副作用且必须返回单个实例或集合 → Function(GET);否则 → Action(POST);
  3. 绑定:一律 IsBound="true",第一个参数必须是绑定参数,绑定参数类型与被调资源匹配;
  4. 参数:简单标识符、重载内唯一、必须指定类型;能用可选参数就避免无谓重载;
  5. 演进:新增必填不可空参数是破坏性变更,必须走 graph/articles/deprecation.md 的 Revisions 注解与弃用流程;
  6. 时长:预期 99 分位超过 1 秒的操作,应叠加 Long running operations 模式 异步化处理。
登录后查看全文
api-guidelines