Cómo usar Cloud SQL con Go

Esta sección del instructivo de Bookshelf para Go muestra cómo la app de muestra almacena los datos persistentes en Google Cloud SQL.

Esta página forma parte de un instructivo de varias páginas. Para comenzar desde el principio y leer las instrucciones de configuración, visita la app de Bookshelf para Go.

Cómo crear una instancia de Cloud SQL

Tras la implementación, la aplicación usa el proxy de Cloud SQL integrado en el entorno de App Engine para comunicarse con la instancia de Cloud SQL. Sin embargo, para probar tu aplicación de manera local, debes instalar y usar una copia local del proxy de Cloud SQL en tu entorno de desarrollo.

Obtén más información acerca del proxy de Cloud SQL.

Para realizar tareas administrativas básicas en la instancia de Cloud SQL, usa el cliente MySQL.

Instala el proxy de SQL

Descarga y también instala el proxy de Cloud SQL. El proxy de Cloud SQL se usa para conectarte a tu instancia de Cloud SQL cuando se ejecuta de manera local.

Linux de 64 bits

  1. Descarga el proxy:
    wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
    
  2. Haz que el proxy sea ejecutable:
    chmod +x cloud_sql_proxy
    

Linux de 32 bits

  1. Descarga el proxy:
    wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.386 -O cloud_sql_proxy
    
  2. Haz que el proxy sea ejecutable:
    chmod +x cloud_sql_proxy
    

macOS de 64 bits

  1. Descarga el proxy:
    curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64
    
  2. Haz que el proxy sea ejecutable:
    chmod +x cloud_sql_proxy
    

macOS de 32 bits

  1. Descarga el proxy:
    curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.386
    
  2. Haz que el proxy sea ejecutable:
    chmod +x cloud_sql_proxy
    

Windows de 64 bits

Para descargar el proxy, haz clic derecho en https://dl.google.com/cloudsql/cloud_sql_proxy_x64.exe, selecciona "Guardar vínculo como…" y cámbiale el nombre a cloud_sql_proxy.exe.

Windows de 32 bits

Para descargar el proxy, haz clic derecho en https://dl.google.com/cloudsql/cloud_sql_proxy_x86.exe, selecciona "Guardar vínculo como…" y cámbiale el nombre a cloud_sql_proxy.exe.
Si tu sistema operativo no se incluye aquí, también puedes compilar el proxy desde la fuente.

Crear una instancia de Cloud SQL

  1. Crea una instancia de Cloud SQL para MySQL de segunda generación. Asigna a la instancia el nombre library o uno similar. Es posible que la instancia tarde algunos minutos para estar lista. Cuando esté lista, debería poder verse en la lista de instancias.
  2. Ahora, usa el SDK de Cloud desde la línea de comandos para ejecutar el siguiente comando. Copia el valor que se muestra para connectionName, ya que lo necesitarás en el paso siguiente.
    gcloud sql instances describe [YOUR_INSTANCE_NAME]

    El valor de connectionName tiene el formato [PROJECT_NAME]:[REGION_NAME]:[INSTANCE_NAME].

Inicializa la instancia de Cloud SQL

  1. Inicia el proxy de Cloud SQL con el valor de connectionName del paso anterior.

    Linux/macOS

    ./cloud_sql_proxy -instances="[YOUR_INSTANCE_CONNECTION_NAME]"=tcp:3306

    Windows

    cloud_sql_proxy.exe -instances="[YOUR_INSTANCE_CONNECTION_NAME]"=tcp:3306

    Reemplaza [YOUR_INSTANCE_CONNECTION_NAME] por el valor connectionName que guardaste en el paso anterior.

    Este paso establece una conexión desde tu computadora local hasta tu instancia de Cloud SQL a fin de realizar pruebas locales. Mantén el proxy de Cloud SQL en ejecución durante todo el tiempo que realices pruebas locales en tu aplicación.

Configuraciones

  1. Ve al directorio que contiene el código de muestra:

    Linux/macOS

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/getting-started/bookshelf
    

    Windows

    cd %GOPATH%\src\github.com\GoogleCloudPlatform\golang-samples\getting-started\bookshelf
    

  2. Abre config.go para editar.

  3. Opcional: Borra los comentarios de DB = newMemoryDB().

  4. Borra los comentarios de la porción del código de la sección etiquetada como [START cloudsql].

  5. Ingresa tu nombre de usuario, contraseña y nombre de la conexión de la instancia de Cloud SQL en la estructura cloudSQLConfig.

    Puedes encontrar el nombre de la conexión de tu instancia en Google Cloud Platform Console, en los detalles de tu instancia de Cloud SQL. Los nombres de las conexiones de las instancias utilizan el formato project:region:cloudsql-instance.

    Por ejemplo, supón que el nombre de la conexión de tu instancia es myproject:us-central1:library, tu nombre de usuario es goapp y tu contraseña es secret123. Entonces, la porción de Cloud SQL de tu archivo config.go debería verse de la siguiente manera:

    DB, err = configureCloudSQL(cloudSQLConfig{
        Username: "goapp",
        Password: "secret123",
        Instance: "myproject:us-central1:library",
    })
    
  6. Guarda y cierra config.go.

  7. Abre app/app.yaml para editar.

  8. Borra los comentarios de la línea que comienza con cloud_sql_instances.

  9. Reemplaza INSTANCE_CONNECTION_NAME por el string de la conexión de tu instancia. Esto debe coincidir con el valor en la estructura cloudSQLConfig en config.go.

  10. Guarda y cierra app/app.yaml.

Cómo ejecutar la app en la máquina local

  1. Ejecuta la app para iniciar un servidor web local:

    cd app
    go run app.go auth.go template.go
    
  2. En el navegador web, ingresa la siguiente dirección:

    http://localhost:8080

Ahora puedes navegar por las páginas web de la app y agregar, editar y borrar libros.

Presiona Control + C para salir del servidor web local.

Cómo implementar la app en el entorno flexible de App Engine

  1. En el directorio app, ingresa el siguiente comando para implementar la muestra:

    gcloud app deploy
    
  2. En el navegador web, ingresa la siguiente dirección. Reemplaza [YOUR_PROJECT_ID] por el ID del proyecto:

    https://[YOUR_PROJECT_ID].appspot.com
    

Si actualizas tu app, podrás implementar la versión actualizada mediante el mismo comando que usaste para implementar la app por primera vez. La implementación nueva crea una versión nueva de tu app y la convierte a la versión predeterminada. Las versiones anteriores de la app se conservan, al igual que sus instancias de VM asociadas. Ten en cuenta que todas estas instancias de VM y versiones de la app son recursos facturables.

Para reducir costos, borra las versiones no predeterminadas de la app.

Para borrar una versión de una app, haz lo siguiente:

  1. In the GCP Console, go to the Versions page for App Engine.

    Go to the Versions page

  2. Select the checkbox for the non-default app version you want to delete.
  3. Click Delete to delete the app version.

Para obtener toda la información acerca de la limpieza de los recursos facturables, consulta la sección Limpieza en el paso final de este instructivo.

Estructura de la aplicación

Este diagrama muestra los componentes de la aplicación y la manera en que se conectan entre sí.

Estructura de la app de Bookshelf

Cómo comprender el código

En esta sección se explica el código de la aplicación y su funcionamiento.

Cómo manejar los envíos de los usuarios con formularios

Cuando un usuario hace clic en Add Book (Agregar libro), el navegador lo lleva a /books/add y la función addFormHandler en app/app.go muestra un formulario donde el usuario puede agregar un libro:

r.Methods("GET").Path("/books/add").
	Handler(appHandler(addFormHandler))
// addFormHandler displays a form that captures details of a new book to add to
// the database.
func addFormHandler(w http.ResponseWriter, r *http.Request) *appError {
	return editTmpl.Execute(w, r, nil)
}

Imagen de un formulario para agregar y editar

El formulario incluye campos de entrada de texto para Title (Título), Author (Autor), Date Published (Fecha de publicación) y Description (Descripción), además de otros que se utilizan en otras secciones de este instructivo. Observa los nombres de las variables incorporados en los atributos value: por ejemplo, {{.Title}}. Estos se propagan desde la biblioteca de texto/plantilla estándar de Go.

<form method="post" enctype="multipart/form-data" action="/books{{if .}}/{{.ID}}{{end}}">
  <div class="form-group">
    <label for="title">Title</label>
    <input class="form-control" name="title" id="title" value="{{.Title}}">
  </div>
  <div class="form-group">
    <label for="author">Author</label>
    <input class="form-control" name="author" id="author" value="{{.Author}}">
  </div>
  <div class="form-group">
    <label for="publishedDate">Date Published</label>
    <input class="form-control" name="publishedDate" id="publishedDate" value="{{.PublishedDate}}">
  </div>
  <div class="form-group">
    <label for="description">Description</label>
    <input class="form-control" name="description" id="description" value="{{.Description}}">
  </div>
  <div class="form-group">
    <label for="image">Cover Image</label>
    <input class="form-control" name="image" id="image" type="file">
  </div>
  <button class="btn btn-success">Save</button>
  <input type="hidden" name="imageURL" value="{{.ImageURL}}">
  <input type="hidden" name="createdBy" value="{{.CreatedBy}}">
  <input type="hidden" name="createdByID" value="{{.CreatedByID}}">
</form>

Cómo manejar los envíos de formularios

Cuando un usuario completa el formulario y hace clic en Save (Guardar), el siguiente código maneja la acción HTTP POST /books del formulario. El código inserta el nuevo libro en la base de datos mediante una llamada a la función bookshelf.DB.AddBook().

r.Methods("POST").Path("/books").
	Handler(appHandler(createHandler))
// createHandler adds a book to the database.
func createHandler(w http.ResponseWriter, r *http.Request) *appError {
	book, err := bookFromForm(r)
	if err != nil {
		return appErrorf(err, "could not parse book from form: %v", err)
	}
	id, err := bookshelf.DB.AddBook(book)
	if err != nil {
		return appErrorf(err, "could not save book: %v", err)
	}
	go publishUpdate(id)
	http.Redirect(w, r, fmt.Sprintf("/books/%d", id), http.StatusFound)
	return nil
}

La función AddBook, definida en db_mysql.go, realiza una operación INSERT de SQL para agregar los datos enviados por el usuario a la base de datos de CloudSQL:

const insertStatement = `
  INSERT INTO books (
    title, author, publishedDate, imageUrl, description, createdBy, createdById
  ) VALUES (?, ?, ?, ?, ?, ?, ?)`

// AddBook saves a given book, assigning it a new ID.
func (db *mysqlDB) AddBook(b *Book) (id int64, err error) {
	r, err := execAffectingOneRow(db.insert, b.Title, b.Author, b.PublishedDate,
		b.ImageURL, b.Description, b.CreatedBy, b.CreatedByID)
	if err != nil {
		return 0, err
	}

	lastInsertID, err := r.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("mysql: could not get last insert ID: %v", err)
	}
	return lastInsertID, nil
}
// execAffectingOneRow executes a given statement, expecting one row to be affected.
func execAffectingOneRow(stmt *sql.Stmt, args ...interface{}) (sql.Result, error) {
	r, err := stmt.Exec(args...)
	if err != nil {
		return r, fmt.Errorf("mysql: could not execute statement: %v", err)
	}
	rowsAffected, err := r.RowsAffected()
	if err != nil {
		return r, fmt.Errorf("mysql: could not get rows affected: %v", err)
	} else if rowsAffected != 1 {
		return r, fmt.Errorf("mysql: expected 1 row affected, got %d", rowsAffected)
	}
	return r, nil
}

Cada instrucción de SQL se prepara cuando se inicia la aplicación. Por ejemplo, este código en la función newMySQLDB prepara la instrucción de SQL para crear listas de libros y almacena la instrucción preparada en el objeto db:


// Prepared statements. The actual SQL queries are in the code near the
// relevant method (e.g. addBook).
if db.list, err = conn.Prepare(listStatement); err != nil {
	return nil, fmt.Errorf("mysql: prepare list: %v", err)
}

Cuando el usuario edita la información de un libro después de enviado, el método UpdateBook realiza una operación UPDATE de SQL:

const updateStatement = `
  UPDATE books
  SET title=?, author=?, publishedDate=?, imageUrl=?, description=?,
      createdBy=?, createdById=?
  WHERE id = ?`

// UpdateBook updates the entry for a given book.
func (db *mysqlDB) UpdateBook(b *Book) error {
	if b.ID == 0 {
		return errors.New("mysql: book with unassigned ID passed into updateBook")
	}

	_, err := execAffectingOneRow(db.update, b.Title, b.Author, b.PublishedDate,
		b.ImageURL, b.Description, b.CreatedBy, b.CreatedByID, b.ID)
	return err
}

Cuando el usuario hace clic en Libros, el navegador lo lleva a la página /books y el método ListBooks muestra todos los libros en el almacén de datos.

const listStatement = `SELECT * FROM books ORDER BY title`

// ListBooks returns a list of books, ordered by title.
func (db *mysqlDB) ListBooks() ([]*Book, error) {
	rows, err := db.list.Query()
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var books []*Book
	for rows.Next() {
		book, err := scanBook(rows)
		if err != nil {
			return nil, fmt.Errorf("mysql: could not read row: %v", err)
		}

		books = append(books, book)
	}

	return books, nil
}
¿Te ha resultado útil esta página? Enviar comentarios: