Skip to content

Commit a34e4a3

Browse files
author
Chris Cho
authored
DOCSP-19819: transactions (mongodb#271)
* DOCSP-19396: transactions
1 parent dab14a1 commit a34e4a3

File tree

5 files changed

+706
-0
lines changed

5 files changed

+706
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
const { MongoClient } = require('mongodb');
2+
3+
async function cleanUp(client) {
4+
try {
5+
const customersColl = client.db('testdb').collection('customers');
6+
await customersColl.drop();
7+
} catch(e) {}
8+
try {
9+
const inventoryColl = client.db('testdb').collection('inventory');
10+
await inventoryColl.drop();
11+
} catch(e) {}
12+
try {
13+
const ordersCollection = client.db('testdb').collection('orders')
14+
await ordersCollection.drop();
15+
} catch(e) {}
16+
}
17+
18+
async function setup(client) {
19+
try {
20+
const customerColl = client.db('testdb').collection('customers');
21+
const inventoryColl = client.db('testdb').collection('inventory');
22+
23+
await customerColl.insertOne({ _id: 98765, orders: [] });
24+
25+
await inventoryColl.insertMany([
26+
{ name: 'sunblock', sku: 5432, qty: 85 },
27+
{ name: 'beach towel', sku: 7865, qty: 41 },
28+
]);
29+
} catch (e) {
30+
console.log('Unable to insert test data: ' + e);
31+
}
32+
}
33+
34+
async function queryData() {
35+
const uri = process.env.MONGODB_URI;
36+
const client = new MongoClient(uri, { useUnifiedTopology: true });
37+
try {
38+
await client.connect();
39+
await Promise.all(['customers', 'inventory', 'orders'].map(async c => {
40+
const coll = client.db('testdb').collection(c);
41+
console.log(JSON.stringify(await coll.find().toArray()));
42+
}, client));
43+
} finally {
44+
client.close();
45+
}
46+
}
47+
48+
// start callback
49+
async function placeOrder(client, session, cart, payment) {
50+
const ordersCollection = client.db('testdb').collection('orders');
51+
const orderResult = await ordersCollection.insertOne(
52+
{
53+
customer: payment.customer,
54+
items: cart,
55+
total: payment.total,
56+
},
57+
{ session }
58+
);
59+
60+
const inventoryCollection = client.db('testdb').collection('inventory');
61+
for (let i=0; i<cart.length; i++) {
62+
const item = cart[i];
63+
64+
// Cancel the transaction when you have insufficient inventory
65+
const checkInventory = await inventoryCollection.findOne(
66+
{
67+
sku: item.sku,
68+
qty: { $gte: item.qty }
69+
},
70+
{ session }
71+
);
72+
73+
if (checkInventory === null) {
74+
await session.abortTransaction();
75+
console.error('Insufficient quantity or SKU not found.');
76+
}
77+
78+
await inventoryCollection.updateOne(
79+
{ sku: item.sku },
80+
{ $inc: { 'qty': -item.qty }},
81+
{ session }
82+
);
83+
}
84+
85+
const customerCollection = client.db('testdb').collection('customers');
86+
await customerCollection.updateOne(
87+
{ _id: payment.customer },
88+
{ $push: { orders: orderResult.insertedId }},
89+
{ session }
90+
);
91+
}
92+
// end callback
93+
94+
const uri = process.env.MONGODB_URI;
95+
const client = new MongoClient(uri, { useUnifiedTopology: true });
96+
97+
async function run() {
98+
/* Test code: uncomment block to run
99+
await client.connect();
100+
await cleanUp(client);
101+
await setup(client);
102+
103+
const cart = [
104+
{ name: 'sunblock', sku: 5432, qty: 1, price: 5.19 },
105+
{ name: 'beach towel', sku: 7865, qty: 2, price: 15.99 }
106+
];
107+
const payment = { customer: 98765, total: 37.17 };
108+
109+
const transactionOptions = {
110+
readPreference: 'primary',
111+
readConcern: { level: 'local' },
112+
writeConcern: { w: 'majority' },
113+
maxCommitTimeMS: 1000
114+
};
115+
116+
const session = client.startSession();
117+
try {
118+
await session.withTransaction(async () => {
119+
await placeOrder(client, session, cart, payment)
120+
}, transactionOptions);
121+
} catch(error) {
122+
console.log('Encountered an error during the transaction: ' + error);
123+
} finally {
124+
await session.endSession();
125+
}
126+
await client.close();
127+
*/
128+
129+
// start session
130+
const transactionOptions = {
131+
readPreference: 'primary',
132+
readConcern: { level: 'local' },
133+
writeConcern: { w: 'majority' },
134+
maxCommitTimeMS: 1000
135+
};
136+
137+
const session = client.startSession();
138+
try {
139+
await session.withTransaction(
140+
async (session) => { /* your transaction here */ },
141+
transactionOptions);
142+
} catch(error) {
143+
console.log('Encountered an error during the transaction: ' + error);
144+
} finally {
145+
await session.endSession();
146+
}
147+
// end session
148+
}
149+
run().then(() => queryData());
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
const { MongoError, MongoClient } = require('mongodb');
2+
3+
// drop collections
4+
async function cleanUp(client) {
5+
await Promise.all( ['customers', 'inventory', 'orders'].map(async c => {
6+
try {
7+
const coll = client.db('testdb').collection(c);
8+
await coll.drop();
9+
} catch(e) {}
10+
}));
11+
}
12+
13+
async function setup(client) {
14+
try {
15+
const customerColl = client.db('testdb').collection('customers');
16+
const inventoryColl = client.db('testdb').collection('inventory');
17+
18+
await customerColl.insertOne({ _id: 98765, orders: [] });
19+
20+
await inventoryColl.insertMany([
21+
{ name: 'sunblock', sku: 5432, qty: 85 },
22+
{ name: 'beach towel', sku: 7865, qty: 41 },
23+
]);
24+
} catch (e) {
25+
console.log('Unable to insert test data: ' + e);
26+
}
27+
}
28+
29+
async function queryData() {
30+
const uri = process.env.MONGODB_URI;
31+
const client = new MongoClient(uri, { useUnifiedTopology: true });
32+
try {
33+
await client.connect();
34+
await Promise.all(['customers', 'inventory', 'orders'].map(async c => {
35+
const coll = client.db('testdb').collection(c);
36+
console.log(JSON.stringify(await coll.find().toArray()));
37+
}, client));
38+
39+
} finally {
40+
client.close();
41+
}
42+
}
43+
44+
// start placeOrder
45+
async function placeOrder(client, cart, payment) {
46+
const transactionOptions = {
47+
readConcern: { level: 'snapshot' },
48+
writeConcern: { w: 'majority' },
49+
readPreference: 'primary'
50+
};
51+
52+
const session = client.startSession();
53+
try {
54+
session.startTransaction(transactionOptions);
55+
56+
const ordersCollection = client.db('testdb').collection('orders');
57+
const orderResult = await ordersCollection.insertOne(
58+
{
59+
customer: payment.customer,
60+
items: cart,
61+
total: payment.total,
62+
},
63+
{ session }
64+
);
65+
66+
const inventoryCollection = client.db('testdb').collection('inventory');
67+
for (let i=0; i<cart.length; i++) {
68+
const item = cart[i];
69+
70+
// Cancel the transaction when you have insufficient inventory
71+
const checkInventory = await inventoryCollection.findOne(
72+
{
73+
sku: item.sku,
74+
qty: { $gte: item.qty }
75+
},
76+
{ session }
77+
)
78+
if (checkInventory === null) {
79+
throw new Error('Insufficient quantity or SKU not found.');
80+
}
81+
82+
await inventoryCollection.updateOne(
83+
{ sku: item.sku },
84+
{ $inc: { 'qty': -item.qty }},
85+
{ session }
86+
);
87+
}
88+
89+
const customerCollection = client.db('testdb').collection('customers');
90+
await customerCollection.updateOne(
91+
{ _id: payment.customer },
92+
{ $push: { orders: orderResult.insertedId }},
93+
{ session }
94+
);
95+
await session.commitTransaction();
96+
console.log('Transaction successfully committed.');
97+
98+
} catch (error) {
99+
if (error instanceof MongoError && error.hasErrorLabel('UnknownTransactionCommitResult')) {
100+
// add your logic to retry or handle the error
101+
}
102+
else if (error instanceof MongoError && error.hasErrorLabel('TransientTransactionError')) {
103+
// add your logic to retry or handle the error
104+
} else {
105+
console.log('An error occured in the transaction, performing a data rollback:' + error);
106+
}
107+
await session.abortTransaction();
108+
} finally {
109+
await session.endSession();
110+
}
111+
}
112+
// end placeOrder
113+
114+
async function run() {
115+
const uri = process.env.MONGODB_URI;
116+
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
117+
118+
await client.connect();
119+
await cleanUp(client);
120+
await setup(client);
121+
122+
const cart = [
123+
{ name: 'sunblock', sku: 5432, qty: 1, price: 5.19 },
124+
{ name: 'beach towel', sku: 7865, qty: 2, price: 15.99 }
125+
];
126+
const payment = { customer: 98765, total: 37.17 };
127+
128+
try {
129+
await placeOrder(client, cart, payment);
130+
} finally {
131+
await cleanUp(client);
132+
await client.close();
133+
}
134+
}
135+
run().then(() => queryData());

source/fundamentals.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Fundamentals
1212
/fundamentals/crud
1313
/fundamentals/promises
1414
/fundamentals/aggregation
15+
/fundamentals/transactions
1516
/fundamentals/indexes
1617
/fundamentals/collations
1718
/fundamentals/logging

0 commit comments

Comments
 (0)