In this lab we’ll utilize Spring Boot and Spring Cloud to allow our application to register itself with a service registry. To do this we’ll also need to provision an instance of a Service Registry using Pivotal Cloud Foundry Spring Cloud Services, which is based on Eureka, Netflix’s Service Discovery server and client. We’ll also add a simple client application that looks up our application from the Service Registry and makes requests to our Cities
service.
-
These features are added by adding
spring-cloud-services-starter-service-registry
to the classpath. Add the following spring cloud services to your Maven project dependencies:cloud-native-spring/pom.xml<dependency> <groupId>io.pivotal.spring.cloud</groupId> <artifactId>spring-cloud-services-starter-service-registry</artifactId> </dependency>
-
Thanks to Spring Cloud, instructing your application to register with Service Registry is as simple as adding a single annotation to your app! Add an @EnableDiscoveryClient annotation to the
io.pivotal.cloudnativespring.CloudNativeSpringApplication
class:cloud-native-spring/src/main/java/io/pivotal/cloudnativespring/CloudNativeSpringApplication.java@SpringBootApplication @RestController @EnableJpaRepositories @EnableDiscoveryClient // <---- Add this @Import(RepositoryRestMvcAutoConfiguration.class) public class CloudNativeSpringApplication { [...] }
Completed:
package io.pivotal.cloudnativespring; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController @EnableJpaRepositories @EnableDiscoveryClient @Import(RepositoryRestMvcConfiguration.class) public class CloudNativeSpringApplication { @Value("${greeting:Hola}") private String greeting; public static void main(String[] args) { SpringApplication.run(CloudNativeSpringApplication.class, args); } @RequestMapping("/") public String hello() { return greeting + " World!"; } }
-
Now that our application is ready to read registry with a Service Registry instance, we need to deploy one! This can be done through Cloud Foundry using the services marketplace. Previously we did this through the Marketplace UI, but this time we will use the Cloud Foundry CLI (though we could also do this through the UI):
CN-Workshop/labs/my_work/cloud-native-spring $ cf create-service p-service-registry standard service-registry
-
After you create the service registry instance navigate to your Cloud Foundry space in the Apps Manager UI and refresh the page. You should now see the newly create
service-registry
instance. Select the manage link to view the registry dashboard. Note that there are not any registered applications at the moment: -
We will now bind our application to our service-registry within our Cloud Foundry deployment manifest. Add the additional reference the services list in our Cloud Foundry manifest:
cloud-native-spring/manifest.ymlservices: - config-server - service-registry # <-- Add this
Complete:
--- applications: - name: cloud-native-spring random-route: true memory: 768M path: target/cloud-native-spring-0.0.1-SNAPSHOT-exec.jar timeout: 180 env: JAVA_OPTS: -Djava.security.egd=file:///dev/urandom services: - config-server - service-registry
-
For the 2nd half of this lab we’ll need our
cloud-native-spring
Maven artifact accessible in our local Maven repository. Rebuild and install the artifact with the following command:CN-Workshop/labs/my_work/cloud-native-spring $ ./mvnw install
-
Push application into Cloud Foundry
CN-Workshop/labs/my_work/cloud-native-spring $ cf push
-
If we now test our application URLs we will no change. However, if we view the Service Registry dashboard (accessible from the Manage link in Apps Manager) you will see that a service named
CLOUD-NATIVE-SPRING
has registered: -
Next we’ll create a simple UI application that will read the service registry to discover the location of our cities REST service and connect.
-
Browse to Spring Initializr
-
Generate a
Maven Project
withJava
and Spring Boot1.5.9
-
Fill out the Project metadata fields as follows:
- Group
-
io.pivotal
- Artifact
-
cloud-native-spring-ui
-
In the dependencies section, add each of the following manually:
-
Vaadin
-
Actuator
-
Feign
-
Service Registry (PCF)
-
-
Click the Generate Project button and your browser will download the
cloud-native-spring-ui.zip
file. -
Copy then unpack the downloaded zip file to
CN-Workshop/labs/my_work/cloud-native-spring-ui
CN-Workshop/labs/my_work $ cp ~/Downloads/cloud-native-spring-ui.zip . CN-Workshop/labs/my_work $ unzip cloud-native-spring-ui.zip CN-Workshop/labs/my_work $ cd cloud-native-spring-ui
Your directory structure should now look like:
CN-Workshop: ├── labs │ ├── my_work │ │ ├── cloud-native-spring │ │ ├── cloud-native-spring-ui
-
Rename
application.properties
toapplication.yml
Spring Boot uses the
application.properties
/application.yml
file to specify various properties which configure the behavior of your application. By default, Spring Initializr (start.spring.io) creates a project with anapplication.properties
file, however, throughout this workshop we will be using YAML instead of Properties.CN-Workshop/labs/my_work/cloud-native-spring-ui $ mv src/main/resources/application.properties src/main/resources/application.yml
-
Import the project’s pom.xml into your editor/IDE of choice.
-
Because we politely asked Spring Initializr to include Service Registry (PCF), our Maven project has already been configured with the appropriate Spring Cloud Services dependencies:
cloud-native-spring-ui/pom.xml<project> [...] <dependencies> [...] <dependency> <groupId>io.pivotal.spring.cloud</groupId> <artifactId>spring-cloud-services-starter-service-registry</artifactId> </dependency> [...] </dependencies> [...] <dependencyManagement> <dependencies> [...] <dependency> <groupId>io.pivotal.spring.cloud</groupId> <artifactId>spring-cloud-services-dependencies</artifactId> <version>1.6.3.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Edgware.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> [...] </dependencies> </dependencyManagement> [...] </project>
-
To get a bit of code reuse, we’ll be using the
City
domain object from our maincloud-native-spring
Spring Boot application. We don’t want to pull in any of its transitive dependencies so we explicitly exclude them, however, we do still needspring-boot-starter-data-rest
to consume the/cities
service so we add that one in.Add the following to the Maven project dependencies:
cloud-native-spring-ui/pom.xml<project> [...] <dependencies> [...] <dependency> <groupId>io.pivotal</groupId> <artifactId>cloud-native-spring</artifactId> <version>0.0.1-SNAPSHOT</version> <exclusions> <exclusion> <groupId>*</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> [...] </dependencies> [...] </project>
Finally, for consistency’s sake, we’ll produce an exec classified artifact as we did for cloud-native-spring. Your build section should now include:
<project> [...] <build> <plugins> [...] <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>build-info</goal> </goals> </execution> </executions> <configuration> <classifier>exec</classifier> </configuration> </plugin> [...] </plugins> </build> [...] </project
-
Since this UI is going to consume REST services, its an awesome opportunity to use Feign. Feign will handle ALL the work of invoking our services and marshalling/unmarshalling JSON into domain objects. We’ll add a Feign Client interface into our app. Take note of how Feign references the downstream service; its only the name of the service it will lookup from Service Registry.
Add the following interface declaration to the
CloudNativeSpringUiApplication
class:@FeignClient("cloud-native-spring") public interface CityClient { @GetMapping(value = "/cities", consumes = "application/hal+json") Resources<City> getCities(); }
We’ll also need to add a few annotations to our boot application:
@SpringBootApplication @EnableFeignClients // <---- Add this @EnableDiscoveryClient // <---- And this public class CloudNativeSpringUiApplication {
Completed:
package io.pivotal.cloudnativespringui; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.hateoas.Resources; import org.springframework.web.bind.annotation.GetMapping; import io.pivotal.cloudnativespring.domain.City; @SpringBootApplication @EnableFeignClients @EnableDiscoveryClient public class CloudNativeSpringUiApplication { public static void main(String[] args) { SpringApplication.run(CloudNativeSpringUiApplication.class, args); } @FeignClient("cloud-native-spring") public interface CityClient { @GetMapping(value = "/cities", consumes = "application/hal+json") Resources<City> getCities(); } }
-
Next we’ll create a Vaadin UI for rendering our data. The point of this workshop isn’t to go into detail on creating UIs; for now suffice to say that Vaadin is a great tool for quickly creating User Interfaces. Our UI will consume our Feign client we just created. Create the
io.pivotal.cloudnativespringui.AppUI
class and paste the following code:cloud-native-spring-ui/src/main/java/io/pivotal/cloudnativespringui/AppUI.javapackage io.pivotal.cloudnativespringui; import java.util.ArrayList; import java.util.Collection; import org.springframework.beans.factory.annotation.Autowired; import com.vaadin.annotations.Theme; import com.vaadin.server.VaadinRequest; import com.vaadin.spring.annotation.SpringUI; import com.vaadin.ui.Grid; import com.vaadin.ui.UI; import io.pivotal.cloudnativespring.domain.City; import io.pivotal.cloudnativespringui.CloudNativeSpringUiApplication.CityClient; @SpringUI @Theme("valo") public class AppUI extends UI { private final CityClient cityClient; private final Grid<City> grid; @Autowired public AppUI(CityClient client) { this.cityClient = client; grid = new Grid<>(City.class); } @Override protected void init(VaadinRequest request) { setContent(grid); grid.setWidth(100, Unit.PERCENTAGE); grid.setHeight(100, Unit.PERCENTAGE); Collection<City> collection = new ArrayList<>(); cityClient.getCities().forEach(collection::add); grid.setItems(collection); } }
-
We’ll also want to give our UI App a name so that it can register properly with the Service Registry and potentially use cloud config in the future. The
spring-cloud-services-starter-service-registry
dependency pulls inspring-security
, so we’ll also need to disable security for our sample ui.Add the following to your Spring Boot configuration:
cloud-native-spring-ui/src/main/resources/application.ymlspring: application: name: cloud-native-spring-ui security: basic: enabled: false
-
Build the application.
CN-Workshop/labs/my_work/cloud-native-spring-ui $ ./mvnw package
-
Create a Cloud Foundry application manifest:
CN-Workshop/labs/my_work/cloud-native-spring-ui $ touch manifest.yml
Add application metadata:
--- applications: - name: cloud-native-spring-ui random-route: true memory: 768M path: target/cloud-native-spring-ui-0.0.1-SNAPSHOT-exec.jar env: JAVA_OPTS: -Djava.security.egd=file:///dev/urandom services: - service-registry
-
Push application into Cloud Foundry
CN-Workshop/labs/my_work/cloud-native-spring-ui $ cf push
-
Test your application by navigating to the root URL of the application, which will invoke Vaadin UI. You should now see a table listing the first set of rows returned from the cities microservice:
-
From a commandline stop the cloud-native-spring microservice (the original city service, not the new UI)
CN-Workshop/labs/my_work/cloud-native-spring $ cf stop cloud-native-spring
-
Refresh the UI app. What happens? Now you get a nasty error that is not very user friendly!
-
Next we’ll learn how to make our UI Application more resilient in the case that our downstream services are unavailable.