Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
MongoDBchevron-right

Validação de documentos para coleções polimórficas

Andrew Morgan6 min read • Published Aug 10, 2023 • Updated Oct 01, 2024
MongoDB
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Em Design Reviews de modelagem de dados com clientes, geralmente proponho um esquema em que documentos diferentes na mesma collection contêm diferentes tipos de dados. Isso torna eficiente a busca de documentos relacionados em uma única consulta indexada. O esquema flexível do MongoDB é ótimo para otimizar cargas de trabalho dessa forma, mas as pessoas podem se preocupar em perder o controle sobre quais aplicativos gravam nessascollections.
Os clientes geralmente se preocupam em garantir que apenas documentos formatados corretamente sejam incluídos em uma collection, por isso explico o recurso de validação de esquema do MongoDB. Surge então a pergunta: "Como isso funciona com um esquema polimórfico/de collection única?" Esta postagem pretende responder a essa pergunta – e é mais simples do que você imagina.

O aplicativo bancário e seus dados

O aplicativo em que estou trabalhando gerencia os detalhes do cliente e da conta. Há uma relação de muitos para muitos entre clientes e contas. O aplicativo precisa ser capaz de consultar com eficiência os dados do cliente com base no ID do cliente e os dados da conta com base no ID do cliente ou no ID da conta.
Aqui está um exemplo de documentos de clientes e contas em que minha esposa e eu compartilhamos uma conta corrente, mas cada um tem sua própria conta poupança:
1{
2 "_id": "kjfgjebgjfbkjb",
3 "customerId": "CUST-123456789",
4 "docType": "customer",
5 "name": {
6 "title": "Mr",
7 "first": "Andrew",
8 "middle": "James",
9 "last": "Morgan"
10 },
11 "address": {
12 "street1": "240 Blackfriars Rd",
13 "city": "London",
14 "postCode": "SE1 8NW",
15 "country": "UK"
16 },
17 "customerSince": ISODate("2005-05-20")
18}
19
20{
21 "_id": "jnafjkkbEFejfleLJ",
22 "customerId": "CUST-987654321",
23 "docType": "customer",
24 "name": {
25 "title": "Mrs",
26 "first": "Anne",
27 "last": "Morgan"
28 },
29 "address": {
30 "street1": "240 Blackfriars Rd",
31 "city": "London",
32 "postCode": "SE1 8NW",
33 "country": "UK"
34 },
35 "customerSince": ISODate("2003-12-01")
36}
37
38{
39 "_id": "dksfmkpGJPowefjdfhs",
40 "accountNumber": "ACC1000000654",
41 "docType": "account",
42 "accountType": "checking",
43 "customerId": [
44 "CUST-123456789",
45 "CUST-987654321"
46 ],
47 "dateOpened": ISODate("2003-12-01"),
48 "balance": NumberDecimal("5067.65")
49}
50
51{
52 "_id": "kliwiiejeqydioepwj",
53 "accountNumber": "ACC1000000432",
54 "docType": "account",
55 "accountType": "savings",
56 "customerId": [
57 "CUST-123456789"
58 ],
59 "dateOpened": ISODate("2005-10-28"),
60 "balance": NumberDecimal("10341.21")
61}
62
63{
64 "_id": "djahspihhfheiphfipewe",
65 "accountNumber": "ACC1000000890",
66 "docType": "account",
67 "accountType": "savings",
68 "customerId": [
69 "CUST-987654321"
70 ],
71 "dateOpened": ISODate("2003-12-15"),
72 "balance": NumberDecimal("10341.89")
73}
Como um aparte, esses são os índices que adicionei para tornar mais eficientes as consultas frequentes a que me referi:
1const indexKeys1 = { accountNumber: 1 };
2const indexKeys2 = { customerId: 1, accountType: 1 };
3const indexOptions1 = { partialFilterExpression: { docType: 'account' }};
4const indexOptions2 = { partialFilterExpression: { docType: 'customer' }};
5
6db.getCollection(collection).createIndex(indexKeys1, indexOptions1);
7db.getCollection(collection).createIndex(indexKeys2, indexOptions2);

Adicionando validação de esquema

Para citações Docs...
A validação de esquema permite criar regras de validação para seus campos, como tipos de dados permitidos e intervalos de valores.
O MongoDB usa um modelo de esquema flexível, o que significa que os documentos em uma coleção não precisam ter os mesmos campos ou tipos de dados por padrão. Depois de estabelecer um esquema de aplicativo, você pode usar a validação de esquema para garantir que não haja alterações de esquema não intencionais ou tipos de dados impróprios.
As regras de validação são bem simples de configurar, e ferramentas como o Hackolade podem torná-las ainda mais simples – até mesmo a engenharia reversa de seus documentos existentes.
É simples imaginar a configuração de uma regra de validação do esquema JSON para uma collection em que todos os documentos compartilham os mesmos atributos e tipos. Mas e quanto às coleções polimórficas? Mesmo em collection polimórficas, há estrutura nos documentos. Felizmente, a sintaxe para configurar as regras de validação permite a opcionalidade necessária.
Tenho dois tipos diferentes de documentos que gostaria de armazenar em minha collectionAccountscustomer e account. Incluí um atributodocType em cada documento para identificar que tipo de entidade ele representa.
Começando criando uma definição de JSON schema para cada tipo de documento:
1const customerSchema = {
2 required: ["docType", "customerId", "name", "customerSince"],
3 properties: {
4 docType: { enum: ["customer"] },
5 customerId: { bsonType: "string"},
6 name: {
7 bsonType: "object",
8 required: ["first", "last"],
9 properties: {
10 title: { enum: ["Mr", "Mrs", "Ms", "Dr"]},
11 first: { bsonType: "string" },
12 middle: { bsonType: "string" },
13 last: { bsonType: "string" }
14 }
15 },
16 address: {
17 bsonType: "object",
18 required: ["street1", "city", "postCode", "country"],
19 properties: {
20 street1: { bsonType: "string" },
21 street2: { bsonType: "string" },
22 postCode: { bsonType: "string" },
23 country: { bsonType: "string" }
24 }
25 },
26 customerSince: {
27 bsonType: "date"
28 }
29 }
30};
31
32const accountSchema = {
33 required: ["docType", "accountNumber", "accountType", "customerId", "dateOpened", "balance"],
34 properties: {
35 docType: { enum: ["account"] },
36 accountNumber: { bsonType: "string" },
37 accountType: { enum: ["checking", "savings", "mortgage", "loan"] },
38 customerId: { bsonType: "array" },
39 dateOpened: { bsonType: "date" },
40 balance: { bsonType: "decimal" }
41 }
42};
Essas definições definem quais atributos devem estar no documento e quais tipos eles devem receber. Observe que os campos podem ser opcionais — como name.middle no esquemacustomer .
Em seguida, é uma simples questão de usar o operador de JSON schemaoneOf para permitir documentos que correspondam a qualquer um dos dois esquemas:
1const schemaValidation = {
2 $jsonSchema: { oneOf: [ customerSchema, accountSchema ] }
3};
4
5db.createCollection(collection, {validator: schemaValidation});
Eu queria Go um estágio além e adicionar mais algumas validações semânticas:
  • Para documentos customer, o valorcustomerSince não pode ser anterior à hora atual.
  • Para documentos account, o valordateOpened não pode ser anterior à hora atual.
  • Para contas de poupança, o balance não pode ficar abaixo de zero.
Esses documentos representam essas verificações:
1const badCustomer = {
2 "$expr": { "$gt": ["$customerSince", "$$NOW"] }
3};
4
5const badAccount = {
6 $or: [
7 {
8 accountType: "savings",
9 balance: { $lt: 0}
10 },
11 {
12 "$expr": { "$gt": ["$dateOpened", "$$NOW"]}
13 }
14 ]
15};
16
17const schemaValidation = {
18 "$and": [
19 { $jsonSchema: { oneOf: [ customerSchema, accountSchema ] }},
20 { $nor: [
21 badCustomer,
22 badAccount
23 ]
24 }
25 ]
26};
Atualizei as regras de validação da coleção para incluir estas novas verificações:
1const schemaValidation = {
2 "$and": [
3 { $jsonSchema: { oneOf: [ customerSchema, accountSchema ] }},
4 { $nor: [
5 badCustomer,
6 badAccount
7 ]
8 }
9 ]
10};
11
12db.createCollection(collection, {validator: schemaValidation} );
Se você quiser recriar isso em seu próprio MongoDB database, basta colar isso em seu playground do MongoDB no VS Code:
1const cust1 = {
2 "_id": "kjfgjebgjfbkjb",
3 "customerId": "CUST-123456789",
4 "docType": "customer",
5 "name": {
6 "title": "Mr",
7 "first": "Andrew",
8 "middle": "James",
9 "last": "Morgan"
10 },
11 "address": {
12 "street1": "240 Blackfriars Rd",
13 "city": "London",
14 "postCode": "SE1 8NW",
15 "country": "UK"
16 },
17 "customerSince": ISODate("2005-05-20")
18}
19
20const cust2 = {
21 "_id": "jnafjkkbEFejfleLJ",
22 "customerId": "CUST-987654321",
23 "docType": "customer",
24 "name": {
25 "title": "Mrs",
26 "first": "Anne",
27 "last": "Morgan"
28 },
29 "address": {
30 "street1": "240 Blackfriars Rd",
31 "city": "London",
32 "postCode": "SE1 8NW",
33 "country": "UK"
34 },
35 "customerSince": ISODate("2003-12-01")
36}
37
38const futureCustomer = {
39 "_id": "nansfanjnDjknje",
40 "customerId": "CUST-666666666",
41 "docType": "customer",
42 "name": {
43 "title": "Mr",
44 "first": "Wrong",
45 "last": "Un"
46 },
47 "address": {
48 "street1": "240 Blackfriars Rd",
49 "city": "London",
50 "postCode": "SE1 8NW",
51 "country": "UK"
52 },
53 "customerSince": ISODate("2025-05-20")
54}
55
56const acc1 = {
57 "_id": "dksfmkpGJPowefjdfhs",
58 "accountNumber": "ACC1000000654",
59 "docType": "account",
60 "accountType": "checking",
61 "customerId": [
62 "CUST-123456789",
63 "CUST-987654321"
64 ],
65 "dateOpened": ISODate("2003-12-01"),
66 "balance": NumberDecimal("5067.65")
67}
68
69const acc2 = {
70 "_id": "kliwiiejeqydioepwj",
71 "accountNumber": "ACC1000000432",
72 "docType": "account",
73 "accountType": "savings",
74 "customerId": [
75 "CUST-123456789"
76 ],
77 "dateOpened": ISODate("2005-10-28"),
78 "balance": NumberDecimal("10341.21")
79}
80
81const acc3 = {
82 "_id": "djahspihhfheiphfipewe",
83 "accountNumber": "ACC1000000890",
84 "docType": "account",
85 "accountType": "savings",
86 "customerId": [
87 "CUST-987654321"
88 ],
89 "dateOpened": ISODate("2003-12-15"),
90 "balance": NumberDecimal("10341.89")
91}
92
93const futureAccount = {
94 "_id": "kljkdfgjkdsgjklgjdfgkl",
95 "accountNumber": "ACC1000000999",
96 "docType": "account",
97 "accountType": "savings",
98 "customerId": [
99 "CUST-987654333"
100 ],
101 "dateOpened": ISODate("2030-12-15"),
102 "balance": NumberDecimal("10341.89")
103}
104
105const negativeSavings = {
106 "_id": "shkjahsjdkhHK",
107 "accountNumber": "ACC1000000666",
108 "docType": "account",
109 "accountType": "savings",
110 "customerId": [
111 "CUST-9837462376"
112 ],
113 "dateOpened": ISODate("2005-10-28"),
114 "balance": NumberDecimal("-10341.21")
115}
116
117const indexKeys1 = { accountNumber: 1 }
118const indexKeys2 = { customerId: 1, accountType: 1 }
119const indexOptions1 = { partialFilterExpression: { docType: 'account' }}
120const indexOptions2 = { partialFilterExpression: { docType: 'customer' }}
121
122const customerSchema = {
123 required: ["docType", "customerId", "name", "customerSince"],
124 properties: {
125 docType: { enum: ["customer"] },
126 customerId: { bsonType: "string"},
127 name: {
128 bsonType: "object",
129 required: ["first", "last"],
130 properties: {
131 title: { enum: ["Mr", "Mrs", "Ms", "Dr"]},
132 first: { bsonType: "string" },
133 middle: { bsonType: "string" },
134 last: { bsonType: "string" }
135 }
136 },
137 address: {
138 bsonType: "object",
139 required: ["street1", "city", "postCode", "country"],
140 properties: {
141 street1: { bsonType: "string" },
142 street2: { bsonType: "string" },
143 postCode: { bsonType: "string" },
144 country: { bsonType: "string" }
145 }
146 },
147 customerSince: {
148 bsonType: "date"
149 }
150 }
151}
152
153const accountSchema = {
154 required: ["docType", "accountNumber", "accountType", "customerId", "dateOpened", "balance"],
155 properties: {
156 docType: { enum: ["account"] },
157 accountNumber: { bsonType: "string" },
158 accountType: { enum: ["checking", "savings", "mortgage", "loan"] },
159 customerId: { bsonType: "array" },
160 dateOpened: { bsonType: "date" },
161 balance: { bsonType: "decimal" }
162 }
163}
164
165const badCustomer = {
166 "$expr": { "$gt": ["$customerSince", "$$NOW"] }
167}
168
169const badAccount = {
170 $or: [
171 {
172 accountType: "savings",
173 balance: { $lt: 0}
174 },
175 {
176 "$expr": { "$gt": ["$dateOpened", "$$NOW"]}
177 }
178 ]
179}
180
181const schemaValidation = {
182 "$and": [
183 { $jsonSchema: { oneOf: [ customerSchema, accountSchema ] }},
184 { $nor: [
185 badCustomer,
186 badAccount
187 ]
188 }
189 ]
190}
191
192const database = 'MongoBank';
193const collection = 'Accounts';
194
195use(database);
196db.getCollection(collection).drop();
197db.createCollection(collection, {validator: schemaValidation} )
198db.getCollection(collection).replaceOne({"_id": cust1._id}, cust1, {upsert: true});
199db.getCollection(collection).replaceOne({"_id": cust2._id}, cust2, {upsert: true});
200db.getCollection(collection).replaceOne({"_id": acc1._id}, acc1, {upsert: true});
201db.getCollection(collection).replaceOne({"_id": acc2._id}, acc2, {upsert: true});
202db.getCollection(collection).replaceOne({"_id": acc3._id}, acc3, {upsert: true});
203
204// The following 3 operations should fail
205
206db.getCollection(collection).replaceOne({"_id": negativeSavings._id}, negativeSavings, {upsert: true});
207db.getCollection(collection).replaceOne({"_id": futureCustomer._id}, futureCustomer, {upsert: true});
208db.getCollection(collection).replaceOne({"_id": futureAccount._id}, futureAccount, {upsert: true});
209
210db.getCollection(collection).dropIndexes();
211db.getCollection(collection).createIndex(indexKeys1, indexOptions1);
212db.getCollection(collection).createIndex(indexKeys2, indexOptions2);

Conclusão

Espero que este breve artigo tenha mostrado como é fácil usar validações de esquema com as coleções polimórficas do MongoDB e o padrão de design de coleção única.
Não Go em muitos detalhes sobre por que escolhai o modelo de dados usado neste exemplo. Se você quiser saber mais (e deveria!), então aqui estão alguns ótimos recursos sobre modelagem de dados com o MongoDB:

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Garantir alta disponibilidade para MongoDB no Kubernetes


Jul 12, 2024 | 11 min read
Tutorial

Otimizando o desempenho de $lookup usando o poder da indexação


Aug 30, 2024 | 7 min read
Início rápido

Como criar um aplicativo CRUD com MongoDB, Quarkus e GraalVM


Aug 29, 2024 | 7 min read
Início rápido

Como usar transações MongoDB em Node.js


Aug 24, 2023 | 10 min read
Sumário
  • O aplicativo bancário e seus dados