Background Image
TECNOLOGÍA

El contexto Go

Paul Ferguson - Headshot
Paul Ferguson
Consultor principal

April 11, 2023 | 5 Minuto(s) de lectura

Soy un desarrollador Java desde hace mucho tiempo que tuvo la oportunidad de aprender Go mientras desarrollaba una aplicación para mi cliente. Hay muchas similitudes que hicieron la experiencia bastante fácil, pero hubo algunas pegas que descubrí por el camino. De lo que quiero hablar ahora es del contexto Go.

En una aplicación Java, cuando llega una petición, se procesa completamente y se devuelve una respuesta.

En una aplicación Go, hay un contexto que hace las cosas más interesantes. Un contexto transporta plazos, señales de cancelación y otros valores relacionados con las peticiones a través de los límites de la API y entre procesos. Cuando se trabaja con una petición se puede utilizar para cancelar o poner en timeout la petición. Una cancelación puede ocurrir por muchas razones diferentes. El usuario puede cerrar su navegador o hacer clic en cancelar mientras se procesa la solicitud. Podría haber una mala conexión a Internet o algún otro problema de red. Un cliente podría decidir que la solicitud está tardando demasiado y cerrarla. Independientemente de la razón, el contexto indicará que ha terminado. Esto significa que el trabajo que aún no se ha completado puede ser omitido.

Algunos paquetes para bases de datos, clientes http y otros requerirán que se les pase un contexto. Los otros probablemente tendrán una forma de establecerlo. Al principio no entendía por qué. Ya había un contexto establecido en la solicitud, así que simplemente lo pasé. Cuando un contexto se cancelaba empezábamos a ver errores de contexto cancelado. Esto se debe a que estos paquetes actuaban sobre la señal de contexto cancelado deteniendo todo su procesamiento y devolviendo un error.

ctx := request.Context()

// buscar una persona en la base de datos persona, err := databaseCall(ctx)

...

// databaseCall Busca una persona en la base de datos. 

funcdatabaseCall(ctxcontext.Context) (Persona, error) {

logInfo(ctx, "Realizando la llamada a la base de datos")

var persona Persona

   // pausa un poco para permitir la cancelación del contexto 

   err := pause(ctx)

if err != nil {

return persona, err

}

   // el popular paquete de base de datos pgxpostgres requiere que se establezca un contexto en la mayoría de las operaciones

   connection, err := pgx.Connect(ctx,

"postgres://postgres:postgres@localhost:5432/postgres")

if err != nil {

      // además de los errores habituales si el paquete pgx se da cuenta de que el contexto está hecho devolverá un error 

      return persona, err

}

defer connection.Close(ctx)

   // consulta la base de datos en busca de una persona y rellena sus valores struct 

  err = connection.QueryRow(ctx, "select name from

people").Scan(&person.Name)

return persona, err

}

Para ver el código completo echa un vistazo a mi aplicación de ejemplo en GitHub en https://github.com/paul-ferguson/the-go-context

Observa cómo esta aplicación comprueba si la llamada a la base de datos devuelve un error. Si hay un error comprobamos si el contexto está hecho. Si lo está, la aplicación no realiza ningún otro proceso y simplemente devuelve. Ni siquiera necesita devolver un código de error http ni ninguna respuesta JSON.

Si quieres que una aplicación Go funcione como una aplicación Java necesitas usar el contexto apropiado. Digamos que tenemos un endpoint REST que está procesando una post llamada para guardar algunos datos. Querremos que se procese completamente y se persista en la base de datos independientemente de si se cancela el contexto de la petición. En este caso, podemos utilizar context.Background() para crear un nuevo contexto que nunca pueda ser cancelado. He mostrado un ejemplo de esto a continuación. También he incluido en los comentarios del código el método context.WithCancel(), context.WithDeadline() o context.WithTimeout(). Definen respectivamente un método de cancelación y una fecha límite o tiempo de espera/duración que activará la señal de contexto realizado. A context.Background() sólo debería usarse en ciertos escenarios: funciones principales, inicialización y pruebas, y como Contexto de nivel superior para peticiones entrantes. Nuestro caso de uso es este último. En la mayoría de las situaciones, la mejor práctica es utilizar una de las otras opciones.

ctx:= context.Fondo()

/* esto: Pruebe estas otras opciones en su lugar:

// esto devuelve un contexto y una función que llamamos cancelar

ctx, cancel := context.WithCancel(request.Context())

// llamar a cancel activará la señal done

cancelar()

// esto activará un tiempo de espera después de 2 segundos

ctx, cancel := context.WithTimeout(request.Context(), time.Second * 2)

// del documento Go: Aunque ctx expire, es una buena práctica llamar a su función de cancelación en cualquier caso. No hacerlo puede mantener el contexto y su padre vivos más tiempo del necesario. defer cancel() // esto disparará un timeout después de un tiempo que es 2 segundos en el futuro ctx, cancel := context.WithDeadline(request.Context(), time.Now().Add(time.Second * 2)) aplazar cancel()

*/

...

// restCall Busca a una persona haciendo una llamada rest. 

funcrestCall(ctxcontext.Context) (Persona, error) {

logInfo(ctx, "Realizando la llamada rest")

var persona Persona // crear la petición get al endpoint del lado del servidor 

   request, err := http.NewRequestWithContext(ctx, "GET",

"http://localhost:8080/server-side-get", nil)

   /* 

prueba esto: Si no pasamos el contexto, la petición no se cancelará cuando se produzca una señal de "done". La petición 

solicitud se procesará completamente desperdiciando recursos. 

request, err := http.NewRequest("GET", "http://localhost:8080/server-side-get", nil) 

*/ 

// pasamos el identificador de la petición en la cabecera, lo que nos permite rastrear esta petición 

   request.Header.Add(requestIDHeaderKey, ctx.Value(requestIDContextKey).(string))

   // realiza la petición 

   response, err := http.DefaultClient.Do(request)

if err != nil {

      //todo 

      return persona, err

}

//leer el cuerpo completo de la respuesta 

   body, err := io.ReadAll(response.Body)

if err != nil {

return persona, err

}

// cerrar el cuerpo de la respuesta 

   err = response.Body.Close()

if err != nil {

return persona, err

}

   // unmarshal el contenido del cuerpo de la respuesta a una estructura de persona 

   err = json.Unmarshal(cuerpo, &persona) return persona, err

}

Para ver el código completo echa un vistazo a mi aplicación de ejemplo en GitHub en https://github.com/paul-ferguson/the-go-context

Finalmente, para completar, debería mostrar cómo el contexto puede ser usado para pasar valores de solicitud a través de los límites de la API y entre procesos.Anteriormente he utilizado esto para registrar valores comunes, como un id de solicitud.He aquí un ejemplo.

const requestIDHeaderKey= "request-id"

const requestIDContextKey= contextKey(requestIDHeaderKey)

...

// establecer el id de la petición como valor en el contexto 

requestId:= request.Header.Get("request-id")

if requestId== "" {

   // no se ha establecido ningún id de petición, así que crea uno único 

   requestId= uuid.New().String()

}

ctx= context.WithValue(ctx, requestIDContextKey, requestId)

logInfo(ctx, "Se ha llamado a Get")

...

// aquí hay muchos paquetes de logging que podríamos haber usado, pero enrollando el nuestro para mayor claridad en este ejemplo

funclogInfo(ctxcontexto.Contexto, mensaje cadena) {

fmt.Println("info", mensaje, ctx.Value(requestIDContextKey))

}

Para ver el código completo echa un vistazo a mi aplicación de ejemplo en GitHub en https://github.com/paul-ferguson/the-go-context

Tecnología
Modernización de aplicaciones

¿Necesita ayuda para crear su próxima aplicación Go?

Reflexiones más recientes

Explore las entradas de nuestro blog e inspírese con los líderes de opinión de todas nuestras empresas.
Asset - Unlock the Value Thumbnail
Nube

Transformación de la planificación financiera: De SAP BPC a SAP Analytics Cloud (SAC)

Explore las ventajas y los retos de la transición de SAP BPC a SAP Analytics Cloud (SAC) para la planificación financiera.