Spring Cloud GCP – Cloud Spanner

Reading Time: 5 minutes

Spring framework is very popular and most widely used across the world. It is also one of the oldest frameworks in Java. The Spring Cloud GCP project makes the Spring Framework a first-class citizen of the Google Cloud Platform (GCP).

Cloud Spanner is a fully managed relational database with unlimited scale, strong consistency, and up to 99.999% availability. It is like the big brother of Cloud SQL. Cloud Spanner features all the benefits of relational semantics and SQL along with unlimited scaling capabilities. It provides high availability with zero scheduled downtime and online schema changes.

In this blog, we will use a small instance of Cloud Spanner to save and get data. To integrate Google Cloud Spanner with our application we will be using Spring.

Configurations in Google Cloud Platform

Basic GCP setup

Firstly, to get started, you will require an active GCP account with billing enabled. Please navigate to console.cloud.google.com.

If you don’t have an account, please create an account with your new or existing Google account and start the free trial.

Google Cloud Console

Next, you have to create a project under which you will configure and provision all the resources required. You can find the Projects Tab right next to the GCP logo. Please do remember the project ID/name, which is unique across all Google Cloud projects universally.

Now you will have to activate the cloud shell. To activate it please click on the icon mentioned below.

Cloud Shell

Run the following command to check if your project is configured properly:

gcloud config list project

It will show the current project name that you set in the previous step. If it does not set your project using the following command:

gcloud config set project <PROJECT_ID>

Setting up Cloud Spanner

  1. Enable the Cloud Spanner API:
Cloud Spanner API

2. Create a Cloud Spanner instance:

Cloud Spanner instance

3. Now, let us create the database and it’s schema.

To do this let’s first go to Cloud Shell. Then type create a database as shown below

gcloud spanner databases create orders \
  --instance=cloud-spanner-instance

Now, let’s define the schema of the database as follows:

cat << EOF > schema.ddl
CREATE TABLE orders (
  order_id STRING(36) NOT NULL,
  description STRING(255),
  creation_timestamp TIMESTAMP,
) PRIMARY KEY (order_id);

CREATE TABLE order_items (
  order_id STRING(36) NOT NULL,
  order_item_id STRING(36) NOT NULL,
  description STRING(255),
  quantity INT64,
) PRIMARY KEY (order_id, order_item_id),
  INTERLEAVE IN PARENT orders ON DELETE CASCADE;
EOF

Next, we have to add this schema to the database using the following command:

gcloud spanner databases ddl update orders \
  --instance=cloud-spanner-instance \
  --ddl="$(<schema.ddl)"

Creating the Spring Cloud Spanner Application

Use the following command to download the Spring application:

curl https://start.spring.io/starter.tgz \
  -d packaging=jar \
  -d dependencies=cloud-gcp,web,lombok \
  -d baseDir=cloud-spanner-application \
  -d bootVersion=2.6.9 | tar -xzvf -

When downloaded, open the Cloud Shell Editor. It is just beside the Cloud Shell icon.

Once loaded, click on File -> Open Workspace. Then open your project folder.

Navigate to the pom.xml file. Add the Cloud Spanner Starter dependency.

        <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
        </dependency>

Then navigate to application.properties file under resources. We will have to configure the Cloud Spanner database information here:

spring.cloud.gcp.spanner.instance-id=cloud-spanner-instance
spring.cloud.gcp.spanner.database=orders

Now, trigger a Maven Rebuild from the Cloud Shell to verify the changes and obtain the dependency.

./mvnw package

Finally, we are done with the configurations. Now let’s start creating the application.

From a high level view, we will have orders and items like in a restaurant and we will perform some basic operations on them.

Firstly, let’s start by defining the Entities for the database.

1. Create an Item class in the package. (Right-click on the package folder and click new file)

import lombok.Data;
import org.springframework.cloud.gcp.data.spanner.core.mapping.*;

@Table(name="order_items")
@Data
class OrderItem {
  @PrimaryKey(keyOrder = 1)
  @Column(name="order_id")
  private String orderId;

  @PrimaryKey(keyOrder = 2)
  @Column(name="order_item_id")
  private String orderItemId;

  private String description;
  private Long quantity;
}

2. Create an Order class in the package. (Right-click on the package folder and click new file)

import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import org.springframework.cloud.gcp.data.spanner.core.mapping.*;

@Table(name="orders")
@Data
public class Order {
  @PrimaryKey
  @Column(name="order_id")
  private String id;

  private String description;

  @Column(name="creation_timestamp")
  private LocalDateTime timestamp;

  @Interleaved
  private List<OrderItem> items;
}

Now, we will create a Repository Interface to provive CRUD access through this interface.

3. Create an OrderRepository class in the package. (Right-click on the package folder and click new file)

import org.springframework.cloud.gcp.data.spanner.repository.*;
import org.springframework.stereotype.*;

@Repository
public interface OrderRepository extends SpannerRepository<Order, String> {
}

This interface extends SpannerRepository<Order, String> . Here, Order is the domain class and String is the Primary Key. Spring Data will automatically provide CRUD access through this interface.

Now, we will have to create a RestController for performing the basic operations. Create the GET and POST endpoints. To do this modify the main application file as shown below:

import java.time.LocalDateTime;
import java.util.UUID;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

@SpringBootApplication
public class DemoApplication {
        public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
        }
}

@RestController
class OrderController {
  private final OrderRepository orderRepository;

        OrderController(OrderRepository orderRepository) {
                this.orderRepository = orderRepository;
        }

        @GetMapping("/api/orders/{id}")
        public Order getOrder(@PathVariable String id) {
          return orderRepository.findById(id)
                                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, id + " not found"));
        }

        @PostMapping("/api/orders")
        public String createOrder(@RequestBody Order order) {
                order.setId(UUID.randomUUID().toString());
                order.setTimestamp(LocalDateTime.now());

                order.getItems().forEach(item -> {
                        item.setOrderId(order.getId());
                        item.setOrderItemId(UUID.randomUUID().toString());
                });
          Order saved = orderRepository.save(order);
          return saved.getId();
        }
}

We are done with the application now and it’s time to run a check it’s working.

Running the Application

Go to the Cloud Shell and run the application:

./mvnw spring-boot:run

This will run the application and it should listen to port 8080.

Now, we will poost an Order to the REST endpoint we just created:

curl -H"Content-Type: application/json" -d'{"description": "First Order", "items": [{"description": "Cheese Sandwich", "quantity": "1"}]}' \
  http://localhost:8080/api/orders

It should respond with the Order’s UUID. This can be used to retrieve the order from the endpoint.

curl http://localhost:8080/api/orders/"UUID"

To see this data on Cloud Spanner go to: Spanner -> Spanner Instance -> orders -> orders -> Data.

NOTE: When you are done with all the tasks please do not forget to delete the Cloud Spanner Instance to avoid additional charges.

You can delete Cloud Spanner Instance from console or even using Cloud Shell command:

gcloud spanner instances delete spanner-instance -q
Summary

In this blog, we created a basic Spring application that can save and get data from Google Cloud Spanner database.

For more awesome Tech Blogs, like this one, check out Knoldus Blogs.

References

All the contents in this blog are referred from Google Cloud Platform and Spring documentation and guides.

Written by 

Agnibhas Chattopadhyay is a Software Consultant at Knoldus Inc. He is very passionate about Technology and Basketball. Experienced in languages like C, C++, Java, Python and frameworks like Spring/Springboot, Apache Kafka and CI/CD tools like Jenkins, Docker. He loves sharing knowledge by writing easy to understand tech blogs.