The accompanying code for this workshop is on Github
microservices, for better or for worse, involve a lot of moving parts. Let's make sure we can run all those things in this lab.
- you will need JDK 8, Maven, an IDE and Docker in order to follow along. Specify important environment variables before opening any IDEs:
JAVA_HOME,DOCKER_HOST. - Install the Spring Boot CLI and the Spring Cloud CLI.
- Install the Cloud Foundry CLI
- go to the Spring Initializr and specify the latest milestone of Spring Boot 1.3 and then choose EVERY checkbox except those related to AWS, then click generate. In the shell, run
mvn -DskipTests=true clean installto force the resolution of all those dependencies so you're not stalled later. Then, runmvn clean installto force the resolution of the test scoped dependencies. You may discard this project after you'veinstalled everything. - run each of the
.shscripts in the./bindirectory; runpsql.shafter you've runpostgresh.shand confirm that they all complete and emit no obvious errors
in this lab we'll take a look at building a basic Spring Boot application that uses JPA and Spring Data REST. We'll look at how to start a new project, how Spring Boot exposes functionality, and how testing works.
- go to the Spring Initializr and select H2, REST Repositories, JPA, Vaadin, Web. Select the latest Spring Boot 1.3 RC1 version. give it an
artifactIdofreservation-service. - Run
mvn clean installand import it into your favorite IDE using Maven import. - add a simple entity (
Reservation) and a repository (ReservationRepository) - map the repository to the web by adding
org.springframework.boot:spring-boot-starter-data-restand then annotating the repository with@RepositoryRestResource - add custom Hypermedia links
- write a simple unit test
- what is Spring? Spring, fundamentally, is a dependency injection container. This detail is unimportant. What is important is that once Spring is aware of all the objects - beans - in an application, it can provide services to them to support different use cases like persistence, web services, web applications, messaging and integration, etc.
- why
.jars and not.wars? We've found that many organizations deploy only one, not many, application to one Tomcat/Jetty/whatever. They need to configure things like SSL, or GZIP compression, so they end up doing that in the container itself and - because they don't want the versioned configuration for the server to drift out of sync with the code, they end up version controlling the application server artifacts as well as the application itself! This implies a needless barrier between dev and ops which we struggle in every other place to remove. - how do I access the
by-namesearch endpoint? Follow the links! visithttp://localhost:8080/reservationsand scroll down and you'll see _link_s that connect you to related resources. You'll see one forsearch. Follow it, find the relevant finder method, and then follow its link.
Code complete != production ready! If you've ever read Michael Nygard's amazing tome, Release It!, then you know that the last mile between being code complete and being to production is much longer than anyone ever anticipates. In this lab, we'll look at how Spring Boot is optimized for the continuous delivery of applications into production.
- add
org.springframework.boot:spring-boot-starter-actuator - customize the
HealthEndpointby contributing a customHealthIndicator - start
./bin/graphite.sh - configure two environment variables
GRAPHITE_HOST(export GRAPHITE_HOST="$DOCKER_IP") andGRAPHITE_PORT(2003) (you may need to restart your IDE to see these new environment variables) - add a
GraphiteReporterbean - add
io.dropwizard.metrics:metrics-graphite - build an executable
.jar(UNIX-specific) using the<executable/>configuration flag - add the HAL browser -
org.springframework.data:spring-data-rest-hal-browserand view the Actuator endpoints using that - configure Maven resource filtering and the Git commit ID plugin in the
pom.xmlin all existing and subsequentpom.xmls, or extract out a common parentpom.xmlthat all modules may extend. - add
info.build.artifact=${project.artifactId}andinfo.build.version=${project.version}toapplication.properties. - introduce a new
@RepositoryEventHandlerand@Component. Provide handlers for@HandleAfterCreate,@HandleAfterSave, and@HandleAfterDelete. Extract common counters to a shared method - add a semantic metric using
CounterServiceand observe the histogram in Graphite
the 12 Factor manifesto speaks about externalizing that which changes from one environment to another - hosts, locators, passwords, etc. - from the application itself. Spring Boot readily supports this pattern, but it's not enough. In this lab, we'll loko at how to centralize, externalize, and dynamically update application configuration with the Spring Cloud Config Server.
- go to the Spring Initializr, choose the latest milestone of Spring Boot 1.3, specify an
artifactIdofconfig-serviceand addConfig Serverfrom the list of dependencies. - you should
git clonethe Git repository for this workshop - https://github.com/joshlong/bootiful-microservices-config - In the Config Server's
application.properties, specify that it should run on port 8888 (server.port=8888) and that it should manage the Git repository of configuration that lives in the root directory of thegit clone'd configuration. (spring.cloud.config.server.git.uri=...). - add
@EnableConfigServerto theconfig-serviceDemoApplication - Add
server.port=8888to theapplication.propertiesto ensure that the Config Server is running on the right port for service to find it. - add the Spring Cloud BOM (you can copy it from the Config Server) to the
reservation-service.
Add this to your pom.xml of the reservation-service from step #1.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Brixton.M1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- add
org.springframework.cloud:spring-cloud-starter-configto thereservation-service. - create a
boostrap.propertiesthat lives in the same place asapplication.propertiesand discard theapplication.propertiesfile. Instead, we now need only tell the Spring application where to find the Config Server, withspring.cloud.config.uri=${config.server:http://localhost:8888}, and how to identify itself to the Config Server and other services, later, withspring.application.name. - Run the Config Server
We'll copy and paste
bootstrap.propertiesfor each subsequent module, changing only thespring.application.nameas appropriate.
- In the
reservation-service, create aMessageRestControllerand annotate it with@RefreshScope. Inject the${message}key and expose it as a REST endpoint,/message. - trigger a refresh of the message using the
/refreshendpoint. - EXTRA CREDIT: start
./bin/rabbitmq.shand connect the microservice to the event bus using RabbitMQ and by adding theorg.springframework.cloud:spring-cloud-starter-bus-amqpthen triggering the refresh using the/bus/refresh.
In the cloud, applications live and die as capacity dictates, they're ephemeral. Applications should not be coupled to the physical location of other services as this state is fleeting. Indeed, even if it were fixed, services may quickly become overwhelmed, so it's very handy to be able to specify how to load balance among the available instances or indeed ask the system to verify that there are instances at all. In this lab, we'll look at the low-level
DiscoveryClientabstraction at the heart of Spring Cloud's service registration and discovery support.
- go to the Spring Initializr, select the
Eureka Server(this brings inorg.springframework.cloud:spring-cloud-starter-eureka-server) checkbox, name iteureka-serviceand then add@EnableEurekaServerto theDemoApplicationclass. - Make sure this module also talks to the Config Server as described in the last lab by adding the
org.springframework.cloud:spring-cloud-starter-config. - add
org.springframework.cloud:spring-cloud-starter-eurekato thereservation-service - add
@EnableDiscoveryClientto thereservation-service'sDemoApplicationand restart the process, and then confirm its appearance in the Eureka Server athttp://localhost:8761 - demonstrate using the
DiscoveryClientAPI - use the Spring Initializr, setup a new module,
reservation-client, that uses the Config Server (org.springframework.cloud:spring-cloud-starter-config), Eureka Discovery (org.springframework.cloud:spring-cloud-starter-eureka), and Web (org.springframework.boot:spring-boot-starter-web). - create a
bootstrap.properties, just as with the other modules, but name this onereservation-client. - create a
CommandLineRunnerthat uses theDiscoveryClientto look up other services programatically - EXTRA CREDIT: install Consul and replace Eureka with Consul. You could use
./bin/consul.sh, but prepare yourself for some confusion around host resolution if you're running Docker inside a Vagrant VM.
Edge services sit as intermediaries between the clients (smart phones, HTML5 applications, etc) and the service. An edge service is a logical place to insert any client-specific requirements (security, API translation, protocol translation) and keep the mid-tier services free of this burdensome logic (as well as free from associated redeploys!)
Proxy requests from an edge-service to mid-tier services with a microproxy. For some classes of clients, a microproxy and security (HTTPS, authentication) might be enough.
- add
org.springframework.cloud:spring-cloud-starter-zuuland@EnableZuulProxyto thereservation-client, then run it. - launch a browser and visit the
reservation-clientathttp://localhost:9999/reservation-service/reservations. This is proxying your request tohttp://localhost:8000/reservations.
API gateways are used whenever a client - like a mobile phone or HTML5 client - requires API translation. Perhaps the client requires coarser grained payloads, or transformed views on the data
- In the
reservation-service, create a client side DTO - namedReservation, perhaps? - to hold theReservationdata from the service. Do this to avoid being coupled between client and service - add
org.springframework.boot:spring-boot-starter-hateoas - add a REST service called
ReservationApiGatewayRestControllerthat uses the@Autowired @LoadBalanced RestTemplate rtto make a load-balanced call to a service in the registry using Ribbon. - map the controller itself to
/reservationsand then create a new controller handler method,getReservationNames, that's mapped to/names. - in the
getReservationNameshandler, make a call tohttp://reservation-service/reservationsusing theRestTemplate#exchangemethod, specifying the return value with aParameterizedTypeReference<Resources<Reservation>>>as the final argument to theRestTemplate#exchangemethod. - take the results of the call and map them from
ReservationtoReservation#getReservationName. Then, confirm thathttp://localhost:9999/reservations/namesreturns the names.
the code works, but it assumes that the
reservation-servicewill always be up and responding to requests. We need to be a bit more defensive in any code that clients will connect to. We'll use a circuit-breaker to ensure that thereservation-clientdoes something useful as a fallback when it can't connect to thereservation-service.
- add
org.springframework.boot:spring-boot-starter-actuatorandorg.springframework.cloud:spring-cloud-starter-hystrixto thereservation-client - add
@EnableCircuitBreakerto ourDemoApplicationconfiguration class - add
@HystrixCommandaround any potentially shaky service-to-service calls, likegetReservationNames, specifying a fallback method that returns an empty collection. - test that everything works by killing the
reservation-serviceand revisiting the/reservations/namesendpoint - go to the Spring Initializr and stand up a new service - with an
artifactIdofhystrix-dashboard- that uses Eureka Discovery, Config Client, and the Hystrix Dashboard. - identify it is as
hystrix-dashboardinbootstrap.propertiesand point it to config server. - annotate it with
@EnableHystrixDashboardand run it. You should be able to load it athttp://localhost:8010/hystrix.html. It will expect a heartbeat stream from any of the services with a circuit breaker in them. Give it the address from thereservation-client:http://localhost:9999/hystrix.stream
Spring Cloud helps you develop services that are resilient to failure - they're fault tolerant. If a service goes down, they'll degrade gracefully, and correctly expand to accommodate the available capacity. But who starts and stops these services? You need a platform for that. In this lab, we'll use a free trial account at Pivotal Web Services to demonstrate how to deploy, scale and heal our services.
- do a
mvn clean installto get binaries for each of the modules - comment out the
GraphiteReporterbean inreservation-service - remove the
<executable/>attribute from your Maven build plugin - make sure that each Maven build defines a
<finalName>element as:<finalName>${project.artifactId}</finalName>so that you can consistently refer to the built artifact from each Cloud Foundry manifest. - sign up for a free trial account at Pivotal Web Services.
- change the various
bootstrap.propertiesfiles to load configuration from an environment property,vcap.services.config-service.credentials.uri, or, if that's not available,http://localhost:8888:spring.cloud.config.uri=${vcap.services.config-service.credentials.uri:http://localhost:8888} cf loginthe Pivotal Web Services endpoint,api.run.pivotal.ioand then enter your credentials for the free trial account you've signed up for.- enable the Spring Boot actuator
/shutdownendpoint in the Config Serverapplication.properties:endpoints.shutdown.enabled=true - describe each service - its RAM, DNS
route, and required services - using amanifest.ymlfile collocated with each binary
you may generate the
manifest.ymlmanually or you may use a tool like Spring Tool Suite's Spring Boot Dashboard which will, on deploy, prompt you to save the deployment configuration as amanifest.yml.
- run
cf.shin thelabs/6folder to deploy the whole suite of services to Pivotal Web Services, OR: - follow the steps in
cf-simple.sh. This willcf pushtheeureka-serviceandconfig-service. It will usecf cupsto create services that are available toreservation-serviceandreservation-clientas environment variables, just like any other standard service. Then, it willcf pushreservation-clientandreservation-service, binding to those services. Seecf-simple.shfor details and comments - you should be able to follow along on Windows as well.
As you push new instances, you'll get new routes because of the configuration in the
manifest.ymlwhich specifies host is "...-${random-word}". When creating the user-provided-services (cf cups ..) be sure to choose only the first route. To delete orphaned routes, usecf delete-orphaned-routes
if you're running the
cf cupscommands, remember to quote and escape correctly, e.g.:cf cups "{ \"uri":\"..\" }"
cf scale -i 4 reservation-serviceto scale that single service to 4 instances. Call the/shutdownactuator endpoint forreservation-client:curl -d{} http://_RESERVATION_CLIENT_ROUTE_/shutdown, replacing_RESERVATION_CLIENT_ROUTE_.- observe that
cf appsrecords the downed, flapping service and eventually restores it. - observe that the configuration for the various cloud-specific backing services is handled in terms of various configuration files in the Config Server suffixed with
-cloud.properties.
if you need to delete an application, you can use
cf d _APP_NAME_, where_APP_NAME_is your application's logical name. If you want to delete a service, usecf ds _SERVICE_NAME_where_SERVICE_NAME_is a logical name for the service. Use-fto force the deletion without confirmation.
while REST is an easy, powerful approach to building services, it doesn't provide much in the way of guarantees about state. A failed write needs to be retried, requiring more work of the client. Messaging, on the other hand, guarantees that eventually the intended write will be processed. Eventual consistency works most of the time; even banks don't use distributed transactions! In this lab, we'll look at Spring Cloud Stream which builds atop Spring Integration and the messaging subsystem from Spring XD. Spring Cloud Stream provides the notion of binders that automatically wire up message egress and ingress given a valid connection factory and an agreed upon destination (e.g.:
reservationsororders).
- start
./bin/rabbitmq.sh.
This will install a RabbitMQ instance that is available at
$DOCKER_IP. You'll also be able to access the console, which is availablehttp://$DOCKER_IP:15672. The username and password to access the console areguest/guest.
- add
org.springframework.cloud:spring-cloud-starter-stream-rabbitto both thereservation-clientandreservation-service.
Sources - like water from a faucet - describe where messages may come from. In our example, messages come from the
reservation-clientthat wishes to write messages to thereservation-servicefrom the API gateway.
- add
@EnableBinding(Source.class)to thereservation-clientDemoApplication - create a new REST endpoint - a
POSTendpoint that accepts a@RequestBody Reservation reservation- in theReservationApiGatewayRestControllerto accept new reservations - observe that the
Source.classdescribes one or more SpringMessageChannels which are themselves annotated with useful qualifiers like@Output("output"). - in the new endpoint, inject the Spring
MessageChanneland qualify it with@Output("output")- the same one as in theSource.classdefinition. - use the
MessageChannelto send a message to thereservation-service. Connect the two modules through a agreed upon name, which we'll callreservations. - Observe that this is specified in the config server for us in the
reservation-servicemodule:spring.cloud.stream.bindings.output=reservations.outputis arbitrary and refers to the (arbitrary) channel of the same name described and referenced from theSource.classdefinition.
Sinks receive messages that flow to this service (like the kitchen sink into which water from the faucet flows).
- add
@EnableBinding(Sink.class)to thereservation-serviceDemoApplication - observe that the
Sink.classdescribes one or more SpringMessageChannels which are themselves annotated with useful qualifiers like@Input("input"). - create a new
@MessagingEndpointthat has a@ServiceActivator-annotated handler method to receive messages whose payload is of typeString, thereservationNamefrom thereservation-client. - use the
Stringto save newReservations using an injectedReservationRepository - Observe that this is specified in the config server for us in the
reservation-clientmodule:spring.cloud.stream.bindings.input=reservations.inputis arbitrary and refers to the (arbitrary) channel of the same name described in theSink.classdefinition.
Distributed tracing lets us trace the path of a request from one service to another. It's very useful in understanding where a failure is occurring in a complex chain of calls.
- run
./bin/zipkin.sh - add
org.springframework.cloud:spring-cloud-starter-zipkinto both thereservation-serviceand thereservation-client - configure a
@Beanof typeAlwaysSamplerfor both thereservation-serviceandreservation-client. - observe that as messages flow in and out of the
reservation-client, you can observe their correspondances and sequences in a waterfall graph in the ZipKin web UI athttp://$DOCKER_HOST:8080by drilling down to the service of choice. You can further drill down to see the headers and nature of the exchange between endpoints.
- add
org.springframework.cloud:spring-cloud-starter-oauth2to thereservation-client. - add
@EnableOAuthSso - observe that we've already pointed it to use GitHub for authentication in the config server's
application.properties - in the
reservation-client, create a new REST endpoint called/user/infoand use it to expose the authenticated principal to the authenticated client. - confirm this works by launching a new browser in incognito mode and then hitting the protected resource
- switch to a qualified
loadBalancedOauth2RestTemplateinstead of any old@RestTemplate. - EXTRA CREDIT: use Spring Security OAuth's Authroization Server instead of GitHub
- create a parent dependency that in turn defines all the Git Commit ID plugins, the executable jars, etc.
- package up common resources like
logstash.xml - create a new stereotypical and task-centric Maven
starterdependency that in turn brings in commonly used dependencies likeorg.springframework.cloud:spring-cloud-starter-zipkin,org.springframework.cloud:spring-cloud-starter-eureka,org.springframework.cloud:spring-cloud-starter-config,org.springframework.cloud:spring-cloud-starter-stream-binder-rabbit,org.springframework.boot:spring-boot-starter-actuator,net.logstash.logback:logstash-logback-encoder:4.2, - extract all the repeated code into auto-configuration: the
AlwaysSamplerbean,@EnableDiscoveryClient, the customHealthIndicators. - EXTRA CREDIT: define a Logger that is in turn a proxy that can only be injected using a custom qualfier (
@Logstash)
- run
./bin/elk.sh - add
net.logstash.logback:logstash-logback-encoder:4.2to thereservation-serviceandreservation-client - add
logback.xmlto each project'sresourcesdirectory. it should be configured to point to the value of$DOCKER_HOSTor some DNS entry - import
org.slf4j.Loggerandorg.slf4j.LoggerFactory - declare a logger:
Logger LOGGER = LoggerFactory.getLogger( DemoApplication.class); - in the
reservation-service, useLogstashMarkers to emit interesting semantic logs to be collected by the Kibana UI athttp://$DOCKER_HOST:...