在微服务架构中管理与 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_id
和 department_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)
- 对时间序列等情况很有用。
- 混合 = 嵌入 + Linking
- 优势:单次读取一次获取 100 名员工,允许 高效分页。
- 见Building with Patterns: The Bucket Pattern
我们会将员工分成桶,每个桶中最多有 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 ]
}
]
参考资料
我使用微服务已经有一段时间了,总是使用关系数据库。我正在查看 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_id
和 department_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)
- 对时间序列等情况很有用。
- 混合 = 嵌入 + Linking
- 优势:单次读取一次获取 100 名员工,允许 高效分页。
- 见Building with Patterns: The Bucket Pattern
我们会将员工分成桶,每个桶中最多有 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 ]
}
]