If we need to update a CUSTOMER document and send an email in the same transaction, what's the best way to ensure this is done atomically? We're building an ecomm website and we need this functionality since, when a customer purchases an item, we have to update their order history and send them an email confirmation. In Java using a RDBMS database, we would do this very easily by simplu updating the database and sending a JMS message containing the email conte开发者_如何学Gont and details; both JDBC and JMS support distributed transactions, so either can be rolled back if something goes wrong, but not so with MongoDB. Is there a messaging feature in Mongo?
We were thinking about using a flag "emailSentFlag" in the orderHistory embedded document of Customer. When an order is placed, the flag is set to false. We would then use an external job that scans all order histories where emailSentFlag="false" and send the email at that point, but that puts us back int he same situation since we would have to set the flag back to "true" after sending the email, and that's not atomic.
> customer {
> name:
> email:
> orderHistory{
> orderId:
> status:
> emailSentFlag:
No transaction in Mongo as said by others already.
I would recommend that you build a mail queue instead of setting a flag and then search the entire collection. I would rather put an entry in a mail queue and then process that queue using the external job.
At least this is how I would try to do it first. Anyone, any comment good or bad?
You can do Atomic operations on the DOCUMENT level: http://www.mongodb.org/display/DOCS/Atomic+Operations
I'd have a collection for queueing your emails, and put a field in the email document to update the record when the email is sent. Then have your send job update the parent record when the send is complete.
You could even use a DB Ref from the email document tot he customer document.
It's worth thinking about whether these actions really need to happen atomically:
- Adding order history line item
- Send confirmation email
What would be the rollback criteria for sending an email? Invalid email address? Couldn't connect to mail server? Email sent but not received? Would you want to cancel a customer's transaction because the mail server is down?
I'd say it's fine to create a new order line with a customer.orderHistory.emailSentFlag
, then have a scheduled job to process the emailSentFlag = 0
orders to send confirmation emails, then set the flag once the email has been sent off successfully.
If you get a bounce-back, or delivery fails for some reason you can always reset the flag and try again. Or better still, dispatch the emailing job off to a mail queuing system that handles all that stuff for you.
It is a schema less database, so take advantage of it! :-) You can use duck typing but adding a field to any document type that is recognized across the types. Make sure you use names that are not used in the document.
In any document with a task (like sending an email) add a tasks: []
array. When you update a document and you need to have an "atomic" thingy associated with it push a task to this queue. For example, for email: { email: "cust@client.com", at: 0 }
. A background process polls for these tasks, sets them to expire in the future, executes them, and removes them. If something fails anywhere, the task will expire and be rerun. So you might send two mails but that is more or less inevitable with mail, even with transactions.
you can run this in the mongodb shell:
#db.orders.remove()
db.orders.save( { name: "abc", tasks : [ { email: 'a@dom.com', at: 0}, { email: 'b@dom.com', at: 0} ] })
db.orders.save( { name: "def", tasks : [ { email: 'c@dom.com', at: 0}] })
now = 10; future = 100; me = "some unique identifier for this scanning process"
while ( true ) {
task = db.orders.findAndModify({
query: { "tasks.at": { $lt: now }},
update : { $set : { "tasks.$.at": future, "tasks.$.who": me } },
fields: { "tasks":1},
new: true } )
if ( !task)
break;
for ( var i in task.tasks ) {
if ( task.tasks[i].who === me )
print( "Send email " + task.tasks[i].email )
}
db.orders.update( { _id: task._id }, { $pull: { tasks: { who: me } } } )
}
Look at http://www.mongodb.org/display/DOCS/findAndModify+Command for a similar example.
精彩评论