循序渐进 · 教学 · TypeScript v2(五)

Staged writes:带读后写保证的编辑(Beta)

Staged writes 是更强的编辑模式:写入后立刻能读到、不必在结尾返回编辑、整体原子执行。目前处于 Beta 阶段。

全部目录 ← 上一篇 Staged writes:带读后写保证的编辑(Beta) 下一篇 →
本文来源 · Source 内容整理自 Palantir Foundry 官方文档:
https://www.palantir.com/docs/foundry/functions/typescript-v2-staged-writes/
原始标题:TypeScript v2 > Staged writes

先记住这几条

① 读后写保证
写进去之后,同一函数里马上能读到新值 —— 普通编辑做不到。
② 不用在结尾返回编辑
写法上更接近命令式,编辑在函数内直接生效。
③ 原子执行
整段要么全成,要么全回滚。
④ 用 WriteableClient
需要一个可写客户端,这是入口。
⑤ 目前是 Beta
功能可能变动,且不一定在你的环境开放。
0

写在前面

:::callout{theme="neutral" title="Beta"}

暂存写入(staged writes)处于测试(beta)开发阶段,在你的租户中可能不可用。功能在积极开发期间可能发生变化。请联系 Palantir 支持以申请访问。

实时预览与已发布函数预览仅在 Code Repositories 中支持,而非在本地开发或 VS Code 工作区环境中。

本页介绍 TypeScript v2 函数中的暂存写入。对于 Python,请参阅 Python 函数中的暂存写入

暂存写入为编辑 Ontology 中对象的函数提供了一种额外的执行模型。与通过常规 Ontology 编辑函数 所做的编辑不同,暂存写入函数:

  • 为函数中应用的 Ontology 编辑提供「写后可读」保障。函数内应用的所有编辑都会被暂存,并反映在函数后续对 Ontology 的查询与聚合中。
  • 允许嵌套调用其他暂存写入函数来进行 Ontology 编辑。

本页展示了如何编写暂存写入函数,并记录了它们的独特属性。关于编辑函数如何工作的更多细节,请参阅概览页

1

与普通编辑函数的差异

与普通编辑函数的差异

要点:读后写保证、无需返回编辑、原子执行、WriteableClient —— 四条核心差异。

暂存写入函数与常规编辑函数在几个重要方面有所不同。

Read-after-write guarantee

在暂存写入函数内,任何对 Ontology 数据的读取都会反映函数先前所做的所有编辑(以及在同一次动作执行中,调用方暂存写入函数所做的所有编辑)。这些编辑仅被暂存,在由其他用户发起的查询中、或在本次执行上下文之外的函数中都不可见。这让你可以使用搜索请求与聚合,从函数内部查询 Ontology,而所有暂存的编辑都会在查询结果中得到反映。

No requirement to return edits at the end of the function

常规的 Python 与 TypeScript v2 Ontology 编辑函数要求将一批 Ontology 编辑作为函数的返回值返回,这些编辑才会被应用。在暂存写入函数中,Ontology 编辑会被自动暂存,并在函数执行结束时、动作完成时应用到 Ontology。这释放了函数的返回值,使其可以向调用方返回其他信息。

例如,你可以应用一个动作,它执行一个 TypeScript v2 暂存写入函数,该函数随后做一些编辑并进一步调用一个 AIP Logic 函数。AIP Logic 函数所做的查询会返回 TypeScript v2 函数中对 Ontology 所做的更改;AIP Logic 函数所做的任何额外编辑都会并入同一批暂存编辑,而无需作为 Logic 函数返回值的一部分返回。一旦动作完成,所有暂存编辑都会被自动应用。

Atomic execution

暂存写入函数内的所有操作,包括查询、函数调用与 AIP Logic 执行,都会将其编辑暂存到一起。这些暂存编辑在函数成功完成后被提交(即应用到 Ontology)。如果函数抛出错误,Ontology 保持未修改状态,所有暂存编辑在动作重试该函数之前被丢弃。

WriteableClient

暂存写入函数使用 WriteableClient 而非标准的 ClientWriteableClient 提供了用于创建、更新和删除对象的直接方法,而无需构造编辑批次。

2

定义 staged-write 函数

定义 staged-write 函数

要点:函数签名与入口写法。

暂存写入函数必须显式声明将要被编辑的实体,使用从 @osdk/functions 包导出的 Edits 类型。第一个参数必须是 WriteableClient<T>,其中 T 是函数将执行的所有编辑类型的并集。返回值不再被限制为编辑数组,因此你可以返回任意值。下面的例子声明了一个将编辑 Employee 对象类型的函数:

import { Employee } from "@ontology/sdk";
import { Edits, Integer } from "@osdk/functions";
import { WriteableClient } from "@osdk/functions/experimental";

type OntologyEdit = Edits.Object<Employee>;

export default async function assignTicket(
    client: WriteableClient<OntologyEdit>,
    employeeId: string,
    ticketId: string
): Promise<Integer> {
    // ...
}
3

创建对象

创建对象

要点:包含用生成 ID 创建的方式。

使用 WriteableClient 上的 create 方法创建新对象。你必须指定对象类型,并为它的主键提供一个值,以及任何你想要初始化的其他属性。

import { Employee } from "@ontology/sdk";
import { Edits } from "@osdk/functions";
import { WriteableClient } from "@osdk/functions/experimental";

type OntologyEdit = Edits.Object<Employee>;

async function createEmployee(
    client: WriteableClient<OntologyEdit>,
    employeeId: string,
    firstName: string,
    lastName: string
): Promise<Integer> {
    await client.create(Employee, {
        employeeId: employeeId,
        firstName: firstName,
        lastName: lastName
    });

    return employeeId;
}

export default createEmployee;

Creating with generated IDs

当你需要生成一个 ID 并立即使用时:

import { Ticket } from "@ontology/sdk";
import { Edits, Integer } from "@osdk/functions";
import { WriteableClient } from "@osdk/functions/experimental";
import { randomUUID } from "crypto";

type OntologyEdit = Edits.Object<Ticket>;

async function createTicket(
    client: WriteableClient<OntologyEdit>,
    title: string
): Promise<string> {
    const ticketId = randomUUID();

    await client.create(Ticket, {
        ticketId: ticketId,
        title: title,
        status: "open"
    });

    return ticketId;
}

export default createTicket;
4

更新对象

更新对象

要点:对象属性的更新。

Object properties

使用 WriteableClient 上的 update 方法修改对象属性:

await client.update(employee, { lastName: newName });

你也可以通过引用对象的 API 名称与主键来更新它:

await client.update({ $apiName: "Employee", $primaryKey: 23 }, { lastName: newName });

暂存写入函数不支持接口编辑。

5

删除对象

删除对象

要点:删除操作。

你可以通过调用 WriteableClient 上的 delete 方法删除一个对象:

await client.delete(ticket);

也可以使用主键而非实例来删除对象:

await client.delete({ $apiName: "Ticket", $primaryKey: 12 });
7

函数内的读后写

函数内的读后写

要点:这是 staged writes 最大的卖点,注意作用域限制。

暂存写入函数的主要优势之一,是能够读取同一次执行中刚刚写入的数据。这对于实现要求即时一致性的工作流很有用。

import { Employee, Ticket } from "@ontology/sdk";
import { Edits, Integer } from "@osdk/functions";
import { WriteableClient } from "@osdk/functions/experimental";
import { randomUUID } from "crypto";

type OntologyEdit = Edits.Object<Ticket> | Edits.Link<Employee, "assignedTickets">;

async function assignTicketAndCheckWorkload(
    client: WriteableClient<OntologyEdit>,
    employeeId: Integer,
    title: string
): Promise<{ ticketId: string, totalAssignedTickets: number }> {
    const ticketId = randomUUID();

    // 创建工单
    await client.create(Ticket, {
        ticketId: ticketId,
        title: title,
        status: "open"
    });

    // 将工单分配给员工
    await client.link(
        { $apiName: "Employee", $primaryKey: employeeId },
        "assignedTickets",
        { $apiName: "Ticket", $primaryKey: ticketId }
    );

    // 查询员工的总工作量,包括新分配的工单。
    // 这之所以可行,是因为「写后可读」保障。
    // where 子句过滤的是对象类型自身的属性,因此要先用 pivotTo() 遍历
    // assignedTickets 链接,再对 Ticket 做过滤。
    const result = await client(Employee)
        .where({ employeeId: { $eq: employeeId } })
        .pivotTo("assignedTickets")
        .where({ status: { $eq: "open" } })
        .aggregate({ $select: { $count: "unordered" } });

    return {
        ticketId: ticketId,
        totalAssignedTickets: result.$count
    };
}

export default assignTicketAndCheckWorkload;
8

调用其他函数

调用其他函数

要点:在函数里再调别的函数时的注意事项。

当你在暂存写入函数内调用另一个函数或查询时,这些操作会参与到同一批暂存编辑中。任何读取都会反映执行中先前暂存的编辑,被调用函数所做的任何编辑都会加入同一批暂存编辑。这适用于:

  • 其他 TypeScript 暂存写入函数
  • AIP Logic 函数
  • Ontology 查询

如果顶层函数成功完成,跨嵌套调用的所有编辑会一起提交。如果任何调用抛出异常,整批会被回滚。

在下面的例子中,assignTicket 是一个独立的暂存写入函数,从同一仓库发布。bulkAssignTickets 通过 OSDK 生成的 $Queries 导入来调用它;每次调用都会将其编辑加入调用函数的同一批暂存编辑中。

import { Employee, Ticket, $Queries } from "@ontology/sdk";
import { Edits, Integer } from "@osdk/functions";
import { WriteableClient } from "@osdk/functions/experimental";

type OntologyEdit = Edits.Object<Ticket> | Edits.Link<Employee, "assignedTickets">;

async function bulkAssignTickets(
    client: WriteableClient<OntologyEdit>,
    employeeId: Integer,
    ticketIds: string[]
): Promise<Integer> {
    let assignedCount = 0;

    for (const ticketId of ticketIds) {
        // 每次对 `assignTicket` 的调用,都将其编辑与调用函数的编辑暂存到一起。
        await client($Queries.assignTicket).executeFunction({
            employeeId: employeeId,
            ticketId: ticketId,
        });

        assignedCount++;
    }

    // 所有嵌套调用中的暂存编辑将在顶层函数完成时一起提交
    return assignedCount;
}

export default bulkAssignTickets;
9

执行生命周期

执行生命周期

要点:理解执行的各个阶段,便于排查。

理解暂存编辑何时被提交,对于构建可靠的函数很重要:

  1. 函数执行: 所有操作(创建、更新、删除、读取、嵌套函数调用)都被暂存到 Ontology。它们对函数自身及所有嵌套函数可见,但在提交之前不会出现在当前执行之外。
  2. 提交: 如果函数成功完成,所有暂存编辑会在动作结束前被提交。
  3. 错误时回滚: 如果函数抛出异常,Ontology 保持未修改状态,所有暂存编辑被丢弃。然后函数由动作重试。
import { Employee } from "@ontology/sdk";
import { Edits, Integer } from "@osdk/functions";
import { WriteableClient } from "@osdk/functions/experimental";

async function updateEmployeeWithValidation(
    client: WriteableClient<Edits.Object<Employee>>,
    employeeId: Integer,
    newSalary: number
): Promise<Integer> {
    // 校验输入
    if (newSalary < 0) {
        // 暂存编辑将被丢弃
        throw new Error("Salary cannot be negative");
    }

    // 更新员工
    await client.update(
        { $apiName: "Employee", $primaryKey: employeeId },
        { salary: newSalary }
    );

    // 如果我们能执行到这里,所有暂存编辑将被原子化提交
}

export default updateEmployeeWithValidation;

延伸阅读 · 相关页面

按主题横向跳转,不必顺着目录一篇篇读。

常见问题速答 · FAQ

关于「Staged writes:带读后写保证的编辑(Beta)」,读者最常问的几个问题。

与普通编辑函数的差异是什么?
读后写保证、无需返回编辑、原子执行、WriteableClient —— 四条核心差异。
定义 staged-write 函数是什么?
函数签名与入口写法。暂存写入函数必须显式声明将要被编辑的实体,使用从 @osdk/functions 包导出的 Edits 类型。第一个参数必须是 WriteableClient<T>,其中 T 是函数将执行的所有编辑类型的并集。
创建对象是什么?
包含用生成 ID 创建的方式。使用 WriteableClient 上的 create 方法创建新对象。你必须指定对象类型,并为它的主键提供一个值,以及任何你想要初始化的其他属性。
更新对象是什么?
对象属性的更新。使用 WriteableClient 上的 update 方法修改对象属性。