我试图创建一个网站,允许一些人创建事件(标题,日期,时间等)和其他人注册,也从这些事件注销。活动的创建者可以指定最大参加人数(例如,烧烤最多10人)(大多数情况下在10到500人之间)。
我也有一些要求(从"must have"到"nice To have"):
- 事务安全性:不可能超过与会者的最大数量
- 允许增加或减少最大出席人数(只要
max number of attendees > current number of attendees
) - 查询当前出席人数应该是便宜的(例如"4/10")
- 查询每个用户的事件应该是便宜的(例如用户U注册了事件E1, E2和E3)
- 避免存储冗余数据
我决定给CosmosDB和它的SQL API一个尝试。我设置了一个"事件"用于存储事件的容器。现在我正在考虑将事件注册存储在我看到以下可能性的地方,它们都有各自的优点和缺点:
-
在事件本身中包含注册,例如
{ "id": "b21e28e9-61c6-454a-8438-4a75e74a854b", "title": "BBQ", "date": "2022-05-17", "time": "17:00", "maxAttendees": 10, "attendeeIds": [ "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "1ad88926-037d-4bf0-b50c-b380f3f5fa9f", ] }
- 优势简单
- 考虑
maxAttendees
的注册可以使用补丁和条件更新(https://learn.microsoft.com/en-us/azure/cosmos-db/partial-document-update)使其事务性
- 考虑
- 缺点
- 注销只能使用
attendeeIds
的数组索引(再次使用补丁和条件更新,以确保索引指向正确的项目,这可能工作,但感觉有点脏)
- 注销只能使用
- 优势简单
-
在事件本身中存储注册事件(类似于事件存储),例如
{ "id": "b21e28e9-61c6-454a-8438-4a75e74a854b", "title": "BBQ", "date": "2022-05-17", "time": "17:00", "maxAttendees": 10, "attendanceEvents": [ { "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "type": "register" }, { "userId": "1ad88926-037d-4bf0-b50c-b380f3f5fa9f", "type": "register" }, { "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "type": "deregister" }, { "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "type": "register" } ] }
- 优势
- 注销更容易,因为它只是另一个添加的操作
- 缺点
- 不确定我们如何允许或拒绝注册用户,因为要获得当前的与会者数量,我们需要考虑所有
attendanceEvents
。即使在额外存储当前与会者数量时,我们也需要确保两个连续的"register"。事件只算作一个。
- 不确定我们如何允许或拒绝注册用户,因为要获得当前的与会者数量,我们需要考虑所有
- 优势
-
在另一个容器中存储注册(例如EventRegistrations")(与典型的SQL数据库一样):
- 事件
{ "id": "b21e28e9-61c6-454a-8438-4a75e74a854b", "title": "BBQ", "date": "2022-05-17", "time": "17:00", "maxAttendees": 10, "currentAttendees": 3 // Optional, simplifies displaying available slots }
- 登记
{ "id": "6b5beddc-24be-4ddf-9171-7a680093870f", // optionally eventId and userId concatenated "eventId": "b21e28e9-61c6-454a-8438-4a75e74a854b", "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357" }
- 优势
- 添加和删除注册很容易
- 查询每个用户的事件更容易
- 缺点
- 添加注册不能做事务性的,因为我不能读取
maxAttendees
并一次写入注册(对吗?)
- 添加注册不能做事务性的,因为我不能读取
- 事件
谁能想到一种方法来减轻上述方法的缺点?或者有人能想出一个完全不同的方法吗?
我脑海中出现的第一件事可能是考虑模式:Command/Query
。这意味着您创建了两个模型(ReadModel
,WriteModel
),其中一个用于编写包含您需要的所有数据,并且编辑它应该非常容易。
当您保存WriteModel
时,您可以制作一个称为ReadModel
的蒸馏版本并保存它,以便快速使用。指数高,结构扁平。这将大大提高性能。
类似:
WriteModel
:
{
"id": "b21e28e9-61c6-454a-8438-4a75e74a854b",
"title": "BBQ",
"date": "2022-05-17",
"time": "17:00",
"maxAttendees": 10,
"attendanceEvents": [
{ "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "type": "register" },
{ "userId": "1ad88926-037d-4bf0-b50c-b380f3f5fa9f", "type": "register" },
{ "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "type": "deregister" },
{ "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "type": "register" }
]
}
在模型(在我的情况下c#),你可以使所有的验证
public class EventModel
{
public string Id {get;set;}
public string Title {get;set;}
public DateTime Date {get;set;}
public int MaxAttendees {get;set;}
public List<EventUserModel> EventUsers {get; private set;}
public void AddUser(string id, string type)
{
if (this.EventUsers.Count >= this.MaxAttendees) {
return;
}
this.EventUsers.Add(new EventUserModel() { Id = id, Type =type });
}
}
public class EventUserModel {
public string UserId {get;set;}
public string Type {get;set;}
}
ReadModel
看起来像这样:
public class EventReadModel
{
private int _currentAttendees;
public string Id {get;set;}
public string Title {get;set;}
public DateTime Date {get;set;}
public int MaxAttendees {get;set;}
public int CurrentAttendees
{
get => this._currentAttendees;
set => _currentAttendees = this.EventUsers.Count;
}
public List<EventUserReadModel> EventUsers {get; set;}
}
public class EventUserReadModel {
public string UserId {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public string Email {get;set;}
public string Type {get;set;}
}
ReadModel
将用于查询数据(Select),并将在每次更新模型时更新。
{
"id": "b21e28e9-61c6-454a-8438-4a75e74a854b",
"title": "BBQ",
"date": "2022-05-17",
"time": "17:00",
"maxAttendees": 10,
"currentAttendees": 3,
"attendanceEvents": [
{ "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "type": "register", "firstName": ... },
{ "userId": "1ad88926-037d-4bf0-b50c-b380f3f5fa9f", "type": "register", "firstName": ... },
{ "userId": "5b5a6b75-4b4e-4824-8a7b-c1d9c7783357", "type": "deregister", "firstName": ... }
]
}
我希望这能回答你的问题。编码快乐!