Building Microservices with Spring and Cloud Spanner

Develop and test your Spring applications with Google Cloud Spanner

Jonathan Manera
4 min readOct 1, 2022
Image by GCP Podcast

Google Cloud Spanner is a globally distributed relational database service. Using this solution, you can execute ACID transactions and use SQL semantics without compromising horizontal scaling and high availability.

In this article, we will go through how to develop and test a Spring Boot Kotlin application using Spanner (for free).

Why choosing Spanner?

If you are familiar with CAP theorem, you may know that any distributed data store can provide only two of the following three guarantees:

  • Consistency: Every read must return the most recent write .
  • Availability: Every read/write must have zero-error response.
  • Partition Tolerance: The system continues to operate despite delays or communication errors between nodes (network partitions).
Image by Author

ACID databases, such as Traditional Relational Databases, are Consistent and Available, but not Partition Tolerant.

BASE databases, like many NoSQL Databases, are Available and Partition Tolerant, but not Consistent.

Cloud Spanner’s private network makes it possible to provide Consistency and Availability, thus minimizing the chances of network partitioning (Partition Tolerance).

However, because partitions are possible, Spanner usually prioritises Consistency over Availability.

Spanner Emulator

Spanner Emulator is an in-memory database that you can use to develop and test your applications for free.

Install and run the emulator following these instructions:

  1. Make sure Docker is installed.
  2. Get the latest emulator image.
$ docker pull gcr.io/cloud-spanner-emulator/emulator

3. Start the emulator.

$ docker run -d --name spanner-emulator -p 9010:9010 -p 9020:9020 gcr.io/cloud-spanner-emulator/emulator

Once the emulator is running, you will have two endpoints available: localhost:9010 for gRPC communication, and localhost:9020 for REST.

Instances and Databases in Spanner

To use Spanner, you must create a Spanner instance. Instances provide resources to Spanner databases within these instances.

Create an instance and the database following these instructions:

  1. Make sure to have gcloud CLI installed.
  2. Create and activate the emulator configuration.
$ gcloud config configurations create emulator
$ gcloud config set auth/disable_credentials true

3. Set the configuration for your project.

$ gcloud config set project your-project-id

4. Override the endpoint from Spanner to localhost:9020.

$ gcloud config set api_endpoint_overrides/spanner http://localhost:9020/

5. Create an instance with the emulator configuration and one node.

$ gcloud spanner instances create test-instance \
--description="Test Instance" --config=emulator-config --nodes=1

6. Create a database within your instance.

$ gcloud spanner databases create test-database \
--instance test-instance

7. Check if the database has status READY.

$ gcloud spanner databases list --instance test-instance

Setting up the application

The Dependencies

First, add the required dependencies for the Spring Boot application. Make sure you have the dependencies listed below in your build.gradle.kt file.

implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("com.google.cloud:google-cloud-spanner:$spannerVersion")
implementation("com.google.cloud:google-cloud-spanner-jdbc:$spannerJdbcVersion")
implementation("com.google.cloud:google-cloud-spanner-hibernate-dialect:$hibernateDialectVersion")
implementation("com.google.cloudspannerecosystem:liquibase-spanner:$liquibaseVersion")

The Migration Tool

In order to create the database schema when the microservices are deployed, we are using Liquibase. With the Spanner Liquibase Extension, you can enable Liquibase to target Spanner.

In the path src/main/resources create a directory called db and within it, another directory called changelog.

Then, create a file called db.changelog-master.yaml. In this Changelog file, we will define Liquibase’s “changeSets”.

Spring will read the Database Changelog file from the location classpath:/db/changelog/db.changelog-master.yaml when the application is launched.

In this example, we are using YAML to design the schema following the Schema Design Best Practices for Spanner. In the db.changelog-master.yaml file, we have the table singer with the columns singer_id and singer_name:

databaseChangeLog:
- preConditions:
onFail: HALT
onError: HALT

- changeSet:
id: create-singers-table
author: spanner-examples
changes:
- createTable:
tableName: singer
columns:
- column:
name: singer_id
type: STRING(36)
constraints:
primaryKey: true
- column:
name: singer_name
type: STRING(255)
constraints:
nullable: false

The Datasource

Although Cloud Spanner has its integration with Spring Framework, we are using JDBC to connect the application with the Database. Set up the Datasource with the following properties:

your-project-id=test-spanner
your-instance=test-instance
your-database=test-database

# spanner datasource
spring.datasource.driver-class-name=com.google.cloud.spanner.jdbc.JdbcDriver
spring.jpa.properties.hibernate.dialect=com.google.cloud.spanner.hibernate.SpannerDialect
spring.datasource.url=jdbc:cloudspanner:/\
projects/${your-project-id}/\
instances/${your-instance}/\
databases/${your-database}?\
autoConfigEmulator=true

The Model

In the domain model, there is simply an Entity for the table singer and its JPA Repository:

@Entity
@Table(name = "singer")
class Singer(
@Id @GeneratedValue(generator = "UUID")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "singer_id") val id: String?,
@Column(name = "singer_name") val name: String = ""
)

interface SingerRepo : JpaRepository<Singer, String>

Testing the Application

For the integration tests, we are using Testcontainers for Spanner. This library allows to easily create the Spanner instance and database in a Docker container.

First, make sure you have the following test dependencies in your build.gradle.kt file.

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.testcontainers:testcontainers:$testcontainersVersion")
testImplementation("org.testcontainers:gcloud:$testcontainersVersion")
testImplementation("org.testcontainers:junit-jupiter:$testcontainersVersion")

In this example, we use a Companion Object that sets up the container with the Spanner Emulator, and creates the Spanner instance and the database for your tests:

Use the @Container annotation to create a Docker container with the container image version. We recommend you to stick with a specific image version for your tests, instead of latest.

Also, use the @DynamicPropertySource annotation to override the spring.datasource.url property with the URL pointing to the container.

Finally, we are creating a simple JUnit5 test, which injects the JPA Repository SingerRepo to store and fetch the data from the database.

Summary

In this article, we learned how to build and test your microservices using a cloud database solution that manages distributed data, provides data consistency and is also highly available, like Google Cloud Spanner.

Thanks for reading. I hope this was helpful!

The example code is available on GitHub.

--

--

Jonathan Manera

If you wish to make a Java app from scratch, you must first invent the universe.