在微服务架构中管理与 MongoDb 的关系

Managing relationships with MongoDb in a Microservices architecture

我使用微服务已经有一段时间了,总是使用关系数据库。我正在查看 MongoDb,但我不确定如何处理涉及不同微服务的实体关系。举个例子:

public class Employee implements Serializable {
   private String id;
   ...
}

public class Department implements Serializable {
    private String id;
    private String desc;
    private List<Employee> employees = new ArrayList<>();
    ...
}

这两个实体由两个不同的微服务管理,一对多关系由 Department 实体管理。到目前为止,还不错。

使用关系数据库(是一种可选关系,一名员工可能属于多个部门)我将在 Departments 微服务中将其映射到一个 table 包含两个字段: employee_iddepartment_id。当客户端调用 getDepartmentWithEmployees(depId) 时,此微服务将读取 table 并从 Employees 微服务中获取适当的员工。

但是,据我所知,在 MongoDb 数据库中,当我存储一个 Department 对象时,它会存储所有关联的 Employee。那不是重复信息吗?有没有办法 MongoDb 不存储有关员工的所有信息,而只存储他们的 ID?或者还有别的答案?

我很确定这是一个非常基本的问题,但我是新手。

提前致谢。

But, in a MongoDB database, as far as I know, when I store a Department object it stores all associated Employees. Is not that duplicating the information?

首先,上面的说法是不正确的。从 MongoDB 的角度来看,作为 BSON 提供的任何内容都按原样存储。如果您为员工提供部门,那么是的,它应该。您可以在创建部门后应用部分更新...(例如使用 $set 运算符)。但是,我认为你的问题范围比这个更广泛。

恕我直言,为数据库中的每个 document/table 创建 nano-services 不是一个好方法。特别是当服务只负责基本的 CRUD 操作时。您应该首先定义您的限界上下文、聚合根等……简而言之,在将业务需求映射到域对象之前,不要尝试设计表。我想说的是使用 DDD 原则 :)

这些是我目前找到的策略。在设计微服务时,您还应该考虑每种策略的优缺点。 (参考文献见底部。)

将关系数据库映射到 NoSQL 的一般原则

  • 1:1 关系
    • 嵌入
    • Link 外键
  • 1:M 关系
    • 嵌入
    • Link使用外键
    • (混合)分桶策略
  • N:M 关系
    • 双向引用
    • 单向引用

1:1 关系

1:1 关系可以用两种方式映射;

  • 将关系作为文档嵌入
  • Link 到单独集合中的文档

表格:

// Employee document
{
   "id": 123,
   "Name":"John Doe"
}

// Address document
{
   "City":"Ankara",
   "Street":"Genclik Street",
   "Nr":10
}

示例:嵌入 (1:1)

  • 优点:可以通过一次读取操作检索地址。

{
  "id": 123,
  "Name":"John Doe",
  "Address": {
    "City":"Ankara",
    "Street":"Genclik Street",
    "Nr":10
  } 
}

示例:Link 带外键 (1:1)

{
   "id": 763541685,  // link this
   "Name":"John Doe"
}

带文档密钥的地址;

{
   "employee_id": 763541685,
   "City":"Ankara",
   "Street":"Genclik street",
   "Nr":10
}

1:M 关系

初始:

// Department collection
{
  "id": 1,
  "deparment_name": "Software",
  "department_location": "Amsterdam"
}

/// Employee collection
[
    {
      "employee_id": 46515,
      "employee_name": "John Doe"
    },
    {
      "employee_id": 81584,
      "employee_name": "John Wick"
    }
]

示例:嵌入 (1:M)

警告:

  • 员工名单可能很大!
  • 在写入繁重的系统中使用此方法时要小心。由于索引、复制等内务处理操作,IO 负载会增加。
  • 员工分页很难!!!
{
  "id": 1,
  "deparment_name": "Software",
  "department_location": "Amsterdam",
  "employess": [
                   {
                     "employee_id": 46515,
                     "employee_name": "John Doe"
                   },
                   {
                     "employee_id": 81584,
                     "employee_name": "John Wick"
                   }
               ]
}

示例:Linking (1:M)

我们可以 link department_id 从员工文件中。

  • 优点:更容易分页
  • 缺点: Retrieve all employees that are belong to department X.这个查询会需要很多读操作!
[
    {
      "employee_id": 46515,
      "employee_name": "John Doe",
      "department_id": 1
    },
    {
      "employee_id": 81584,
      "employee_name": "John Wick",
      "department_id": 1
    }
]

示例:分桶策略(混合 1:M)

我们会将员工分成桶,每个桶中最多有 100 名员工。

{
    "id":1,
    "Page":1,
    "Count":100,
    "Employees":[
        {
            "employee_id": 46515,
            "employee_name": "John Doe"
        },
        {
            "employee_id": 81584,
            "employee_name": "John Wick"
        }
    ]
}

N:M 关系

To choose Two Way Embedding or One Way Embedding, the user must establish the maximum size of N and the size of M.
For example; if N is a maximum 3 categories for a book and M is a maximum of 5,000,000 books in a category you should pick One Way Embedding.
If N is a maximum 3 and M is a maximum of 5 then Two Way Embedding might work well. schema basics

示例:双向引用 (N:M)

在双向嵌入中,我们将在作者文档的书籍字段下包含书籍外键。

作者合集

[
    {
       "id":1,
       "Name":"John Doe",
       "Books":[ 1, 2 ]
    },{
       "id":2,
       "Name": "John Wick",
       "Books": [ 2 ]
    }
]

藏书:

[
    {
       "id": 1,
       "title": "Brave New World",
       "authors": [ 1 ]
    },{
       "id":2,
       "title": "Dune",
       "authors": [ 1, 2 ]
    }
]

示例:单向引用 (N:M)

示例书籍和类别:案例是几本书属于几个类别,但几个类别可以有很多本书。

  • 优点:优化读取性能
  • 之所以选择在书中嵌入所有对类别的引用,是因为一个类别中的书籍比一本书中的类别多得多。

类别

[
  {
    "id": 1,
    "category_name": "Brave New World"
  },
  {
    "id": 2,
    "category_name": "Dune"
  }
]

Book 文档的示例,其中包含 Categories

的外键
[
    {
      "id": 1,
      "title": "Brave New World",
      "categories": [ 1, 2 ],
      "authors": [ 1 ] 
    },
    {
      "id": 2,
      "title": "Dune",
      "categories": [ 1],
      "authors": [ 1, 2 ] 
    }
]

参考资料

  1. Case study: An algorithm for mapping the relational databases to mongodb
  2. The Little MongoDB Schema Design Book
  3. 6 Rules of Thumb for MongoDB Schema Design