摘要
在本文中,我們將探討為什么我們在 .NET 中可能需要可排序的唯一ID,以及如何使用 NewId NuGet 包來創(chuàng)建它們。
原文 Generate Sortable Unique IDs With the NewId Library in .NET 由 Ivan Gechev 撰寫。
在本文中,我們將探討為什么我們在 .NET 中可能需要可排序的唯一ID,以及如何使用 NewId NuGet 包來創(chuàng)建它們。
要下載本文的源代碼,您可以訪問我們的 GitHub 倉庫。
為什么我們在 .NET 中需要可排序的唯一ID
我們都知道,生成項目中實體的ID有兩種主要方法:要么是 int
,要么是 Guid
(全局唯一標(biāo)識符)值。但這兩種方法都有它們的問題。讓我們在一個處理客戶訂單的 API 中探討一下。
整數(shù)作為主鍵
首先,讓我們看看整數(shù)作為主鍵:
public class Order
{
public int Id { get; set; }
public required string CustomerName { get; set; }
public required IEnumerable<string> Products { get; set; }
public required decimal TotalAmount { get; set; }
}
我們創(chuàng)建了一個 Order
類并使用 int
作為ID。
采用這種方法,我們將ID生成工作交給數(shù)據(jù)庫提供商。好處是我們得到了短小、美觀、連續(xù)的ID。但,使用數(shù)據(jù)庫生成的主鍵,我們遇到一個主要缺點 —— 我們的應(yīng)用程序變得更難擴(kuò)展。處理大量的插入語句迫使我們的數(shù)據(jù)庫不斷使用鎖來處理生成新的主鍵。
在幾個不同的數(shù)據(jù)庫中存儲實體并維護(hù)唯一的 int
ID也幾乎是不可能的。另外,我們可能會向我們的競爭對手泄露敏感信息 - 我們是否希望擁有連續(xù)的ID并讓別人確切知道我們有多少訂單?
全局唯一標(biāo)識符作為主鍵
另一種流行的方法是使用 Guid
值:
public class OrderService(IUnitOfWork unitOfWork) : IOrderService
{
public async Task<OrderDto> CreateAsync(
OrderForCreationDto orderForCreationDto,
CancellationToken cancellationToken = default)
{
var order = new Order
{
Id = Guid.NewGuid(),
CustomerName = orderForCreationDto.CustomerName,
Products = orderForCreationDto.Products,
TotalAmount = orderForCreationDto.TotalAmount,
};
unitOfWork.OrderRepository.Insert(order);
await unitOfWork.SaveChangesAsync(cancellationToken);
return new OrderDto
{
Id = order.Id,
CustomerName = order.CustomerName,
Products = order.Products,
TotalAmount = order.TotalAmount,
};
}
// 省略以簡潔
}
我們首先將 Order
的標(biāo)識從 int
變更為 Guid
,然后我們創(chuàng)建了 OrderService
類。我們的項目基于 Onion 架構(gòu),所以我們的服務(wù)使用一個 Dto 對象,處理它,并使用倉庫將實體插入數(shù)據(jù)庫。
采用這種方法,OrderService
類負(fù)責(zé)生成主鍵值。最大的好處在于我們可以使用 Guid.NewGuid()
輕松獲得唯一ID。擁有唯一但隨機(jī)的ID使我們的應(yīng)用擴(kuò)展到幾個數(shù)據(jù)庫變得非常容易。但這也是它們最大的問題:它使我們的數(shù)據(jù)基于主鍵單獨排序變得不可能,并可能導(dǎo)致潛在的索引問題。這種類型的主鍵在數(shù)據(jù)庫中存儲時也會占用四倍于常規(guī)整數(shù)的空間。
這就是 NewId 庫發(fā)揮作用的地方。通過使用它,我們結(jié)合了 int
和 Guid
主鍵的優(yōu)點,同時消除了一些缺點。
NewId 庫是什么
NewId 庫是一個 NuGet 包,我們可以用它來生成唯一但可排序的ID。 它基于現(xiàn)已退役的 Snowflake:X(前 Twitter)內(nèi)部服務(wù),用于生成可排序的唯一主鍵。NewId 是分布式應(yīng)用框架 MassTransit 的一部分,旨在解決 int
和 Guid
標(biāo)識符存在的問題。其目的是提供一種在大規(guī)模下生成唯一且可排序ID的方法。
該包基于三個事物生成ID - 時間戳、工作ID和進(jìn)程ID。這樣我們最終得到的是雖然唯一但仍可排序的ID,并且當(dāng)我們的應(yīng)用和數(shù)據(jù)庫有多個實例時不會發(fā)生沖突。
這個材料對你有用嗎?考慮訂閱并免費獲取 ASP.NET Core Web API 最佳實踐 電子書!
在我們開始生成ID之前,我們需要安裝 NewId
包:
dotnet add package NewId
使用 dotnet add package
命令,我們安裝了這個庫。
現(xiàn)在我們已經(jīng)準(zhǔn)備好了,讓我們開始使用這個包來生成ID。
要生成我們的可排序唯一ID,我們需要使用 NewId
類。它位于 MassTransit
命名空間中,有三個方法。
首先是 Next()
方法 - 生成一個新的 NewId
類實例:
00070000-ac11-0242-3d9b-08dc45bed613
00070000-ac11-0242-c9d3-08dc45bed614
00070000-ac11-0242-cafd-08dc45bed614
00070000-ac11-0242-cb2e-08dc45bed614
00070000-ac11-0242-cb69-08dc45bed614
接下來,NextGuid()
方法 - 生成一個新的 Guid
值:
00070000-ac11-0242-df20-08dc45bed614
00070000-ac11-0242-0c11-08dc45bed615
00070000-ac11-0242-0d30-08dc45bed615
00070000-ac11-0242-0d46-08dc45bed615
00070000-ac11-0242-0d58-08dc45bed615
最后,NextSequentialGuid()
方法 - 生成一個新的順序 Guid
值:
08dc45be-d615-19b5-0242-ac1100070000
08dc45be-d615-1b01-0242-ac1100070000
08dc45be-d615-1b46-0242-ac1100070000
08dc45be-d615-1b66-0242-ac1100070000
08dc45be-d615-1ba4-0242-ac1100070000
我們可以看到,使用 Next()
和 NextGuid()
方法,我們得到相同的模式,其中 NextSequentialGuid()
方法有稍微不同的模式。后兩種方法返回一個 Guid
值,我們的類不需要修改,但如果我們選擇 Next()
方法,我們將需要更改我們 Order
類的ID類型。
讓我們使用其中一個:
public class OrderService(IUnitOfWork unitOfWork) : IOrderService
{
public async Task<OrderDto> CreateAsync(
OrderForCreationDto orderForCreationDto,
CancellationToken cancellationToken = default)
{
var order = new Order
{
Id = NewId.NextSequentialGuid(),
CustomerName = orderForCreationDto.CustomerName,
Products = orderForCreationDto.Products,
TotalAmount = orderForCreationDto.TotalAmount,
};
unitOfWork.OrderRepository.Insert(order);
await unitOfWork.SaveChangesAsync(cancellationToken);
return new OrderDto
{
Id = order.Id,
CustomerName = order.CustomerName,
Products = order.Products,
TotalAmount = order.TotalAmount,
};
}
// 省略以簡潔
}
在我們的 OrderService
類中,我們生成ID的地方,我們用 NewId.NextSequentialGuid()
方法替換了 Guid.NewGuid()
方法。這是我們唯一需要做的改變。
讓我們運行我們的 API 并添加一些訂單:
[
{
"id": "08dc45c0-b5b5-0d5d-5811-22b038790000",
"customerName": "Marcel Waters",
"products": ["Piano"],
"totalAmount": 599.99
},
{
"id": "08dc45c0-d6f4-fbed-5811-22b038790000",
"customerName": "Elizabeth Doyle",
"products": ["Vase", "Mirror", "Blanket"],
"totalAmount": 49.39
},
{
"id": "08dc45c0-f143-a9f9-5811-22b038790000",
"customerName": "Rayford Lopez",
"products": ["Headphones", "Microphone"],
"totalAmount": 86.06
}
]
我們可以看到,我們的實體現(xiàn)在擁有唯一但不完全隨機(jī)的標(biāo)識符,這些標(biāo)識符可以排序。
何時不應(yīng)使用 NewId 庫生成可排序的唯一ID
雖然 NewId 庫為我們提供了生成可排序唯一ID的便利,但在一些場景下我們應(yīng)該避免使用它們。至關(guān)重要的是要記住,我們生成的ID具有一定程度的可預(yù)測性。當(dāng)你知道它們的創(chuàng)建算法時,它們可以被猜測。
因此,當(dāng)不可預(yù)測性和安全性至關(guān)重要時,最好不要使用 NewId 生成的ID。我們不應(yīng)該在需要高度保密性的敏感信息如密碼、安全令牌或任何其他數(shù)據(jù)上使用這樣的ID。如果我們在這樣的場景下依賴 NewId 庫,我們可能會危及我們應(yīng)用程序的安全。這可能會使它們暴露于漏洞和未經(jīng)授權(quán)的訪問中。因此,當(dāng)我們的應(yīng)用程序在安全上有重要需求時,需要格外小心。
結(jié)論
在本文中,我們探討了在作為主鍵時,整數(shù)和全局唯一標(biāo)識符的限制,以及需要一種新方法的原因。NewId NuGet 包為我們提供了一個解決方案,提供了基于時間戳、工作ID和進(jìn)程ID組合的唯一、可排序的ID。通過使用 NewId 庫,我們獲得了 int 和 Guid 主鍵的好處,同時減少了它們的缺點。這使我們能夠輕松實現(xiàn)可擴(kuò)展性、有效的索引,并提高我們的數(shù)據(jù)組織。所有這些最終增強(qiáng)了我們的應(yīng)用程序的健壯性和功能。
該文章在 2024/4/9 22:21:59 編輯過