Menggunakan Cloud Spanner dengan Cloud Functions (generasi ke-2)

Cloud Spanner

Tujuan

Tulis, deploy, dan picu HTTP Cloud Function yang mengakses Spanner.

Biaya

Dokumen ini menggunakan Spanner dan Cloud Functions, yang merupakan komponen Google Cloud yang dapat ditagih.

  • Untuk mengetahui informasi tentang biaya penggunaan Spanner, lihat Harga Spanner.

  • Untuk informasi tentang biaya penggunaan Cloud Functions, termasuk pemanggilan gratis, lihat Harga Cloud Functions.

Sebelum memulai

  1. Dokumen ini mengasumsikan bahwa Anda memiliki instance Spanner bernama test-instance dan database bernama example-db yang menggunakan skema aplikasi musik. Untuk petunjuk tentang cara membuat instance dan database dengan skema aplikasi musik, lihatPanduan memulai menggunakan konsol atau tutorial Memulai di Go ,Java ,Node.js, atau Python.

  2. Mengaktifkan Cloud Functions dan Cloud Build API:

    Aktifkan API

  3. Instal dan lakukan inisialisasi gcloud CLI.

    Jika Anda sudah menginstal gcloud CLI, update dengan menjalankan perintah berikut:

    gcloud components update
    
  4. Menyiapkan lingkungan pengembangan:

Menyiapkan aplikasi

  1. Clone repositori aplikasi contoh ke komputer lokal Anda:

    Node.js

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

    Atau, Anda dapat mendownload sampel sebagai file ZIP dan mengekstraknya.

    Python

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

    Atau, Anda dapat mendownload sampel sebagai file ZIP dan mengekstraknya.

    Go

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

    Atau, Anda dapat mendownload sampel sebagai file ZIP dan mengekstraknya.

    Java

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

    Atau, Anda dapat mendownload sampel sebagai file ZIP dan mengekstraknya.

  2. Beralihlah ke direktori yang berisi kode contoh Cloud Functions untuk mengakses 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. Lihat kode contoh:

    Node.js

    // Imports the Google Cloud client library
    const {Spanner} = require('@google-cloud/spanner');
    
    // Imports the functions framework to register your HTTP function
    const functions = require('@google-cloud/functions-framework');
    
    // 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.
     */
    functions.http('spannerQuickstart', 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

    import functions_framework
    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)
    
    @functions_framework.http
    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"
    	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
    	"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"
    
    func init() {
    	functions.HTTP("HelloSpanner", HelloSpanner)
    }
    
    // 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());
        }
      }
    }

    Fungsi ini mengirim kueri SQL untuk mengambil semua data Albums dari database Anda. Fungsi ini dijalankan saat Anda membuat permintaan HTTP ke endpoint fungsi.

Menerapkan fungsi

Untuk men-deploy fungsi dengan pemicu HTTP, jalankan perintah berikut di direktori spanner:

Node.js

gcloud functions deploy nodejs-spanner-function \
--gen2 \
--runtime=nodejs20 \
--region=REGION \
--source=. \
--entry-point=spannerQuickstart 
--trigger-http

Gunakan flag --runtime untuk menentukan ID runtime dari versi Node.js yang didukung untuk menjalankan fungsi Anda.

Python

gcloud functions deploy python-spanner-function \
--gen2 \
--runtime=python312 \
--region=REGION \
--source=. \
--entry-point=spanner_read_data 
--trigger-http

Gunakan flag --runtime untuk menentukan ID runtime versi Python yang didukung untuk menjalankan fungsi Anda.

Go

gcloud functions deploy go-spanner-function \
--gen2 \
--runtime=go121 \
--region=REGION \
--source=. \
--entry-point=HelloSpanner 
--trigger-http

Gunakan flag --runtime untuk menentukan ID runtime versi Go yang didukung untuk menjalankan fungsi Anda.

Java

gcloud functions deploy java-spanner-function \
--gen2 \
--runtime=java17 \
--region=REGION \
--source=. \
--entry-point=functions.HelloSpanner \
--memory=512MB 
--trigger-http

Gunakan flag --runtime untuk menentukan ID runtime versi Java yang didukung guna menjalankan fungsi Anda.

Ganti REGION dengan nama region Google Cloud tempat Anda ingin men-deploy fungsi (misalnya, us-west1).

Deployment fungsi mungkin memerlukan waktu hingga dua menit.

Perhatikan bahwa nilai url yang ditampilkan saat fungsi Anda selesai di-deploy. Anda akan menggunakannya saat memicu fungsi.

Anda dapat melihat fungsi yang di-deploy pada halaman Cloud Functions di Konsol Google Cloudsole. Anda juga dapat membuat dan mengedit fungsi di halaman tersebut, serta mendapatkan detail dan diagnostik untuk fungsi Anda.

Picu fungsi

Buat permintaan HTTP ke fungsi Anda:

curl URL

Ganti URL dengan nilai URL yang ditampilkan saat fungsi Anda selesai di-deploy.

Anda akan melihat output yang menunjukkan hasil kueri SQL, dengan asumsi Anda telah menyelesaikan tutorial Memulai dan mengisi 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

Anda juga dapat membuka URL fungsi di browser untuk melihat hasil kueri SQL.

Pembersihan

Agar tidak menimbulkan biaya tambahan ke akun Google Cloud Anda untuk resource Spanner dan Cloud Functions yang digunakan dalam dokumen ini:

  1. Hapus instance:

    gcloud spanner instances delete test-instance
    
  2. Hapus fungsi yang Anda deploy:

    Node.js

    gcloud functions delete nodejs-spanner-function 

    Python

    gcloud functions delete python-spanner-function 

    Go

    gcloud functions delete go-spanner-function 

    Java

    gcloud functions delete java-spanner-function 

Langkah selanjutnya