Utilizzo di Cloud Spanner con Cloud Functions

Cloud Spanner

Obiettivi

Scrivi, esegui il deployment e attiva una funzione cloud HTTP che accede a Spanner.

Costi

Questo argomento utilizza Spanner e Cloud Functions, che sono componenti fatturabili di Google Cloud.

  • Per informazioni sul costo dell'utilizzo di Spanner, consulta i prezzi di Spanner.

  • Per informazioni sul costo dell'utilizzo di Cloud Functions, incluse le chiamate gratuite, consulta i prezzi di Cloud Functions.

Prima di iniziare

  1. Questo argomento presuppone che tu abbia un'istanza di Spanner denominata test-instance e un database chiamato example-db che utilizza lo schema dell'applicazione musicale. Per istruzioni sulla creazione di un'istanza e un database con lo schema di applicazione musicale, consulta la guida rapida all'utilizzo della console o i tutorial per iniziare a utilizzare Go, Java, Node.js o Python.

  2. Abilita le API Cloud Functions e Cloud Build.

    Abilita le API

  3. Installa e inizializza l'interfaccia a riga di comando gcloud.

    Se hai già installato l'interfaccia a riga di comando gcloud, aggiornala eseguendo il comando seguente:

    gcloud components update
    
  4. Prepara l'ambiente di sviluppo:

Prepara l'applicazione

  1. Clonare il repository dell'app di esempio sul computer locale:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Go

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

  2. Passa alla directory che contiene il codice di esempio di Cloud Functions per accedere a Spanner:

    Node.js

    cd nodejs-docs-samples/functions/spanner/

    Python

    cd python-docs-samples/functions/spanner/

    Go

    cd golang-samples/functions/spanner/

    Java

    cd java-docs-samples/functions/spanner/
  3. Dai un'occhiata al codice di esempio:

    Node.js

    // Imports the Google Cloud client library
    const {Spanner} = require('@google-cloud/spanner');
    
    // Instantiates a client
    const spanner = new Spanner();
    
    // Your Cloud Spanner instance ID
    const instanceId = 'test-instance';
    
    // Your Cloud Spanner database ID
    const databaseId = 'example-db';
    
    /**
     * HTTP Cloud Function.
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.get = async (req, res) => {
      // Gets a reference to a Cloud Spanner instance and database
      const instance = spanner.instance(instanceId);
      const database = instance.database(databaseId);
    
      // The query to execute
      const query = {
        sql: 'SELECT * FROM Albums',
      };
    
      // Execute the query
      try {
        const results = await database.run(query);
        const rows = results[0].map(row => row.toJSON());
        rows.forEach(row => {
          res.write(
            `SingerId: ${row.SingerId}, ` +
              `AlbumId: ${row.AlbumId}, ` +
              `AlbumTitle: ${row.AlbumTitle}\n`
          );
        });
        res.status(200).end();
      } catch (err) {
        res.status(500).send(`Error querying Spanner: ${err}`);
      }
    };

    Python

    from google.cloud import spanner
    
    instance_id = 'test-instance'
    database_id = 'example-db'
    
    client = spanner.Client()
    instance = client.instance(instance_id)
    database = instance.database(database_id)
    
    def spanner_read_data(request):
        query = 'SELECT * FROM Albums'
    
        outputs = []
        with database.snapshot() as snapshot:
            results = snapshot.execute_sql(query)
    
            for row in results:
                output = 'SingerId: {}, AlbumId: {}, AlbumTitle: {}'.format(*row)
                outputs.append(output)
    
        return '\n'.join(outputs)

    Go

    
    // Package spanner contains an example of using Spanner from a Cloud Function.
    package spanner
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"net/http"
    	"sync"
    
    	"cloud.google.com/go/spanner"
    	"google.golang.org/api/iterator"
    )
    
    // client is a global Spanner client, to avoid initializing a new client for
    // every request.
    var client *spanner.Client
    var clientOnce sync.Once
    
    // db is the name of the database to query.
    var db = "projects/my-project/instances/my-instance/databases/example-db"
    
    // HelloSpanner is an example of querying Spanner from a Cloud Function.
    func HelloSpanner(w http.ResponseWriter, r *http.Request) {
    	clientOnce.Do(func() {
    		// Declare a separate err variable to avoid shadowing client.
    		var err error
    		client, err = spanner.NewClient(context.Background(), db)
    		if err != nil {
    			http.Error(w, "Error initializing database", http.StatusInternalServerError)
    			log.Printf("spanner.NewClient: %v", err)
    			return
    		}
    	})
    
    	fmt.Fprintln(w, "Albums:")
    	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
    	iter := client.Single().Query(r.Context(), stmt)
    	defer iter.Stop()
    	for {
    		row, err := iter.Next()
    		if err == iterator.Done {
    			return
    		}
    		if err != nil {
    			http.Error(w, "Error querying database", http.StatusInternalServerError)
    			log.Printf("iter.Next: %v", err)
    			return
    		}
    		var singerID, albumID int64
    		var albumTitle string
    		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
    			http.Error(w, "Error parsing database response", http.StatusInternalServerError)
    			log.Printf("row.Columns: %v", err)
    			return
    		}
    		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
    	}
    }
    

    Java

    import com.google.api.client.http.HttpStatusCodes;
    import com.google.cloud.functions.HttpFunction;
    import com.google.cloud.functions.HttpRequest;
    import com.google.cloud.functions.HttpResponse;
    import com.google.cloud.spanner.DatabaseClient;
    import com.google.cloud.spanner.DatabaseId;
    import com.google.cloud.spanner.LazySpannerInitializer;
    import com.google.cloud.spanner.ResultSet;
    import com.google.cloud.spanner.SpannerException;
    import com.google.cloud.spanner.SpannerOptions;
    import com.google.cloud.spanner.Statement;
    import com.google.common.annotations.VisibleForTesting;
    import com.google.common.base.MoreObjects;
    import java.io.PrintWriter;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    // HelloSpanner is an example of querying Spanner from a Cloud Function.
    public class HelloSpanner implements HttpFunction {
      private static final Logger logger = Logger.getLogger(HelloSpanner.class.getName());
    
      // TODO<developer>: Set these environment variables.
      private static final String SPANNER_INSTANCE_ID =
          MoreObjects.firstNonNull(System.getenv("SPANNER_INSTANCE"), "my-instance");
      private static final String SPANNER_DATABASE_ID =
          MoreObjects.firstNonNull(System.getenv("SPANNER_DATABASE"), "example-db");
    
      private static final DatabaseId databaseId =
          DatabaseId.of(
              SpannerOptions.getDefaultProjectId(),
              SPANNER_INSTANCE_ID,
              SPANNER_DATABASE_ID);
    
      // The LazySpannerInitializer instance is shared across all instances of the HelloSpanner class.
      // It will create a Spanner instance the first time one is requested, and continue to return that
      // instance for all subsequent requests.
      private static final LazySpannerInitializer SPANNER_INITIALIZER = new LazySpannerInitializer();
    
      @VisibleForTesting
      DatabaseClient getClient() throws Throwable {
        return SPANNER_INITIALIZER.get().getDatabaseClient(databaseId);
      }
    
      @Override
      public void service(HttpRequest request, HttpResponse response) throws Exception {
        var writer = new PrintWriter(response.getWriter());
        try {
          DatabaseClient client = getClient();
          try (ResultSet rs =
              client
                  .singleUse()
                  .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
            writer.printf("Albums:%n");
            while (rs.next()) {
              writer.printf(
                  "%d %d %s%n",
                  rs.getLong("SingerId"), rs.getLong("AlbumId"), rs.getString("AlbumTitle"));
            }
          } catch (SpannerException e) {
            writer.printf("Error querying database: %s%n", e.getMessage());
            response.setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, e.getMessage());
          }
        } catch (Throwable t) {
          logger.log(Level.SEVERE, "Spanner example failed", t);
          writer.printf("Error setting up Spanner: %s%n", t.getMessage());
          response.setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, t.getMessage());
        }
      }
    }

    La funzione invia una query SQL per recuperare tutti i dati di Albums dal database. La funzione viene eseguita quando effettui una richiesta HTTP all'endpoint della funzione.

Esegui il deployment della funzione

Per eseguire il deployment della funzione con un trigger HTTP, esegui il seguente comando nella directory spanner:

Node.js

gcloud functions deploy get \
--runtime nodejs16 --trigger-http
Puoi utilizzare i seguenti valori per il flag --runtime per specificare la versione Node.js preferita:
  • nodejs16 (consigliato)
  • nodejs14
  • nodejs12
  • nodejs10

Python

gcloud functions deploy spanner_read_data \
--runtime python39 --trigger-http
Puoi utilizzare i seguenti valori per il flag --runtime per specificare la versione Python preferita:
  • python39 (consigliato)
  • python38
  • python37

Go

gcloud functions deploy HelloSpanner \
--runtime go116 --trigger-http
Puoi utilizzare i seguenti valori per il flag --runtime per specificare la versione Go preferita:
  • go116 (consigliato)
  • go113
  • go111

Java

gcloud functions deploy java-spanner-function \
--entry-point functions.HelloSpanner \
--runtime java11 \
--memory 512MB --trigger-http
Puoi utilizzare i seguenti valori per il flag --runtime per specificare la tua versione di Java preferita:
  • java17 (anteprima)
  • java11 (consigliato)

Il deployment delle funzioni potrebbe richiedere fino a due minuti.

Tieni presente che il valore url restituito al termine del deployment della funzione. Lo utilizzerai quando attivi la funzione.

Puoi visualizzare le funzioni di cui è stato eseguito il deployment nella pagina Cloud Functions in Google Cloud Console. In questa pagina puoi anche creare e modificare le funzioni, nonché visualizzare dettagli e dati diagnostici delle funzioni.

Attiva la funzione

Esegui una richiesta HTTP alla funzione:

Node.js

curl "https://REGION-PROJECT_ID.cloudfunctions.net/get" 

Python

curl "https://REGION-PROJECT_ID.cloudfunctions.net/spanner_read_data" 

Go

curl "https://REGION-PROJECT_ID.cloudfunctions.net/HelloSpanner" 

Java

curl "https://REGION-PROJECT_ID.cloudfunctions.net/java-spanner-function" 

dove REGION e PROJECT_ID corrispondono ai valori visibili nel tuo terminale al termine del deployment della funzione. Dovresti vedere un output che mostra i risultati della query SQL, supponendo che tu abbia elaborato un tutorial per iniziare e completato il database:

SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold Your Peace
SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go
SingerId: 2, AlbumId: 1, AlbumTitle: Green
SingerId: 2, AlbumId: 3, AlbumTitle: Terrified
SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk

Puoi anche visitare l'URL della funzione nel browser per vedere il risultato della query SQL.

Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi aggiuntivi per le risorse Spanner e Cloud Functions utilizzate in questo argomento:

  1. Elimina l'istanza:

    gcloud spanner instances delete test-instance
    
  2. Elimina la funzione di cui hai eseguito il deployment:

    Node.js

    gcloud functions delete get 

    Python

    gcloud functions delete spanner_read_data 

    Go

    gcloud functions delete HelloSpanner 

    Java

    gcloud functions delete java-spanner-function 

Passaggi successivi