Basicamente você tem 3 casos:
- o livro e a resenha existem. Este é um simples
$set
- o livro existe, mas não a resenha. Isso precisa de um
$push
- o livro não existe. Isso precisa de
{upsert:1}
e um$setOnInsert
Não consegui encontrar uma maneira de unificar dois deles sem comprometer a integridade dos dados em caso de falha (lembre-se que o MongoDB não possui transação atômica).
Então meu melhor ideia é a seguinte:
// Case 1:
db.books.update({isbn:'1234567890',
review: { $elemMatch: {userID: '01234'}}},
{$set: {'review.$.rating': NEW_RATING}}
)
// Case 2:
db.books.update({isbn:'1234567890',
review: { $not: { $elemMatch: {userID: '01234'}}}},
{$push: {review: {rating: NEW_RATING, userID:'01234'}}}
)
// Case 3:
db.books.update({isbn:'1234567890'},
{$setOnInsert: {review: [{rating: NEW_RATING, userID:'01234'}]}},
{upsert:1}
)
Você pode executar cegamente essas três atualizações em um raw, pois não há sobreposição de maiúsculas e minúsculas entre elas. A beleza da coisa é que todas essas operações são idempotence . Assim, você pode aplicá-los uma ou várias vezes e obter sempre o mesmo resultado. Isso é especialmente importante em caso de failover. Além disso, não há como seu banco de dados ser inconsistente ou perder o existente dados em caso de falha. Na pior das hipóteses, a avaliação não Atualizada. Finalmente, isso deve garantir a consistência dos dados mesmo em caso de atualizações simultâneas (ou seja:nesse caso, uma atualização substituirá a outra, mas você não deve acabar tendo dois documentos para o mesmo livro ou duas resenhas do mesmo usuário para o mesmo livro).
Esse ponto posterior precisa ser confirmado, pois é tarde aqui, então minha análise pode ser um pouco duvidosa.
Como nota final, se você quiser reduzir o número de viagens de ida e volta entre o MongoDB e seu aplicativo, você pode dar uma olhada no
update
comando de banco de dados
permitindo que você envolva várias atualizações em um comando.