Utiliser Cloud Spanner avec Cloud Functions

Cloud Spanner

Objectifs

Rédiger, déployer et déclencher une fonction cloud HTTP qui accède à Spanner

Coûts

Cette rubrique utilise Spanner et Cloud Functions, qui sont des composants facturables de Google Cloud.

  • Pour en savoir plus sur le coût d'utilisation de Spanner, consultez la page Tarifs de Spanner.

  • Pour en savoir plus sur le coût d'utilisation de Cloud Functions, y compris les appels gratuits, consultez la section Tarifs de Cloud Functions.

Avant de commencer

  1. Dans cette rubrique, nous supposons que vous disposez d'une instance Spanner nommée test-instance et d'une base de données nommée example-db qui utilise le schéma d'une application musicale. Pour obtenir des instructions sur la création d'une instance et d'une base de données avec le schéma d'application musicale, consultez la page Démarrage rapide avec la console ou les tutoriels de prise en main de Go, Java, Node.js ou Python.

  2. Activez l'API Cloud Functions.

    Activer l'API

  3. Installez et initialisez le SDK Cloud.

  4. Mettez à jour les composants gcloud :

    gcloud components update
    
  5. Préparez votre environnement de développement :

Préparer l'application

  1. Clonez le dépôt de l'exemple d'application sur votre ordinateur local :

    Node.js

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Python

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Go

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Java

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

  2. Accédez au répertoire contenant l'exemple de code Cloud Functions pour accéder à 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. Consultez l'exemple de code :

    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.ResultSet;
    import com.google.cloud.spanner.Spanner;
    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());
    
      @VisibleForTesting
      static Spanner createSpanner() {
        return SpannerOptions.newBuilder().build().getService();
      }
    
      // SpannerHolder is a holder class for a Spanner instance that is initialized lazily.
      private static final class SpannerHolder {
        private final Object lock = new Object();
        private volatile boolean initialized;
        private volatile DatabaseClient client;
        private volatile Throwable error;
    
        private SpannerHolder() {}
    
        // Initialize the {@link Spanner} instance in a method and not as a static variable, as it
        // might throw an error, and we want to catch and log that specific error. An administrator must
        // take action to mitigate the reason for the initialization failure, for example ensuring that
        // the service account being used to access Cloud Spanner has permission to do so.
        DatabaseClient get() throws Throwable {
          if (!initialized) {
            synchronized (lock) {
              if (!initialized) {
                try {
                  DatabaseId db =
                      DatabaseId.of(
                          SpannerOptions.getDefaultProjectId(),
                          SPANNER_INSTANCE_ID,
                          SPANNER_DATABASE_ID);
                  client = createSpanner().getDatabaseClient(db);
                } catch (Throwable t) {
                  error = t;
                }
                initialized = true;
              }
            }
          }
          if (error != null) {
            throw error;
          }
          return client;
        }
      }
    
      // The SpannerHolder instance is shared across all instances of the HelloSpanner class.
      private static final SpannerHolder SPANNER_HOLDER = new SpannerHolder();
    
      @VisibleForTesting
      DatabaseClient getClient() throws Throwable {
        return SPANNER_HOLDER.get();
      }
    
      // 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");
    
      @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 fonction envoie une requête SQL pour extraire toutes les données Albums de votre base de données. La fonction est exécutée lorsque vous effectuez une requête HTTP sur le point de terminaison de la fonction.

Déployer la fonction

Pour déployer la fonction avec un déclencheur HTTP, exécutez la commande suivante dans le répertoire spanner :

Node.js

gcloud functions deploy get --runtime nodejs10 --trigger-http

Python

gcloud functions deploy spanner_read_data --runtime python37 --trigger-http

Go

gcloud functions deploy HelloSpanner --runtime go111 --trigger-http
Vous pouvez attribuer les valeurs suivantes à l'option --runtime, afin de spécifier votre version préférée de Go :
  • go111
  • go113

Java

gcloud functions deploy java-spanner-function --entry-point functions.HelloSpanner --runtime java11 --memory 512MB --trigger-http

Le déploiement de la fonction peut prendre jusqu'à deux minutes.

Notez la valeur url affichée lorsque le déploiement de la fonction est terminé. Vous l'utiliserez lorsque vous déclencherez la fonction.

Vous pouvez afficher les fonctions que vous avez déployées sur la page Cloud Functions de Google Cloud Console. Cette page vous permet également de créer et de modifier des fonctions, et d'obtenir des détails ou des diagnostics sur vos fonctions.

Déclencher la fonction

Envoyez une requête HTTP à votre fonction :

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" 

REGION et PROJECT_ID correspondent aux valeurs visibles dans votre terminal une fois le déploiement de la fonction terminé. Si vous avez suivi un tutoriel de prise en main et rempli la base de données, le résultat de la requête SQL semblable aux lignes suivantes devrait s'afficher :

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

Vous pouvez également accéder à l'URL de la fonction dans votre navigateur pour visualiser le résultat de votre requête SQL.

Nettoyage

Afin d'éviter que des frais supplémentaires soient facturés sur votre compte Google Cloud pour les ressources Spanner et Cloud Functions utilisées dans cette rubrique, procédez comme suit :

  1. Supprimez l'instance :

    gcloud spanner instances delete test-instance
    
  2. Supprimez la fonction que vous avez déployée :

    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 

Étapes suivantes