Connecting Spring Boot Applications to Distributed SQL with YugabyteDB Running on PKS

March 16, 2020 Tony Vetter

Nikhil Chandrappa, Ecosystem Software Engineer at Yugabyte, contributed to this blog post.

Modernizing monolithic applications dominated this past decade. Over the next decade, enterprises will increasingly need to look at modernizing Relational Database Management Systems (RDBMS) workloads to handle the scale that the increase in microservices has yielded.

Distributed SQL gives businesses a path toward modernizing this next application tier. App developers will benefit from the ease of linear scaling supported by distributed SQL databases. And they will still have that familiar spring data abstraction, which already has wide adoption in the developer community. 

In this blog, we’ll walk you through the steps of connecting a Spring Boot application with a cloud-native distributed SQL database powered by Yugabyte. This application uses the familiar Spring Data JPA repositories with a cluster-aware JDBC data source that Yugabyte recently released.

YugabyteDB YSQL and YCQL for Spring Data

What You Will Build

In this tutorial, you will install a Spring Boot application that exposes REST APIs for performing CRUD operations against a YugabyteDB cluster. This application will use Spring Data JPA and the YugabyteDB JDBC driver.

This application implements four API endpoints that store mock data about a customer (name, email, and customer ID). Once running, we will curl the REST APIs to add information to the database and query it. The application structure is simple. All the functionality is divided among five classes, which will be discussed below. 

What You Need

In order to complete the tutorial, you will need the following:

  • Access to a PKS cluster, with cluster requirements of, at minimum:

    • 1x master node (4 CPU, 16GB RAM, 32GB disk)

    • 4x worker nodes (8 CPU, 32GB RAM, 64GB disk)

    • Enabled privileged container access

  • Helm 2 and Tiller installed on the PKS cluster

  • Docker installed locally

  • Git CLI tools installed locally

  • JDK 1.8 or later

  • Maven 3.2+

  • A favorite text editor or IDE

  • A container repository such as Harbor or Docker Hub

  • About 15 minutes

Install YugabyteDB on PKS

To begin, we will install a three-node YugabyteDB cluster with a replication factor (RF) of three. At this RF level, the cluster can sustain a single node failure without data loss or downtime.

To install YugabyteDB on PKS, we first need to create an RBAC policy for the service, then initialize it using Helm. 

$ kubectl create -f https://raw.githubusercontent.com/YugaByte/charts/master/stable/yugabyte/yugabyte-rbac.yaml
 
$ helm init --service-account yugabyte-helm --upgrade --wait
 
Next, we will add the Yugabyte repository to Helm. These commands add the repository and refresh Helm’s caches.
$ helm repo add yugabytedb https://charts.yugabyte.com
$ helm repo update

Then we install YugabyteDB. 

$ helm install yugabytedb/yugabyte --wait --namespace yb-demo --name yb-demo --set "disableYsql=false"

Finally, we review the resources deployed by the Helm chart.

$ kubectl get all -n yb-demo

YugabyteDB is now installed on the PKS cluster. In the next section, we will clone the application from GitHub, then go over the classes that make up the integration and the functionality. 

Building the Application

This application was built using Spring Initializr. Spring Initializr both offers an easy way to pull in dependencies and builds the boilerplate application for you. We will go over the application classes that integrate it into YugabyteDB in detail.

For now, we’ll clone the repo from GitHub.

$ git clone https://github.com/yugabyte/spring-yugabytedb-demo.git

Now that we’ve downloaded the application, let’s explore how the integration is done. It consists of five classes, plus an additional dependency. Let’s take a look at how it  works.

Added Dependencies

The Spring Initializr does a great job of building out all of the needed dependencies. But the application needed one more that was not initially included. Here are the lines added in spring-yugabytedb-demo/pom.xml

<dependency>
  <groupId>com.yugabyte</groupId>
  <artifactId>ysql</artifactId>
  <version>42.2.7-yb-2</version>
</dependency>

This tells Maven the application is dependent on YugabyteDB YSQL, Yugabyte’s PostgreSQL-compliant API, and to download the necessary bits. 

Configuring the Connection

Spring Boot’s auto-reconfiguration mechanism will initialize the JDBC data source on startup. But without configuring, it will not know how to connect to the YugabyteDB instances created above.

YugabyteDB supports postgres dialect of Hibernate. Hence, within spring-yugabytedb-demo/src/main/resources/application.properties, we set spring.jpa.properties.hibernate.dialect to configure the JPA to use the PostgreSQL dialect.

Next, we set yugabyte.sql.datasource.url. This configures the data source URL to use the Kubernetes service DNS name of the YugabyteDB cluster, which will be running locally with this application.

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
yugabyte.sql.datasource.url=jdbc:postgresql://yb-tservers.yb-demo.svc.cluster.local:5433/yugabyte
 

 

Instantiate the Cluster-Aware JDBC DataSource

Now that the configurations are done, let’s look at the classes. This first class, located at spring-yugabytedb-demo/src/main/java/com/yugabyte/demo/springyugabytedbdemo/config/YugabyteDataSourceConfig.java, instantiates the cluster-aware data source using the address assigned previously in the application.properties file.

[...]
@Configuration
public class YugabyteDataSourceConfig {

  @Value("${yugabyte.sql.datasource.url}")
  String jdbcUrl;

  @Bean
  public DataSource dataSource() {
    return new YBClusterAwareDataSource(jdbcUrl);
  }
}

 

Define the Entity Model

Next, we need to create a class that will define a JDBC entity—in this case, a table for our customer information to be stored.

First, we create a new table entity “customer” within the class com.yugabyte.spring.ycql.demo.springycqldemo.domain.Customer, then define several attributes related to the customer. Finally, getter and setter functions are provided for those attributes.

[...]
@Entity
@Table(name = "customer")
public class Customer {

  @Id
  @javax.persistence.Id
  private String id;
  private String name;
  private String emailId;

[...]

 

Create the JPA Repository

The Spring Data JPA Repository provides consistent access patterns for working with databases. It considerably reduces the boilerplate code required to implement data access layers. Here we are extending JpaRepository for CRUD operations to be performed against the customer entity defined above.

In class com.yugabyte.spring.ycql.demo.springycqldemo.repo.CustomerRepository, the JpaRepository interface enables APIs for all the common operations Spring Data supports. For example, save(), findById(), findAll(), and deleteByID(). Here, we are simply instantiating a new repository.

[...]
@Repository
public interface CustomerRepository extends JpaRepository<Customer, String> {

  Customer findByEmailId(final String emailId);
}

 

Create the Controller Class

Finally, we examine the Controller class located at:

spring-yugabytedb-demo/src/main/java/com/yugabyte/demo/springyugabytedbdemo/controller/CustomerController.java.

This class defines our API and its behavior. 

[...]
@RestController
public class CustomerController {

  @Autowired
  private CustomerRepository customerRepository;

  @GetMapping("/customers")
  public List<Customer> getProducts() {
    return customerRepository.findAll();
  }

  @RequestMapping(method = RequestMethod.POST, value = "/customer/save",
        consumes = MediaType.APPLICATION_JSON_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
  public Customer createProductUsingSaveAPI(@RequestBody Customer customer) {
    return customerRepository.save(customer);
  }

  @GetMapping("/customer/{email}")
  public Customer findProductById(@PathVariable String email) {
    return customerRepository.findByEmailId(email);
  }

  @GetMapping("/customer/delete/{customerId}")
  public String deleteProductById(@PathVariable String customerId) {
      customerRepository.deleteById(customerId);
      return "Delete Success";
  }
}

In this controller, we are instantiating APIs for creating a new customer record, retrieving that record and the ability to delete it. 

Build the JAR and Generate the Docker Image

Now that we have seen all the code our app needs to run, it is time to build, package, and deploy the application on PKS.

First, using our IDE or toolset of choice, we build the application. If we use IntelliJ IDEA for MacOS, command+F9 will do this, but your IDE may vary the commands used.

Next, before we package the application, we set a variable for our container repository account name. This will make the next couple of commands easier to run.

$ export REGISTRY_UN=registry-username

Then we package the application into a Docker image.

$ ./mvnw com.google.cloud.tools:jib-maven-plugin:dockerBuild -Dimage=$REGISTRY_UN/spring-yugabytedb-demo:0.1v

Once the application is built, we push it to our registry.

$ ./mvnw com.google.cloud.tools:jib-maven-plugin:build -Dimage=$REGISTRY_UN/spring-yugabytedb-demo:0.1v

 

Deploy the Application on PKS

Now that the application is built and pushed to our registry, it’s time to install it onto the PKS cluster. Since the application is now just a container stored in a repository, this is straightforward to do in Kubernetes. 

$ kubectl run --image=$REPO_ACCOUNT/spring-yugabytedb-demo:0.1v spring-yugabytedb-demo --limits="cpu=1200m,memory=2Gi" --requests="cpu=1000m,memory=2Gi" --image-pull-policy=Always --port=8080

This installs the container onto the cluster and sets up some infrastructure limits. And since we are using cluster-aware drivers, the connection pool gets initialized to all the available servers in the cluster automatically. We can review the connection pool information from the application logs.

To use the service, we need to expose it and get a public IP. To do this, we run the kubectl expose command with type=LoadBalancer.

$ kubectl expose deployment spring-yugabytedb-demo --type=LoadBalancer --name=demo-service

To get the public-facing IP for the exposed service, we run the following. 

$ watch kubectl get service

This will return a list of services running on the cluster. The demo service will initially say the external IP is pending. When it shows the EXTERNAL-IP address, we save it as an environment variable.

$ export LOADBALANCER_IP=xxx.xxx.xxx.xxx

 

Using the Service

Now that the application is running and connected to the YugabyteDB instance, we will insert some data into the customer table. To do this, we run the following curl command, which will create a customer record for “Alice.” You can feel free to play with this and add multiple customer records, just make sure to insert unique IDs and emails for each customer. 

$ curl --location --request POST \
http://$LOADBALANCER_IP:8080/customer/save \
--header 'Content-Type: application/json' \
--data-raw '{"id":"customer1","name":"Alice","emailId":"alice@wonderland.com"}'

Next, we retrieve those records from the customer table with the following command. This should return a JSON object containing all uploaded customers, and all data about them. 

$ curl http://$LOADBALANCER_IP:8080/customers

Finally, we retrieve some customer information using only the customer email as an ID. This should also return a JSON object, but unlike the command above, this should be just for the single customer queried. 

$ curl http://$LOADBALANCER_IP:8080/customer/alice@wonderland.com

That’s it! This is now a functioning Spring application connected to a distributed SQL instance powered by Yugabyte. And it’s all running on PKS. There is more that can be done with this application, so go explore the code some more!

If you want to know more about Spring, check out the recently refreshed spring.io site! If you want to know more about YugabyteDB, and the ease of use it brings to distributed SQL and more, check out their site. And for more about Enterprise PKS, and the Tanzu suite of applications, check out tanzu.io.

About the Author

Tony Vetter

Tony Vetter is Associate Technical Product Marketing Manager at Pivotal.

More Content by Tony Vetter
Previous
Managing Kubernetes at Enterprise Scale: A Closer Look at Tanzu Mission Control
Managing Kubernetes at Enterprise Scale: A Closer Look at Tanzu Mission Control

Manage Kubernetes clusters across cloud with Tanzu Mission Control.

Next
What’s in a Name? How Pivotal’s Products Are Being Renamed as Part of VMware Tanzu
What’s in a Name? How Pivotal’s Products Are Being Renamed as Part of VMware Tanzu

The new VMware Tanzu portfolio includes products from Pivotal Software, which will be reflected in their ne...