Service discovery in Pivotal Cloud Foundry

In this blog post I am going to show you how to enable your Spring Boot apps to talk to each other in Pivotal Cloud Foundry without knowing each other’s physical address.

Recently PCF published a free service called Service Registry which allows you to dynamically discover and call registered services with minimal code changes. This service is backed up by Eureka, Netflix open source framework for locating services in the cloud.

What we are going to build today

To illustrate this, we are going to build 2 micro-services, a producer and a consumer. The producer will expose a web endpoint which will return a random translation to another language of the word “hello”. The consumer service will call every second the producer to get the word hello translated to a random language at each call.

Both apps are going to be registered to the PCF service registry so they can be discovered at runtime.

Creating the producer

Go to https://start.spring.io and setup an application named “producer”, for maven, Kotlin and with no dependencies. Click on “Generate project”.

Open the pom.xml from your downloaded project in your IDE.

Ensure the parent group points to the following starter dependency:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/>
</parent>

Add the following compile dependencies to the project:

<dependency>
<groupId>io.pivotal.spring.cloud</groupId>
<artifactId>spring-cloud-services-starter-service-registry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

then add the following dependencies to the dependencyManagement block:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.pivotal.spring.cloud</groupId>
<artifactId>spring-cloud-services-dependencies</artifactId>
<version>1.5.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Open the ProducerApplication.kt file that has been generated by the initializr website.

Create a GET mapping that returns a salutation in a random language:

@SpringBootApplication
@EnableDiscoveryClient
@RestController
class ProducerApplication {

@GetMapping("/hello")
fun getHello(): String = randomHello()

private fun randomHello(): String {
val values = "Hola,Kaixo,Guten Tag,Ciao,Namaste,ᎣᏏᏲ,Halito,Salute,Aluu,Bonjour".split(",")
return values[Random().nextInt(values.size)]
}
}

fun main(args: Array<String>) {
SpringApplication.run(ProducerApplication::class.java, *args)
}

Now open the application.yml (or application.properties) and add the following keys:

spring:
cloud:
services:
registrationMethod:
route

security:
basic:
enabled:
false

The last thing we need to do is to assign a name to our app, so it can be discovered by the consumer at runtime. Add a bootstrap.yml file in the same folder of the application.yml with the following key:

spring:
application:
name:
producer-app

Now the producer is capable to register to the PCF service registry backed by Eureka.

Deploying the producer

Let’s deploy the producer to PCF. To do this, go to the root folder of the maven project and create an executable:

$ mvn package

Now the jar artifact will be created under the target directory.

Login to PCF using the cf-cli and push the app:

$ cf push producer-app -p target/producer-0.0.1-SNAPSHOT.jar

Creating the consumer

Go to https://start.spring.io and setup an application named “consumer”, for maven, Kotlin and with no dependencies. Click on “Generate project”.

Open the pom.xml from your downloaded project in your IDE.

Ensure the parent group also looks also like this:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/>
</parent>

Add the spring cloud service compile dependency to the project:

<dependency>
<groupId>io.pivotal.spring.cloud</groupId>
<artifactId>spring-cloud-services-starter-service-registry</artifactId>
</dependency>

then add the following dependencies to the dependencyManagement block:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.pivotal.spring.cloud</groupId>
<artifactId>spring-cloud-services-dependencies</artifactId>
<version>1.5.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Open the ConsumerApplication.kt file that has been generated by the initializr website.

Let’s add the ability to the consumer to talk to the producer. Implement a bean that returns a CommandLineRunner and assign a timer that will call the producer app every second:

@SpringBootApplication
@EnableDiscoveryClient
class ConsumerApplication {

@Autowired
private lateinit var restTemplate: RestTemplate

@Bean
fun commandLineRunner(): CommandLineRunner {
return CommandLineRunner {
timer(name = "test", period = 1000, action = {
val uri = UriComponentsBuilder.fromUriString("//producer-app/hello").build().toUri()
val greeting = restTemplate.getForObject(uri, String::class.java)
println(greeting)
})
}
}

@Bean
@LoadBalanced
fun restTemplate(): RestTemplate = RestTemplate()
}

fun main(args: Array<String>) {
SpringApplication.run(ConsumerApplication::class.java, *args)
}

Notice two things:

  • the URI we use to call the producer is based on the name of the Spring Boot app, instead of the IP or physical address to the server where the producer is deployed:
val uri = UriComponentsBuilder.fromUriString("//producer-app/hello").build().toUri()
  • the RestTemplate is annotated as a load balanced rest template meaning that is equipped with a client-side load balancer.

This is a major improvement to the way services talk to each other in the cloud. You can now forget about passing the host name or IP address of the service you want to consume, instead you only need to know the name.

Deploying the consumer

Let’s deploy the consumer to PCF. To do this, go to the root folder of the maven project and create an executable:

$ mvn package

push the app to PCF:

$ cf push consumer-app -p target/consumer-0.0.1-SNAPSHOT.jar

Binding the discovery registry service

Ensure you are logged in to PCF using the cf-cli.

Add the Eureka Service Registry to your PCF instance, we will call it my-service-registry:

$ cf create-service p-service-registry standard my-service-registry

Now bind it to the producer app:

$ cf bind-service producer-app my-service-registry

And to the consumer app:

$ cf bind-service consumer-app my-service-registry

Now both the producer and consumer apps are discoverable and can talk to each other without knowing the physical location where they are deployed.

Now you are closer to a cloud native architecture

In addition, by using the Service Registry you will get load balancing for free, so no matter how many producer or consumer instances you deploy they will all register to the Eureka server and will use a built-in load balancer that does basic round-robin.

Having the ability to discover services gives by name instead of physical address brings you closer to a cloud native architecture. Now your apps can embrace the fact that in the cloud, because of its inherent nature, servers come and go and there is no longer the need to keep track of IP addresses and host names.