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

Data model for CosmosDB SQL API for limited event registration

我正在尝试创建一个网站,允许某些人创建活动(标题、日期、时间等),而其他人则可以注册和注销这些活动。活动的创建者可以指定最大参加人数(例如烧烤最多 10 人)(大多数时间在 10 到 500 人之间)。

我也有一些要求(从“必须有”到“最好有”):

我决定尝试一下 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",
        ]
    }
    
  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。即使在额外存储当前参加者人数时,我们也需要确保两个连续的“注册”事件仅计为一个。
  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。这意味着您创建了两个模型(ReadModelWriteModel),一个用于编写,其中包含您需要的所有数据,并且编辑它应该非常容易。

当您保存 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": ... }
    ]
}

我希望这能回答您的问题。编码愉快!