Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Learn why MongoDB was selected as a leader in the 2024 Gartner® Magic Quadrant™
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Gochevron-right

Transações ACID multidocumento no MongoDB com Go

Nic Raboy6 min read • Published Feb 01, 2022 • Updated Apr 03, 2024
Go
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Início rápido
star-empty
star-empty
star-empty
star-empty
star-empty
Os últimos meses foram uma aventura quando se trata de começar a usar o MongoDB usando a linguagem de programação Go (Golang). Exploramos tudo, desde operações de criação, recuperação, atualização e exclusão (CRUD) até a modelagem de dados e a alteração de fluxos. Para dar um final sólido a esta série, analisaremos um requisito popular de que muitas organizações precisam, e esse requisito são as transações.
Então, por que você deseja transações?
Existem algumas situações em que você pode precisar de atomicidade de leituras e gravações em vários documentos dentro de uma única coleção ou várias coleções. Isso nem sempre é uma necessidade, mas em alguns casos pode ser.
Veja o exemplo a seguir.
Digamos que você queira criar documentos em uma collection que dependam de documentos em outra collection existente. Ou digamos que você tem regras de validação de esquema em vigor em sua collection. No cenário de que você está tentando criar documentos e o documento relacionado não existe ou suas regras de validação de esquema falham, você não deseja que a operação continue. Em vez disso, você provavelmente gostaria de reverter para antes de acontecer.
Existem outras razões pelas quais você pode usar transações, mas você pode usar sua mente para elas.
Neste tutorial, vamos ver o que é necessário para usar transações com Golang e MongoDB. Nosso exemplo dependerá mais da passagem de regras de validação de esquema, mas isso não é uma limitação.

Noções básicas sobre o modelo de dados e aplicação da validação do esquema

Como continuamos o mesmo tema ao longo da série, acho que seria uma boa ideia relembrar o modelo de dados que usaremos para este exemplo.
Nos últimos tutoriais, exploramos o trabalho com possíveis dados de podcast em várias collections. Por exemplo, nosso modelo de dados Go se parece com o seguinte:
1type Episode struct {
2 ID primitive.ObjectID `bson:"_id,omitempty"`
3 Podcast primitive.ObjectID `bson:"podcast,omitempty"`
4 Title string `bson:"title,omitempty"`
5 Description string `bson:"description,omitempty"`
6 Duration int32 `bson:"duration,omitempty"`
7}
Os campos na estrutura de dados são mapeados para campos de documento MongoDB por meio das anotações BSON. Você pode aprender mais sobre como usar essas anotações no tutorial anterior que escrevi sobre o assunto.
Embora tenhamos outras collections, vamos nos concentrar estritamente na collectionepisodes para este exemplo.
Em vez de criar um código complicado para este exemplo para demonstrar operações que falham ou devem ser revertidas, Go a validação do esquema para forçar a falha de algumas operações. Vamos supor que nenhum episódio deve ter menos de dois minutos de duração, caso contrário, não é válido. Em vez de implementar isso, podemos usar recursos incorporados ao MongoDB.
Pegue a seguinte lógica de validação de esquema:
1{
2 "$jsonSchema": {
3 "additionalProperties": true,
4 "properties": {
5 "duration": {
6 "bsonType": "int",
7 "minimum": 2
8 }
9 }
10 }
11}
A lógica acima seria aplicada usando o MongoDB CLI ou com o Compass, mas estamos basicamente dizendo que nosso esquema para a coleção episodespode conter quaisquer campos em um documento, mas o campoduration deve ser um número inteiro e deve ser pelo menos dois. Nossa validação de esquema poderia ser mais complexa? Com certeza, mas neste exemplo estamos buscando a simplicidade. Se você quiser saber mais sobre validação de esquema, confira este incrível tutorial sobre o assunto.
Agora que sabemos o esquema e o que causará uma falha, podemos começar a implementar um código de transação que confirmará ou reverterá as alterações.

Início e confirmação de transações

Antes de nos aprofundarmos no início de uma sessão para nossas operações e confirmações de transações, vamos estabelecer um ponto base em nosso projeto. Vamos presumir que seu projeto tenha o seguinte código boilerplate MongoDB com Go:
1package main
2
3import (
4 "context"
5 "fmt"
6 "os"
7
8 "go.mongodb.org/mongo-driver/bson/primitive"
9 "go.mongodb.org/mongo-driver/mongo"
10 "go.mongodb.org/mongo-driver/mongo/options"
11)
12
13// Episode represents the schema for the "Episodes" collection
14type Episode struct {
15 ID primitive.ObjectID `bson:"_id,omitempty"`
16 Podcast primitive.ObjectID `bson:"podcast,omitempty"`
17 Title string `bson:"title,omitempty"`
18 Description string `bson:"description,omitempty"`
19 Duration int32 `bson:"duration,omitempty"`
20}
21
22func main() {
23 client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(os.Getenv("ATLAS_URI")))
24 if err != nil {
25 panic(err)
26 }
27 defer client.Disconnect(context.TODO())
28
29 database := client.Database("quickstart")
30 episodesCollection := database.Collection("episodes")
31
32 database.RunCommand(context.TODO(), bson.D{{"create", "episodes"}})
33}
A coleta deve existir antes de trabalhar com transações. Ao usar o RunCommand, se a collection já existir, um erro será retornado. Neste exemplo, o erro não é importante para nós, pois só queremos que a collection exista, mesmo que isso implique criá-la.
Agora, vamos presumir que você incluiu corretamente o driver MongoDB Go, conforme visto em um tutorial anterior intituladoComo se conectar ao cluster MongoDB com Go.
O objetivo aqui será tentar inserir um documento que esteja em conformidade com a validação do nosso esquema e um documento que não o faça, para que tenhamos uma confirmação que não aconteça.
1// ...
2
3func main() {
4 // ...
5
6 wc := writeconcern.New(writeconcern.WMajority())
7 rc := readconcern.Snapshot()
8 txnOpts := options.Transaction().SetWriteConcern(wc).SetReadConcern(rc)
9
10 session, err := client.StartSession()
11 if err != nil {
12 panic(err)
13 }
14 defer session.EndSession(context.Background())
15
16 err = mongo.WithSession(context.Background(), session, func(sessionContext mongo.SessionContext) error {
17 if err = session.StartTransaction(txnOpts); err != nil {
18 return err
19 }
20 result, err := episodesCollection.InsertOne(
21 sessionContext,
22 Episode{
23 Title: "A Transaction Episode for the Ages",
24 Duration: 15,
25 },
26 )
27 if err != nil {
28 return err
29 }
30 fmt.Println(result.InsertedID)
31 result, err = episodesCollection.InsertOne(
32 sessionContext,
33 Episode{
34 Title: "Transactions for All",
35 Duration: 1,
36 },
37 )
38 if err != nil {
39 return err
40 }
41 if err = session.CommitTransaction(sessionContext); err != nil {
42 return err
43 }
44 fmt.Println(result.InsertedID)
45 return nil
46 })
47 if err != nil {
48 if abortErr := session.AbortTransaction(context.Background()); abortErr != nil {
49 panic(abortErr)
50 }
51 panic(err)
52 }
53}
No código acima, passamos a definir as read e write concerns que nos darão o nível desejado de isolamento em nossa transação. Para saber mais sobre as preocupações de leitura e escrita disponíveis, consulte a documentação.
Depois de definir as opções de transação, iniciamos uma sessão que encapsulará tudo o que queremos fazer com a atomicidade. Depois, iniciamos uma transação que usaremos para confirmar tudo na sessão.
Um Session representa uma sessão lógica do MongoDB e pode ser usado para habilitar a consistência causal para um grupo de operações ou para executar operações em uma ACID transaction. Mais informações sobre como elas funcionam no Go podem ser encontradas na documentação.
Dentro da sessão, estamos fazendo duas operaçõesInsertOne. O primeiro seria bem-sucedido porque não viola nenhuma de nossas regras de validação de esquema. Ele até imprimirá um ID de objeto quando terminar. No entanto, a segunda operação falhará porque leva menos de dois minutos. O CommitTransaction nunca será bem-sucedido devido ao erro que a segunda operação criou. Quando a funçãoWithSession retorna o erro que criamos, a transação é anulada usando a funçãoAbortTransaction. Por esse motivo, nenhuma das operaçõesInsertOne aparecerá no banco de dados.

Usando uma API de transações conveniente

Iniciar e confirmar transações de dentro de uma sessão lógica não é a única maneira de trabalhar com transações ACID usando Golang e MongoDB. Em vez disso, podemos usar o que pode ser considerado uma API de transações mais conveniente.
Faça os seguintes ajustes em nosso código:
1// ...
2
3func main() {
4 // ...
5
6 wc := writeconcern.New(writeconcern.WMajority())
7 rc := readconcern.Snapshot()
8 txnOpts := options.Transaction().SetWriteConcern(wc).SetReadConcern(rc)
9
10 session, err := client.StartSession()
11 if err != nil {
12 panic(err)
13 }
14 defer session.EndSession(context.Background())
15
16 callback := func(sessionContext mongo.SessionContext) (interface{}, error) {
17 result, err := episodesCollection.InsertOne(
18 sessionContext,
19 Episode{
20 Title: "A Transaction Episode for the Ages",
21 Duration: 15,
22 },
23 )
24 if err != nil {
25 return nil, err
26 }
27 result, err = episodesCollection.InsertOne(
28 sessionContext,
29 Episode{
30 Title: "Transactions for All",
31 Duration: 2,
32 },
33 )
34 if err != nil {
35 return nil, err
36 }
37 return result, err
38 }
39
40 _, err = session.WithTransaction(context.Background(), callback, txnOpts)
41 if err != nil {
42 panic(err)
43 }
44}
Em vez de usar WithSession, agora estamos usando WithTransaction, que lida com o início de uma transação, a execução de algum código de aplicativo e, em seguida, a confirmação ou o cancelamento da transação com base no sucesso desse código de aplicativo. Não apenas isso, mas novas tentativas podem ocorrer para erros específicos se determinadas operações falharem.

Conclusão

Você acabou de ver como usar transações com o driver Go do MongoDB. Embora neste exemplo tenhamos usado a validação de esquema para determinar se uma operação de confirmação é bem-sucedida ou falha, você pode facilmente aplicar sua própria lógica de aplicativo dentro do escopo da sessão.
Se você quiser acompanhar outros tutoriais da série Introdução ao Golang, veja alguns abaixo:
Como as transações encerram esta série de tutoriais, certifique-se de ficar atento a mais tutoriais que se concentrem em tópicos mais específicos e interessantes que apliquem tudo o que foi ensinado durante os primeiros passos.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Início rápido
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Simultaneidade e fechamento gracioso do cliente MDB


Sep 04, 2024 | 5 min read
Tutorial

Usando Go para AI


Nov 07, 2024 | 19 min read
Tutorial

Vá para o MongoDB usando conectores Kafka - Guia final do agente


Sep 17, 2024 | 7 min read
Tutorial

Servidores HTTP persistindo dados no MongoDB


Sep 04, 2024 | 5 min read
Sumário