用 Prisma 持久化领域模型的实战策略
在领域驱动设计(DDD)落地过程中,把聚合根、实体和值对象映射到关系型数据库时,最常见的两个难题是:
- 聚合根 ID 在业务层已生成,导致无法区分"新增"还是"更新"。
- 值对象被拆到独立表,却不得不为其分配主键,破坏了"无身份"语义。
下面演示如何用 Prisma 在 Node.js/TypeScript 项目中优雅解决这两个问题。
示例模型
class Buyer {
constructor(
public readonly uuid: string,
public name: string,
public purchases: Purchase[]
) {}
}
class Purchase {
constructor(
public readonly uuid: string,
public buyerId: string,
public amount: number,
public lines: PurchaseLine[]
) {}
}
class PurchaseLine {
constructor(
public sku: string,
public qty: number,
public unitPrice: number
) {}
}
难题 1:新增还是更新?
由于 uuid 在领域层已生成,传统 if (id) update else create 逻辑永远走不到 else 分支。
Prisma 的 upsert 可以把"写"统一为"保存":
async function persistBuyer(buyer: Buyer) {
await prisma.buyer.upsert({
where: { uuid: buyer.uuid },
update: { name: buyer.name },
create: { uuid: buyer.uuid, name: buyer.name },
});
}
当聚合较复杂时,upsert 的 create 与 update 分支会膨胀。可用"先删后建"模式拆分:
async function persistPurchase(purchase: Purchase) {
const data = {
uuid: purchase.uuid,
buyerId: purchase.buyerId,
amount: purchase.amount,
};
await prisma.$transaction([
prisma.purchaseLine.deleteMany({ where: { purchaseId: purchase.uuid } }),
prisma.purchase.upsert({
where: { uuid: purchase.uuid },
update: data,
create: {
...data,
lines: {
createMany: {
data: purchase.lines.map(l => ({
sku: l.sku,
qty: l.qty,
unitPrice: l.unitPrice,
})),
},
},
},
}),
]);
}
难题 2:值对象被表化
PurchaseLine 没有身份,却因表结构需要主键。为避免领域层感知数据库细节,采用"事务包裹的级联写":
- 在事务里先删除该聚合下的所有值对象记录。
- 用
createMany批量插入新的值对象。 - 聚合根用
upsert保存。
这样值对象表的主键对领域层完全透明。
完整事务示例
async function saveFullPurchase(purchase: Purchase) {
await prisma.$transaction(async tx => {
await tx.purchaseLine.deleteMany({ where: { purchaseId: purchase.uuid } });
await tx.purchase.upsert({
where: { uuid: purchase.uuid },
update: {
amount: purchase.amount,
},
create: {
uuid: purchase.uuid,
buyerId: purchase.buyerId,
amount: purchase.amount,
},
});
await tx.purchaseLine.createMany({
data: purchase.lines.map(l => ({
purchaseId: purchase.uuid,
sku: l.sku,
qty: l.qty,
unitPrice: l.unitPrice,
})),
});
});
}
通过 upsert 与事务级联写,领域模型保持纯净,持久化逻辑也足够简洁。