Controlador Amazon QLDB para Go: referencia del libro de cocina - Amazon Quantum Ledger Database (Amazon QLDB)

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Controlador Amazon QLDB para Go: referencia del libro de cocina

Esta guía de referencia muestra casos de uso comunes del controlador Amazon QLDB para Go. Proporciona ejemplos de código de Go que demuestran cómo usar el controlador para ejecutarcrear, leer, actualizar y eliminar(CRUD). También incluye ejemplos de código para procesar datos de Amazon Ion. Además, esta guía destaca las mejores prácticas para hacer que las transacciones sean idempotentes e implementar restricciones de exclusividad.

nota

Cuando corresponda, algunos casos de uso tienen diferentes ejemplos de código para cada versión principal compatible del controlador QLDB para Go.

Importación del controlador

El siguiente ejemplo de código importa el controlador y otrosAWSpaquetes.

3.x
import ( "github.com/amzn/ion-go/ion" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/qldbSession" "github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver" )
2.x
import ( "github.com/amzn/ion-go/ion" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" )
nota

Este ejemplo también importa el paquete Amazon Ion (amzn/ion-go/ion). Necesita este paquete para procesar los datos de Ion al ejecutar algunas operaciones de datos de esta referencia. Para obtener más información, consulte Trabajo con Amazon Ion.

Creación de una instancia del controlador

El siguiente ejemplo de código crea una instancia del controlador que se conecta a un nombre de libro mayor especificado en unAWSRegión .

3.x
cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { panic(err) } qldbSession := qldbsession.NewFromConfig(cfg, func(options *qldbsession.Options) { options.Region = "us-east-1" }) driver, err := qldbdriver.New( "vehicle-registration", qldbSession, func(options *qldbdriver.DriverOptions) { options.LoggerVerbosity = qldbdriver.LogInfo }) if err != nil { panic(err) } defer driver.Shutdown(context.Background())
2.x
awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) driver, err := qldbdriver.New( "vehicle-registration", qldbSession, func(options *qldbdriver.DriverOptions) { options.LoggerVerbosity = qldbdriver.LogInfo }) if err != nil { panic(err) }

Operaciones CRUD

QLDB se ejecutacrear, leer, actualizar y eliminar(CRUD) como parte de una transacción.

aviso

Como práctica recomendada, haga que sus transacciones de escritura sean estrictamente idempotentes.

Hacer que las transacciones sean idempotentes

Le recomendamos que haga las transacciones de escritura idempotentes para evitar cualquier efecto secundario inesperado en caso de reintentos. Una transacción esidempotentesi se puede ejecutar varias veces y producir resultados idénticos cada vez.

Por ejemplo, tomemos una transacción que inserta un documento en una tabla denominadaPerson. La transacción debe comprobar primero si el documento ya existe o no en la tabla. Sin esta comprobación, la tabla podría terminar con documentos duplicados.

Supongamos que QLDB confirma correctamente la transacción en el lado del servidor, pero el cliente agota el tiempo de espera mientras espera una respuesta. Si la transacción no es idempotente, se podría insertar el mismo documento más de una vez en caso de que se vuelva a intentar.

Uso de índices para evitar escaneos de tablas completas

También recomendamos que ejecute declaraciones con unaWHEREcláusula de predicado que utiliza unigualdadoperador en un campo indexado o un identificador de documento; por ejemplo,WHERE indexedField = 123oWHERE indexedField IN (456, 789). Sin esta búsqueda indexada, QLDB necesita realizar un escaneo de tabla, lo que puede provocar tiempos de espera de transacción ocontrol de concurrencia optimista(OCC) conflictos.

Para obtener más información sobre OCC, consulteModelo de simultaneidad de Amazon QLDB.

Transacciones creadas implícitamente

Laqldbdriver.executefunction acepta una función lambda que recibe una instancia deTransacción, que puede usar para ejecutar instrucciones. La instancia deTransactionenvuelve una transacción creada implícitamente.

Puede ejecutar sentencias dentro de la función lambda mediante el comandoTransaction.Executefunción. El controlador confirma implícitamente la transacción cuando se devuelve la función lambda.

Las siguientes secciones muestran cómo ejecutar operaciones CRUD básicas, especificar la lógica de reintento personalizada e implementar restricciones de unicidad.

Creación de tablas

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE TABLE Person") })

Creación de índices

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE INDEX ON Person(GovId)") })

Lectura de documentos

var decodedResult map[string]interface{} // Assumes that Person table has documents as follows: // { "GovId": "TOYENC486FH", "FirstName": "Brent" } _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'") if err != nil { return nil, err } for result.Next(txn) { ionBinary := result.GetCurrentData() err = ion.Unmarshal(ionBinary, &decodedResult) if err != nil { return nil, err } fmt.Println(decodedResult) // prints map[GovId: TOYENC486FH FirstName:Brent] } if result.Err() != nil { return nil, result.Err() } return nil, nil }) if err != nil { panic(err) }

Uso de parámetros de consulta

El siguiente ejemplo de código utiliza un parámetro de consulta de tipo nativo.

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH") }) if err != nil { panic(err) }

El siguiente ejemplo de código usa varios parámetros de consulta.

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", "TOYENC486FH", "Brent") }) if err != nil { panic(err) }

El siguiente ejemplo de código utiliza una lista de parámetros de consulta.

govIDs := []string{}{"TOYENC486FH", "ROEE1", "YH844"} result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", govIDs...) }) if err != nil { panic(err) }
nota

Cuando se ejecuta una consulta sin una búsqueda indexada, se invoca un análisis de tabla completa. En este ejemplo, recomendamos que tenga unaíndiceen laGovIdpara optimizar el rendimiento. Sin un índice enGovId, las consultas pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o a tiempos de espera de transacciones agotados.

Inserción de documentos

El siguiente ejemplo de código inserta tipos de datos nativos.

_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { // Check if a document with a GovId of TOYENC486FH exists // This is critical to make this transaction idempotent result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH") if err != nil { return nil, err } // Check if there are any results if result.Next(txn) { // Document already exists, no need to insert } else { person := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } _, err = txn.Execute("INSERT INTO Person ?", person) if err != nil { return nil, err } } return nil, nil })

Esta transacción inserta un documento en elPersonmesa. Antes de insertarlo, primero comprueba si el documento ya existe en la tabla. Esta comprobación hace que la transacción sea de naturaleza idempotente. Incluso si ejecuta esta transacción varias veces, no causará ningún efecto secundario no deseado.

nota

En este ejemplo, recomendamos que tenga un índice en laGovIdpara optimizar el rendimiento. Sin un índice enGovId, las declaraciones pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o a tiempos de espera de transacciones agotados.

Inserción de varios documentos en una declaración

Para insertar varios documentos mediante un únicoINSERT, puede pasar un parámetro de tipolistaa la declaración de la siguiente manera.

// people is a list txn.Execute("INSERT INTO People ?", people)

No se incluye el marcador de posición de la variable (?) entre corchetes angulares dobles (<<...>>) al pasar una lista. En las instrucciones manuales de PartiQL, los corchetes angulares dobles denotan una colección desordenada conocida comobolsa.

Actualización de documentos

El siguiente ejemplo de código utiliza tipos de datos nativos.

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", "John", "TOYENC486FH") })
nota

En este ejemplo, recomendamos que tenga un índice en laGovIdpara optimizar el rendimiento. Sin un índice enGovId, las declaraciones pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o a tiempos de espera de transacciones agotados.

Eliminación de documentos

El siguiente ejemplo de código utiliza tipos de datos nativos.

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("DELETE FROM Person WHERE GovId = ?", "TOYENC486FH") })
nota

En este ejemplo, recomendamos que tenga un índice en laGovIdpara optimizar el rendimiento. Sin un índice enGovId, las declaraciones pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o a tiempos de espera de transacciones agotados.

Ejecución de varios estados de cuenta en una transacción

// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. func InsureCar(driver *qldbdriver.QLDBDriver, vin string) (bool, error) { insured, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin) if err != nil { return false, err } hasNext := result.Next(txn) if !hasNext && result.Err() != nil { return false, result.Err() } if hasNext { _, err = txn.Execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin) if err != nil { return false, err } return true, nil } return false, nil }) if err != nil { panic(err) } return insured.(bool), err }

Lógica de reintento

El conductorExecutetiene un mecanismo de reintento incorporado que reintenta la transacción si se produce una excepción reintentable (como tiempos de espera o conflictos de OCC). El número máximo de reintentos y la estrategia de retroceso se pueden configurar.

El límite de reintentos predeterminado es4, y la estrategia de postergación por defecto esExponentialBackoffStrategycon una básica10milisegundos. Puede establecer la política de reintentos por instancia de controlador y también por transacción utilizando una instancia deRetryPolicy.

El siguiente ejemplo de código especifica la lógica de reintentos con un límite de reintentos personalizado y una estrategia de postergación personalizada para una instancia del controlador.

import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) func main() { awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) // Configuring retry limit to 2 retryPolicy := qldbdriver.RetryPolicy{MaxRetryLimit: 2} driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy }) if err != nil { panic(err) } // Configuring an exponential backoff strategy with base of 20 milliseconds retryPolicy = qldbdriver.RetryPolicy{ MaxRetryLimit: 2, Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000, }} driver, err = qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy }) if err != nil { panic(err) } }

El siguiente ejemplo de código especifica la lógica de reintentos con un límite de reintentos personalizado y una estrategia de postergación personalizada para una función anónima en particular. LaSetRetryPolicyanula la política de reintentos establecida para la instancia del controlador.

import ( "context" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) func main() { awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) // Configuring retry limit to 2 retryPolicy1 := qldbdriver.RetryPolicy{MaxRetryLimit: 2} driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy1 }) if err != nil { panic(err) } // Configuring an exponential backoff strategy with base of 20 milliseconds retryPolicy2 := qldbdriver.RetryPolicy{ MaxRetryLimit: 2, Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000, }} // Overrides the retry policy set by the driver instance driver.SetRetryPolicy(retryPolicy2) driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE TABLE Person") }) }

Implementación de restricciones de exclusividad

QLDB no admite índices únicos. Sin embargo, es fácil implementar este comportamiento en la aplicación.

Supongamos que desea implementar una restricción de unicidad en laGovIden laPersonmesa. Para ello, puede escribir una transacción que haga lo siguiente:

  1. Afirmar que la tabla no tiene documentos existentes con unGovId.

  2. Inserte el documento si la aserción se aprueba.

Si una transacción de la competencia pasa simultáneamente la afirmación, solo una de las transacciones se confirmará correctamente. La otra transacción fallará con una excepción de conflicto de OCC.

El siguiente ejemplo de código muestra cómo implementar esta lógica de restricción de exclusividad.

govID := "TOYENC486FH" document := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { // Check if doc with GovId = govID exists result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", govID) if err != nil { return nil, err } // Check if there are any results if result.Next(txn) { // Document already exists, no need to insert return nil, nil } return txn.Execute("INSERT INTO Person ?", document) }) if err != nil { panic(err) }
nota

En este ejemplo, recomendamos que tenga un índice en laGovIdpara optimizar el rendimiento. Sin un índice enGovId, las declaraciones pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o a tiempos de espera de transacciones agotados.

Trabajo con Amazon Ion

En las siguientes secciones se muestra cómo usar el módulo Amazon Ion para procesar datos Ion.

Importación del módulo Ion

import "github.com/amzn/ion-go/ion"

Creación de tipos Ion

La biblioteca Ion para Go actualmente no admite el Modelo de objetos de documento (DOM), por lo que no puede crear tipos de datos de Ion. Pero puede ordenar y desagrupar entre los tipos nativos de Go y el binario Ion cuando trabaja con QLDB.

Obtención de Ion

aDict := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } ionBytes, err := ion.MarshalBinary(aDict) if err != nil { panic(err) } fmt.Println(ionBytes) // prints [224 1 0 234 238 151 129 131 222 147 135 190 144 133 71 111 118 73 100 137 70 105 114 115 116 78 97 109 101 222 148 138 139 84 79 89 69 78 67 52 56 54 70 72 139 133 66 114 101 110 116]

Obtención de Ion

aDict := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } ionBytes, err := ion.MarshalText(aDict) if err != nil { panic(err) } fmt.Println(string(ionBytes)) // prints {FirstName:"Brent",GovId:"TOYENC486FH"}

Para obtener más información sobre IonDocumentación de Amazon Ionen GitHub. Para obtener más ejemplos de código sobre cómo trabajar con Ion en QLDB, consulteTrabajo con tipos de datos de Amazon Ion en Amazon QLDB.