有限事件注册的 CosmosDB SQL API 的数据模型
Data model for CosmosDB SQL API for limited event registration
我正在尝试创建一个网站,允许某些人创建活动(标题、日期、时间等),而其他人则可以注册和注销这些活动。活动的创建者可以指定最大参加人数(例如烧烤最多 10 人)(大多数时间在 10 到 500 人之间)。
我也有一些要求(从“必须有”到“最好有”):
- 交易安全:不应该超过最大参加人数
- 允许增加或减少最大参加人数(只要
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://docs.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
。即使在额外存储当前参加者人数时,我们也需要确保两个连续的“注册”事件仅计为一个。
在另一个容器中存储注册(例如“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": ... }
]
}
我希望这能回答您的问题。编码愉快!
我正在尝试创建一个网站,允许某些人创建活动(标题、日期、时间等),而其他人则可以注册和注销这些活动。活动的创建者可以指定最大参加人数(例如烧烤最多 10 人)(大多数时间在 10 到 500 人之间)。
我也有一些要求(从“必须有”到“最好有”):
- 交易安全:不应该超过最大参加人数
- 允许增加或减少最大参加人数(只要
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://docs.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
。即使在额外存储当前参加者人数时,我们也需要确保两个连续的“注册”事件仅计为一个。
- 不确定我们将如何允许或拒绝注册用户,因为要获得当前的与会者人数,我们需要考虑所有
- 优势
在另一个容器中存储注册(例如“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": ... }
]
}
我希望这能回答您的问题。编码愉快!