用于有限事件注册的CosmosDB SQL API的数据模型



我试图创建一个网站,允许一些人创建事件(标题,日期,时间等)和其他人注册,也从这些事件注销。活动的创建者可以指定最大参加人数(例如,烧烤最多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一个尝试。我设置了一个"事件"用于存储事件的容器。现在我正在考虑将事件注册存储在我看到以下可能性的地方,它们都有各自的优点和缺点:

  1. 在事件本身中包含注册,例如

    {
    "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的数组索引(再次使用补丁和条件更新,以确保索引指向正确的项目,这可能工作,但感觉有点脏)
  2. 在事件本身中存储注册事件(类似于事件存储),例如

    {
    "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"。事件只算作一个。
  3. 在另一个容器中存储注册(例如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": ... }
]
}
我希望这能回答你的问题。编码快乐!

最新更新