Статьи

Новый CRUD API MongoDB Shell

Лист на воде

На прошлой неделе мы выпустили последнюю версию 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. Еще раз спасибо Нику Милону за то, что он дал нам толчок.