На прошлой неделе мы выпустили последнюю версию MongoDB для разработки. Официальный анонс фокусируется на исправлении ошибок, но я гораздо более взволнованы о новом свойства: Монго оболочка включает в себя новый CRUD API! В дополнении к старому insert
, update
и remove
, оболочка поддерживает insertMany
, replaceOne
и целый ряд других новых методов.
Почему я забочусь об этом, и почему вы должны?
Драйверы MongoDB следующего поколения, выпущенные этой весной, включают новый API для операций CRUD , но оболочка изначально не следовала этому примеру. Мой читатель Ник Милон заметил, что это шаг в неверном направлении: драйверы теперь менее совместимы с оболочкой. Он отметил: «Разработчики чаще переключаются между драйвером и оболочкой, чем драйверами на разных языках программирования». Поэтому я предложил эту функцию , ее кодировал Кристиан Квалхейм, а Кей Ким обновляет руководство пользователя.
Приятно, когда предложение незнакомца настолько очевидно верно, что мы спешим его реализовать.
Оболочка теперь более совместима с драйверами, чем когда-либо прежде. Но помимо этого, новый API просто лучше. Например, старый insert
метод может принимать один или несколько документов, и его возвращаемое значение не включает новые идентификаторы:
> // the old insert API
> db.test.insert({_id: 1})
WriteResult({ "nInserted" : 1 })
> db.test.insert([{_id: 2}, {_id: 3}, {_id: 4}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 3,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
Новый API лучше различает одиночную и массовую вставку и возвращает более полезные результаты:
> // the new CRUD API
> db.test2.insertOne({_id: 1})
{
"acknowledged" : true,
"insertedId" : 1
}
> db.test2.insertMany([{_id: 2}, {_id: 3}, {_id: 4}])
{
"acknowledged" : true,
"insertedIds" : [ 2, 3, 4 ]
}
Однако старый insert
метод остается в оболочке неопределенно долго: мы знаем, что есть куча скриптов, написанных старыми методами, и мы не планируем их отбрасывать.
На следующей операции. Панцирь update
отлично спроектирован. Никто не может вспомнить порядок «upsert» и «multi» опций, и по умолчанию «multi» ложно ставит в тупик поколения новых пользователей:
> // the old update API
> db.test.update(
... {_id: 1},
... {$set: {x: 1}},
... true /* upsert */,
... false /* multi */
)
WriteResult({
"nMatched" : 0,
"nUpserted" : 1,
"nModified" : 0,
"_id" : 1
})
Новый updateOne
метод гораздо проще использовать правильно:
> // the new update API
> db.test2.updateOne(
... {_id: 1},
... {$set: {x: 1}},
... {upsert: true}
)
{
"acknowledged" : true,
"matchedCount" : 0,
"modifiedCount" : 0,
"upsertedId" : 1
}
Мы представляем updateMany
для нескольких обновлений.
Еще один недостаток старого update
: если вы забыли знак $ в операторе, вы заменили целый документ вместо его изменения:
> // the old replace API
> db.test.update(
... {_id: 1},
... {set: {x: 1}} // OOPS!!
)
WriteResult({
"nMatched" : 1,
"nUpserted" : 0,
"nModified" : 1
})
> // document was replaced
> db.test.findOne()
{ "_id" : 1, "set" : { "x" : 1 } }
Теперь замена документа и его обновление хранятся четко, предотвращая ошибки:
> // the new update API catches mistakes
> db.test2.updateOne(
... {_id: 1},
... {set: {x: 1}}
)
Error: the update operation document must contain atomic operators
>
> // explicitly replace with "replaceOne"
> db.test2.replaceOne(
... {_id: 1},
... {x: 1}
)
{
"acknowledged" : true,
"matchedCount" : 1,
"modifiedCount" : 1
}
> db.test2.findOne()
{ "_id" : 1, "x" : 1 }
Старый remove
метод полон сюрпризов: по умолчанию для «multi» установлено значение «true», хотя «multi» имеет значение false для обновлений:
> // the old delete API
> db.test.remove({}) // remove EVERYTHING!!
Новые методы позволяют четко сказать, что вы хотите:
> // the new delete API
> db.test2.deleteOne({})
{ "acknowledged" : true, "deletedCount" : 1 }
> db.test2.deleteMany({})
{ "acknowledged" : true, "deletedCount" : 3 }
findAndModify
Команда MongoDB является мощной, и ее параметры невозможно изучить.
> // the old findAndModify
> db.test.findAndModify({
... query: {_id: 1},
... update: {$set: {x: 1}},
... new: true
... })
{ "_id" : 1, "x" : 1 }
> db.test.findAndModify({
... query: {_id: 1},
... // REPLACE the document!
... update: {field: 'value'},
... new: false
... })
{ "_id" : 1, "x" : 1 }
> db.test.findAndModify({
... query: {_id: 1},
... remove: true
... })
{ "_id" : 1, "field" : "value" }
Итак, мы разделили перегруженный findAndModify
на три:
> // the new API
> db.test2.findOneAndUpdate(
... {_id: 1},
... {$set: {x: 1}},
... {returnNewDocument: true}
... )
{ "_id" : 1, "x" : 1 }
> db.test2.findOneAndReplace(
... {_id: 1},
... {field: 'value'},
... {returnNewDocument: false}
... )
{ "_id" : 1, "x" : 1 }
> db.test2.findOneAndDelete({_id: 1})
{ "_id" : 1, "field" : "value" }
Это не полное описание изменений. find
, findOne
И другие методы запроса стандартизировали новые возможности при сохранении совместимости со старыми скриптами. Есть также новый bulkWrite
метод, который проще и эффективнее, чем старый Bulk API. У нас будет полная документация для нового API оболочки, когда мы опубликуем руководство для MongoDB 3.2. Тем временем, прочитайте статью Джереми Миколы о CRUD API , и сама спецификация также вполне разборчива.
Поскольку старые insert
, update
и remove
настолько распространены в коде наших пользователей, у нас нет планов удалять их или вносить обратно несовместимые изменения.
Я так рад, что мы нашли время для внедрения нового CRUD API в оболочке. Это была большая работа по созданию, тестированию и документированию — разница только для первоначального патча — пугающая — но оно того стоит, чтобы дать следующему поколению разработчиков непротиворечивый опыт, когда они впервые изучат MongoDB. Еще раз спасибо Нику Милону за то, что он дал нам толчок.