概念概述
工作单元(Unit of Work)指的是:工作单元跟踪业务事务期间的变更并在一次协调提交中写出,集中处理事务边界与并发问题。
从更严格的定义看,A unit of work maintains a list of objects affected by a business transaction and coordinates the writing of those changes and the resolution of concurrency problems in a single commit. 该概念在工程实践中的价值,在于它能帮助团队用统一的语言描述一个具体的质量属性或设计约束,从而在评审与协作中减少歧义。
为什么需要它
It is used to group several repository operations into one atomic transaction and to defer writes until the end of a request. 它与 Publish-Subscribe Messaging、Message Broker、Layered Architecture 等概念经常一起出现,用于解决相近的工程问题。理解它的适用场景,有助于在多种备选方案之间做出有依据的取舍,而不是凭直觉选择技术方案。
常见误解
- a unit of work is just a database transaction wrapper
- changes are written as soon as they occur
需要避免的反模式
- committing inside each repository call and losing atomicity
- keeping one unit of work open across a whole long-lived process
实践要点
- Changes accumulate in memory and are flushed at commit, so several aggregates can be persisted together and either all succeed or all roll back.
- It centralises concurrency handling, such as optimistic version checks, rather than scattering those checks across individual writes.
- A unit of work is scoped to a transaction and should live no longer than that, since a long-lived one accumulates state and holds connections unnecessarily.
参考来源
https://martinfowler.com/eaaCatalog/unitOfWork.html
代码示例
interface VersionedRow {
readonly key: string;
readonly value: string;
readonly startTs: number;
readonly endTs: number;
}
export class MvccStore {
private versions: VersionedRow[] = [];
private clock = 0;
beginTransaction(): number {
return ++this.clock;
}
write(key: string, value: string, ts: number): void {
for (const row of this.versions) {
if (row.key === key && row.endTs === Number.POSITIVE_INFINITY) row.endTs = ts;
}
this.versions.push({ key, value, startTs: ts, endTs: Number.POSITIVE_INFINITY });
}
read(key: string, snapshotTs: number): string | undefined {
let found: VersionedRow | undefined;
for (const row of this.versions) {
if (row.key !== key) continue;
if (row.startTs <= snapshotTs && snapshotTs < row.endTs) {
if (!found || row.startTs > found.startTs) found = row;
}
}
return found?.value;
}
}