#Labs for Spring Boot and Spring Cloud Welcome to Pivotal Cloud Native Workshop, we will use this repository for Spring Boot and Spring Cloud labs.
###Prerequisites
- CF command line tools installed and login
- Get a free PWS account, we don't mind you swipe your credit card too :)
- CURL or any browser-based REST client like this
- JDK 1.8
To save your time and resources, Config server, Eureka, Hystrix Dashboard and Zipkin have been always setup and configurated in both provider and consumer projects.
To set the proxy, please add below to mvnw or mvnw.cmd
set MAVEN_OPTS=-Dhttp.proxyHost=proxy.ha.org.hk -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.ha.org.hk -Dhttps.proxyPort=8080โ -Dproxy.user=user -Dproxy.password=pass
##Lab 1 - create a simple backend microservice, let's call it a provider app, with a database, a table, and RESTful API
-
Open
01-provider-init
with your favourite IDE -
Take a look on
pom.xml
, you will find below dependencies:
spring-boot-starter-actuator
for metrics, configuration, jmx, etcspring-cloud-starter-eureka
for service discoveryspring-boot-starter-data-rest
to quickly expose RESTful APIspring-cloud-starter-sleuth
for distributed tracingspring-cloud-starter-zipkin
for sending trace to zipkin server- mysql, h2
-
Add below annotation in the main class
@EnableDiscoveryClient // to register and be discoverable by others
-
Add a JPA Entity class named
Contact
with below attributes and a no argument constructor, I'm using@Data
from Lombok below, to make the code cleaner.@Entity // JPA @Data @NoArgsConstructor // Lombok class Contact{ @Id @GeneratedValue // simple JPA staff Long id; String name; String address; }
-
Add an
@RestResource
annotated interface extendsJpaRepository
to store theContact
class with aLong
typed key@RestResource // Standard JPA staff except this annotation. It saves you a lot of effort to create RESTful API. Thanks Spring Data Rest! interface ContactRepository extends JpaRepository<Contact, Long>{ // if you are familiar with JPA, feel free to add more method here, for example findAllByName. // it will be appeared in http://your-consumer.cfapps.io/contacts/search }
-
To avoid hostname conflict, in manifest.yml, replace the host with your name, for example
host: cna-provider-dwong
-
package the app (
mvnw.cmd
for windows)./mvnw clean package
-
Create an User Provided Service by
cf cups eureka-service -p '{"uri":"http://cna-eureka.cfapps.io"}'
-
run
cf push
to push the app (cf cli read the content in manifest.yml, save you a bit of typing) -
Check the Apps Manager
-
Check registry to see if your app is registered
-
try
/contacts
endpoint -
You can create a new record with
HTTP POST
to the same endpointcurl -X POST your-host.cfapps.io/contacts -H 'Content-type: application/json' -d '{"name":"any name", "address":"any address"}'
-
ADVANCE: The app is using an embedded H2 database, you may also try to create a MySQL database from marketplace and bind to the app.
cf create-service cleardb spark contact-db cf bind-service cna-provider contact-db cf restart cna-provider
-
Do you notice that it is not necessary to specify any host/port/credential of the database? The action bind-service does all of those, give you a 'It just works!' experience.
##Lab 2 - create another service to consume above microservice, let's call it consumer app
-
Open
02-consumer-init
-
Take a look on
pom.xml
, you can find below dependencies besides Lab 1's
spring-cloud-starter-hystrix
for circuit breakerspring-boot-starter-hateoas
for parsing HATEOAS responsespring-cloud-starter-feign
for easily consume provider service
-
Add below annotations
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) // because of HATEOAS format @EnableFeignClients // because we will use Feign Client to consume the provider @EnableCircuitBreaker // because we will use circuit breaker to protect our consumer app @EnableDiscoveryClient // well, because we will lookup the provider app from registry // that's all
-
Add
Contact
class with the same attributes as provider (Yes, in microservice architecture, you may need duplicating class definition. However, this give you a lot more flexibility, imagine a new attribute added in provider but you don't need in your consumer app, guess what? Nothing need to be done, we call it tolerence reader pattern!. Thinking your inhouse SOA?) -
Add Feign Client to integrate with provider
// The name within FeignClient is the service name registered in Eureka, // Feign Client will lookup the address(es) of provider. // Imagine there are multiple instances of provider sitting in multiple data center/cloud, // the client side load balancer (Ribbon) select the best one to consume. // If the circuit is open, the fallback class will be invoked. // Circuit open means something is wrong, close is normal, remember science class in sec school? // Not just try..catch..exception @FeignClient(name = "cna-provider", fallback = RemoteServiceFallback.class) interface RemoteService{ // The /contacts request mapping below is the HTTP endpoint of provider app // Feign Client expect Resources of Contact from this endpoint @RequestMapping("/contacts") Resources<Contact> getAllContacts(); }
-
Implement a fallback
// Fallback implementation is easy, // just create a class and implemnt the Feign Client interface, make sense?! @Component class RemoteServiceFallback implements RemoteService{ @Override public Resources<Contact> getAllContacts() { // just simple default thing here, // but you can do a lot more to gracefully degrade a service Contact defaultContact = new Contact(); defaultContact.setAddress("default"); defaultContact.setName("default"); defaultContact.setId(0L); return new Resources<>(Arrays.asList(defaultContact)); } }
-
Add an
RestController
and an method to consume the provider service@RestController class SimpleController{ // Thanks for Spring Boot autowiring capability, get you out of the XML hell. @Autowired RemoteService remoteService; // Map this method to HTTP endpoint /names @GetMapping("/names") public List<String> getAllNames(){ // Now, really consume the provider service by calling the Feign Client method // And you don't need to worry about cascade failure // Because this service is protected by circuit breaker! return remoteService.getAllContacts() .getContent() .stream() .map(c -> c.getName()) // simple Java 8 stream .collect(Collectors.toList()); } }
-
To avoid hostname conflict, in manifest.yml, replace the host with your name, for example
host: cna-consumer-dwong
-
package the app (
mvnw.cmd
for windows)./mvnw clean package
-
run
cf push
to push the app -
check registry to see if your app is registered
-
Open Hystrix dashboard here
-
Enter your Hystrix stream of this consumer app, for example
http://cna-provider-dwong.cfapps.io/hystrix.stream
-
You should see the Hystrix dashboard
-
Create some requests to the
/names
endpoint and check the Hystrix dashboard again -
Stop the provider app and continue sending request to
/names
endpoint to see the response and Hystrix dashboard -
Restart provide app to see what happen
##Lab 3 - Distrubuted Tracing
-
add a sampler in main class of both provider and consumer
@Bean public AlwaysSampler defaultSampler() { // use always sampler here, but in production environment you want not want to keep every single trace return new AlwaysSampler(); }
-
re-package and push for both provider and consumer
./mvnw clean package && cf push
-
Create request to consumer
/names
endpoint -
Take a look at Zipkin
Remarks:
- Feel free to contact me by [email protected] or [email protected] if any enquiry
- Help developer do what they love!