Staged writes:带读后写保证的编辑(Beta)
Staged writes 是更强的编辑模式:写入后立刻能读到、不必在结尾返回编辑、整体原子执行。目前处于 Beta 阶段。
https://www.palantir.com/docs/foundry/functions/typescript-v2-staged-writes/
原始标题:TypeScript v2 > Staged writes
先记住这几条
写在前面
:::callout{theme="neutral" title="Beta"}
暂存写入(staged writes)处于测试(beta)开发阶段,在你的租户中可能不可用。功能在积极开发期间可能发生变化。请联系 Palantir 支持以申请访问。
实时预览与已发布函数预览仅在 Code Repositories 中支持,而非在本地开发或 VS Code 工作区环境中。
本页介绍 TypeScript v2 函数中的暂存写入。对于 Python,请参阅 Python 函数中的暂存写入。
暂存写入为编辑 Ontology 中对象的函数提供了一种额外的执行模型。与通过常规 Ontology 编辑函数 所做的编辑不同,暂存写入函数:
- 为函数中应用的 Ontology 编辑提供「写后可读」保障。函数内应用的所有编辑都会被暂存,并反映在函数后续对 Ontology 的查询与聚合中。
- 允许嵌套调用其他暂存写入函数来进行 Ontology 编辑。
本页展示了如何编写暂存写入函数,并记录了它们的独特属性。关于编辑函数如何工作的更多细节,请参阅概览页。
与普通编辑函数的差异
与普通编辑函数的差异
暂存写入函数与常规编辑函数在几个重要方面有所不同。
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 而非标准的 Client。WriteableClient 提供了用于创建、更新和删除对象的直接方法,而无需构造编辑批次。
定义 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> {
// ...
}创建对象
创建对象
使用 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;更新对象
更新对象
Object properties
使用 WriteableClient 上的 update 方法修改对象属性:
await client.update(employee, { lastName: newName });你也可以通过引用对象的 API 名称与主键来更新它:
await client.update({ $apiName: "Employee", $primaryKey: 23 }, { lastName: newName });暂存写入函数不支持接口编辑。
删除对象
删除对象
你可以通过调用 WriteableClient 上的 delete 方法删除一个对象:
await client.delete(ticket);也可以使用主键而非实例来删除对象:
await client.delete({ $apiName: "Ticket", $primaryKey: 12 });创建或删除链接
创建或删除链接
使用 WriteableClient 上的 link 与 unlink 方法,在对象之间添加或移除多对多链接:
// 将工单分配给员工
await client.link(employee, "assignedTickets", ticket);
// 解除员工与工单的分配
await client.unlink(employee, "assignedTickets", ticket);你也可以通过 API 名称与主键引用链接的任意一侧:
await client.link(
{ $apiName: "Employee", $primaryKey: 23 },
"assignedTickets",
{ $apiName: "Ticket", $primaryKey: 12 }
);要编辑一对多链接,请使用创建或更新对象编辑来修改外键属性。
函数内的读后写
函数内的读后写
暂存写入函数的主要优势之一,是能够读取同一次执行中刚刚写入的数据。这对于实现要求即时一致性的工作流很有用。
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;调用其他函数
调用其他函数
当你在暂存写入函数内调用另一个函数或查询时,这些操作会参与到同一批暂存编辑中。任何读取都会反映执行中先前暂存的编辑,被调用函数所做的任何编辑都会加入同一批暂存编辑。这适用于:
- 其他 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;执行生命周期
执行生命周期
理解暂存编辑何时被提交,对于构建可靠的函数很重要:
- 函数执行: 所有操作(创建、更新、删除、读取、嵌套函数调用)都被暂存到 Ontology。它们对函数自身及所有嵌套函数可见,但在提交之前不会出现在当前执行之外。
- 提交: 如果函数成功完成,所有暂存编辑会在动作结束前被提交。
- 错误时回滚: 如果函数抛出异常,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)」,读者最常问的几个问题。