├── spring-boot-webapp ├── src │ ├── main │ │ ├── resources │ │ │ ├── static │ │ │ │ ├── styles │ │ │ │ │ └── main.css │ │ │ │ ├── index.html │ │ │ │ └── js │ │ │ │ │ └── registration.js │ │ │ ├── config │ │ │ │ └── application.yml │ │ │ ├── logback.xml │ │ │ ├── dustjs │ │ │ │ └── dust-wrapper.js │ │ │ ├── dustjsviews │ │ │ │ ├── registrationconfirmation.dust │ │ │ │ └── register.dust │ │ │ └── dustjstemplates │ │ │ │ └── main_template.dust │ │ └── scala │ │ │ └── net │ │ │ └── chrisrichardson │ │ │ └── microservices │ │ │ └── restfulspringboot │ │ │ ├── backend │ │ │ ├── RegistrationError.scala │ │ │ ├── RegistrationBackendResponse.scala │ │ │ ├── RegistrationBackendRequest.scala │ │ │ ├── RegistrationService.scala │ │ │ ├── ScalaObjectMapper.scala │ │ │ ├── DiscoveryHealthIndicator.scala │ │ │ └── RegistrationServiceProxy.scala │ │ │ ├── dustview │ │ │ ├── DustViewResolver.scala │ │ │ ├── DustView.scala │ │ │ └── DustTemplateRenderer.scala │ │ │ ├── main │ │ │ └── UserRegistrationMain.scala │ │ │ ├── controllers │ │ │ └── UserRegistrationController.scala │ │ │ └── UserRegistrationConfiguration.scala │ └── test │ │ └── scala │ │ └── net │ │ └── chrisrichardson │ │ └── microservices │ │ └── restfulspringboot │ │ ├── UserRegistrationTestConfiguration.scala │ │ ├── MyPages.scala │ │ └── UserRegistrationWebIntegrationTest.scala ├── build-docker.sh ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── Dockerfile ├── README.md ├── build.gradle ├── gradlew.bat ├── pom.xml └── gradlew ├── spring-boot-restful-service ├── build-docker.sh ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ ├── main │ │ ├── resources │ │ │ ├── config │ │ │ │ └── application.yml │ │ │ └── logback.xml │ │ └── scala │ │ │ └── net │ │ │ └── chrisrichardson │ │ │ └── microservices │ │ │ └── restfulspringboot │ │ │ ├── backend │ │ │ ├── RegisteredUserRepository.scala │ │ │ ├── ScalaObjectMapper.scala │ │ │ └── RegisteredUser.scala │ │ │ ├── main │ │ │ └── UserRegistrationMain.scala │ │ │ ├── UserRegistrationConfiguration.scala │ │ │ ├── SwaggerConfig.scala │ │ │ └── controllers │ │ │ └── UserRegistrationController.scala │ └── test │ │ └── scala │ │ └── net │ │ └── chrisrichardson │ │ └── microservices │ │ └── restfulspringboot │ │ ├── UserRegistrationTestConfiguration.scala │ │ └── UserRegistrationWebIntegrationTest.scala ├── Dockerfile ├── README.md ├── build.gradle ├── gradlew.bat └── gradlew ├── e2e-test ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── build.gradle ├── gradlew.bat ├── src │ └── test │ │ └── scala │ │ └── net │ │ └── chrisrichardson │ │ └── microservices │ │ └── e2etest │ │ └── EndToEndTest.scala └── gradlew ├── mongodb-cli.sh ├── eureka-server ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── Dockerfile ├── src │ └── main │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ └── net │ │ └── chrisrichardson │ │ └── microservices │ │ └── eurekaserver │ │ └── EurekaServer.java ├── build.gradle ├── gradlew.bat └── gradlew ├── gradle-all.sh ├── zipkin-server ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ └── main │ │ ├── resources │ │ ├── logback.xml │ │ └── application.yml │ │ └── java │ │ └── net │ │ └── chrisrichardson │ │ └── microservices │ │ └── zipkinserver │ │ └── ZipkinServer.java ├── Dockerfile ├── build.gradle ├── gradlew.bat └── gradlew ├── clean-all.sh ├── wait-for-running-system.sh ├── register-user.sh ├── .gitignore ├── set-env.sh ├── run-e2e-test-images.sh ├── show-urls.sh ├── run-e2e-test.sh ├── LICENSE.txt ├── wait-for-services.sh ├── docker-compose-images.yml ├── Vagrantfile ├── docker-compose.yml └── README.md /spring-boot-webapp/src/main/resources/static/styles/main.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | Hello There -------------------------------------------------------------------------------- /spring-boot-webapp/build-docker.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | ./gradlew assemble 4 | docker build -t spring-boot-webapp . 5 | -------------------------------------------------------------------------------- /spring-boot-restful-service/build-docker.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | ./gradlew assemble 4 | docker build -t spring-boot-restful-service . 5 | -------------------------------------------------------------------------------- /e2e-test/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cer/microservices-examples/HEAD/e2e-test/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mongodb-cli.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | docker run --rm --network microservices-examples_default -i -t mongo:3.0.4 /usr/bin/mongo --host mongodb 4 | -------------------------------------------------------------------------------- /eureka-server/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cer/microservices-examples/HEAD/eureka-server/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle-all.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | for dir in spring-boot-* zipkin-server eureka-server ; do 4 | (cd $dir ; ./gradlew -b build.gradle $*) 5 | done 6 | -------------------------------------------------------------------------------- /zipkin-server/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cer/microservices-examples/HEAD/zipkin-server/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-webapp/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cer/microservices-examples/HEAD/spring-boot-webapp/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /clean-all.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | cd spring-boot-restful-service 6 | 7 | ./gradlew clean 8 | 9 | cd ../spring-boot-webapp 10 | 11 | ./gradlew clean 12 | -------------------------------------------------------------------------------- /spring-boot-restful-service/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cer/microservices-examples/HEAD/spring-boot-restful-service/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /wait-for-running-system.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | ./wait-for-services.sh ${DOCKER_HOST_IP?} /health 8080 8081 4 | ./wait-for-services.sh ${DOCKER_HOST_IP?} /eureka/apps/REGISTRATION-SERVICE 8761 5 | -------------------------------------------------------------------------------- /register-user.sh: -------------------------------------------------------------------------------- 1 | curl -v -d "$(echo '{"emailAddress": "fooSUFFIX@bar.com", "password" : "secret1234"}' | sed -e "s/SUFFIX/$(date +%s)/")" -H "content-type: application/json" http://${DOCKER_HOST_IP}:8081/user 2 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/resources/config/application.yml: -------------------------------------------------------------------------------- 1 | user_registration_url: http://localhost:8081/user 2 | 3 | eureka: 4 | client: 5 | registryFetchIntervalSeconds: 5 6 | serviceUrl: 7 | defaultZone: http://localhost:8761/eureka/ 8 | 9 | 10 | -------------------------------------------------------------------------------- /zipkin-server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/RegistrationError.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | trait RegistrationError 4 | case object DuplicateRegistrationError extends RegistrationError 5 | 6 | -------------------------------------------------------------------------------- /zipkin-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8u171-jre-alpine 2 | RUN apk --no-cache add curl 3 | HEALTHCHECK --start-period=30s --interval=5s CMD curl -f http://localhost:9411 || exit 1 4 | CMD java -jar zipkin-server.jar --server.port=9411 5 | EXPOSE 9411 6 | COPY build/libs/zipkin-server.jar . 7 | -------------------------------------------------------------------------------- /e2e-test/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 03 01:04:28 PST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.11-all.zip 7 | -------------------------------------------------------------------------------- /eureka-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8u171-jre-alpine 2 | RUN apk --no-cache add curl 3 | HEALTHCHECK --start-period=30s --interval=5s CMD curl -f http://localhost:8761/health || exit 1 4 | CMD java -jar eureka-server.jar --server.port=8761 5 | EXPOSE 8761 6 | COPY build/libs/eureka-server.jar . 7 | -------------------------------------------------------------------------------- /eureka-server/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 20 10:11:32 PDT 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.11-bin.zip 7 | -------------------------------------------------------------------------------- /zipkin-server/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 20 10:11:32 PDT 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.11-bin.zip 7 | -------------------------------------------------------------------------------- /spring-boot-webapp/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 03 01:04:28 PST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.11-all.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | .DS_Store 5 | .cache 6 | .classpath 7 | .gradle 8 | .idea 9 | .project 10 | .scala_dependencies 11 | .settings 12 | .springBeans 13 | bin 14 | build 15 | build.log 16 | classes 17 | genjs 18 | node_modules 19 | npm-debug.log 20 | target 21 | out 22 | .vagrant 23 | -------------------------------------------------------------------------------- /spring-boot-restful-service/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 05 20:39:18 PDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.11-all.zip 7 | -------------------------------------------------------------------------------- /eureka-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 3 | 4 | eureka: 5 | instance: 6 | hostname: localhost 7 | client: 8 | registerWithEureka: false 9 | fetchRegistry: false 10 | serviceUrl: 11 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 12 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/resources/config/application.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | serviceUrl: 4 | defaultZone: http://localhost:8761/eureka/ 5 | 6 | spring: 7 | application: 8 | name: registration-service 9 | data: 10 | mongodb: 11 | uri: mongodb://localhost/userregistration 12 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/RegistrationBackendResponse.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | /** 4 | * Created by cer on 5/30/16. 5 | */ 6 | case class RegistrationBackendResponse(id: String, emailAddress: String) 7 | -------------------------------------------------------------------------------- /spring-boot-webapp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8u171-jre-alpine 2 | RUN apk --no-cache add curl 3 | MAINTAINER chris@chrisrichardson.net 4 | EXPOSE 8080 5 | HEALTHCHECK --start-period=30s --interval=5s CMD curl -f http://localhost:8080/health || exit 1 6 | CMD java -jar spring-boot-webapp.jar 7 | COPY build/libs/spring-boot-webapp.jar . 8 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/RegistrationBackendRequest.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | /** 4 | * Created by cer on 5/30/16. 5 | */ 6 | case class RegistrationBackendRequest(emailAddress: String, password: String) 7 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/RegistrationService.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | trait RegistrationService { 4 | 5 | def registerUser(emailAddress: String, password : String) : Either[RegistrationError, String] 6 | 7 | } 8 | -------------------------------------------------------------------------------- /zipkin-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.application.name: zipkin 2 | 3 | logging: 4 | level.org.springframework.cloud: DEBUG 5 | 6 | server: 7 | port: 9411 8 | 9 | spring: 10 | rabbitmq: 11 | host: ${RABBIT_HOST:localhost} 12 | sleuth: 13 | enabled: false 14 | zipkin: 15 | store: 16 | type: mem -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /spring-boot-restful-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8u171-jre-alpine 2 | RUN apk --no-cache add curl 3 | MAINTAINER chris@chrisrichardson.net 4 | EXPOSE 8080 5 | HEALTHCHECK --start-period=30s --interval=5s CMD curl -f http://localhost:8080/health || exit 1 6 | CMD java -jar spring-boot-restful-service.jar 7 | COPY build/libs/spring-boot-restful-service.jar . 8 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/RegisteredUserRepository.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository 4 | 5 | trait RegisteredUserRepository extends MongoRepository[RegisteredUser, String] -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/resources/dustjs/dust-wrapper.js: -------------------------------------------------------------------------------- 1 | outputHolder = { 2 | setDone: function (javaCallback) { 3 | this.javaCallback = javaCallback; 4 | } 5 | }; 6 | 7 | myDustCallback = function (err, out) { 8 | outputHolder.err = err; 9 | outputHolder.out = out; 10 | outputHolder.javaCallback.done(err, out); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/resources/dustjsviews/registrationconfirmation.dust: -------------------------------------------------------------------------------- 1 | {>"main_template" title="Registration Complete"/} 2 | 3 | {Thanks for signing up to have your world changed! 6 | 7 |
An email has been sent to {emailAddress}
8 | 9 | 10 | {/bodyContent} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/ScalaObjectMapper.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.fasterxml.jackson.module.scala.DefaultScalaModule 5 | 6 | class ScalaObjectMapper extends ObjectMapper { 7 | 8 | registerModule(DefaultScalaModule) 9 | } -------------------------------------------------------------------------------- /spring-boot-webapp/README.md: -------------------------------------------------------------------------------- 1 | This project is a web application implemented using [Spring Boot](http://projects.spring.io/spring-boot/). 2 | It's the example code for the article [Building microservices with Spring Boot - part 2](http://plainoldobjects.com/2014/05/05/building-microservices-with-spring-boot-part-2/). 3 | 4 | To build and test this web application, please see the instructions in the parent README.md. 5 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/ScalaObjectMapper.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.fasterxml.jackson.module.scala.DefaultScalaModule 5 | 6 | class ScalaObjectMapper extends ObjectMapper { 7 | 8 | registerModule(DefaultScalaModule) 9 | } -------------------------------------------------------------------------------- /spring-boot-restful-service/README.md: -------------------------------------------------------------------------------- 1 | This project is a RESTful web service implemented using [Spring Boot](http://projects.spring.io/spring-boot/). 2 | It is the example code for the article [Building microservices with Spring Boot - part 1](http://plainoldobjects.com/2014/04/01/building-microservices-with-spring-boot-part1/). 3 | 4 | To build and test this web application, please see the instructions in the parent README.md. 5 | -------------------------------------------------------------------------------- /e2e-test/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'scala' 3 | dependencies { 4 | compile "org.scala-lang:scala-library:2.10.2" 5 | 6 | testCompile "junit:junit:4.11" 7 | testCompile "org.scalatest:scalatest_2.10:2.1.0" 8 | testCompile "org.seleniumhq.selenium:selenium-java:2.45.0" 9 | } 10 | 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | task wrapper(type: Wrapper) { 17 | gradleVersion = '2.11' 18 | } 19 | -------------------------------------------------------------------------------- /set-env.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | if [ -z "$DOCKER_HOST_IP" ] ; then 4 | if [ -z "$DOCKER_HOST" ] ; then 5 | export DOCKER_HOST_IP=`hostname` 6 | else 7 | echo using ${DOCKER_HOST?} 8 | XX=${DOCKER_HOST%\:*} 9 | export DOCKER_HOST_IP=${XX#tcp\:\/\/} 10 | fi 11 | fi 12 | 13 | export SPRING_RABBITMQ_HOST=${DOCKER_HOST_IP?} 14 | export SPRING_DATA_MONGODB_URI=mongodb://${DOCKER_HOST_IP?}/userregistration 15 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/dustview/DustViewResolver.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.dustview 2 | 3 | import org.springframework.web.servlet.view.AbstractTemplateViewResolver 4 | 5 | class DustViewResolver extends AbstractTemplateViewResolver { 6 | 7 | setViewClass(requiredViewClass()) 8 | 9 | override def requiredViewClass = classOf[DustView] 10 | 11 | } 12 | -------------------------------------------------------------------------------- /run-e2e-test-images.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | ./build-docker-images.sh 6 | 7 | docker-compose -f docker-compose-images.yml up -d 8 | 9 | ./wait-for-running-system.sh 10 | 11 | #echo -n Sleeping for service discovery ... 12 | #sleep 30 13 | #echo ... running 14 | 15 | set +e 16 | (cd e2e-test ; ./gradlew cleanTest test) 17 | set -e 18 | (cd e2e-test ; ./gradlew cleanTest test) 19 | 20 | docker-compose stop 21 | docker-compose rm -v --force 22 | -------------------------------------------------------------------------------- /show-urls.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | ./wait-for-running-system.sh 4 | 5 | echo The microservices are running 6 | echo You can visit these URLS 7 | echo http://${DOCKER_HOST_IP?}:8080/register.html - registration UI 8 | echo http://${DOCKER_HOST_IP?}:8761 - Eureka console 9 | echo http://${DOCKER_HOST_IP?}:8081/swagger-ui.html - the Backend Swagger UI 10 | echo http://${DOCKER_HOST_IP?}:9411 - Zipkin 11 | echo http://${DOCKER_HOST_IP?}:15672 - RabbitMQ admin - guest/guest login 12 | -------------------------------------------------------------------------------- /run-e2e-test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | docker-compose stop 6 | docker-compose rm -v --force 7 | 8 | docker-compose up -d 9 | 10 | ./wait-for-running-system.sh 11 | 12 | #echo -n Sleeping for service discovery ... 13 | #sleep 30 14 | #echo ... running 15 | 16 | ./register-user.sh 17 | 18 | set +e 19 | (cd e2e-test ; ./gradlew cleanTest test) 20 | set -e 21 | (cd e2e-test ; ./gradlew cleanTest test) 22 | 23 | docker-compose stop 24 | docker-compose rm -v --force 25 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/main/UserRegistrationMain.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.main 2 | 3 | import org.springframework.boot.SpringApplication 4 | import net.chrisrichardson.microservices.restfulspringboot.UserRegistrationConfiguration 5 | 6 | object UserRegistrationMain extends App { 7 | 8 | SpringApplication.run(classOf[UserRegistrationConfiguration], args: _ *) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/RegisteredUser.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | import org.springframework.data.mongodb.core.index.Indexed 4 | import org.springframework.data.mongodb.core.mapping.Document 5 | import scala.annotation.meta.field 6 | 7 | @Document 8 | case class RegisteredUser(id : String, @(Indexed@field)(unique = true) emailAddress : String, password : String) 9 | 10 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/main/UserRegistrationMain.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.main 2 | 3 | import org.springframework.boot.SpringApplication 4 | import net.chrisrichardson.microservices.restfulspringboot.UserRegistrationConfiguration 5 | 6 | object UserRegistrationMain { 7 | 8 | def main(args: Array[String]) : Unit = SpringApplication.run(classOf[UserRegistrationConfiguration], args :_ *) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /zipkin-server/src/main/java/net/chrisrichardson/microservices/zipkinserver/ZipkinServer.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.zipkinserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer; 6 | 7 | @SpringBootApplication 8 | @EnableZipkinStreamServer 9 | public class ZipkinServer { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ZipkinServer.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014 Chris Richardson 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /eureka-server/src/main/java/net/chrisrichardson/microservices/eurekaserver/EurekaServer.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.eurekaserver; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaServer 9 | public class EurekaServer { 10 | 11 | public static void main(String[] args) { 12 | new SpringApplicationBuilder(EurekaServer.class).web(true).run(args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /wait-for-services.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set +e 3 | 4 | done=false 5 | 6 | host=$1 7 | shift 8 | path=$1 9 | shift 10 | ports=$* 11 | 12 | while [[ "$done" = false ]]; do 13 | for port in $ports; do 14 | curl -q http://${host}:${port}${path} >& /dev/null 15 | if [[ "$?" -eq "0" ]]; then 16 | done=true 17 | else 18 | done=false 19 | break 20 | fi 21 | done 22 | if [[ "$done" = true ]]; then 23 | echo $path is available 24 | break; 25 | fi 26 | echo -n . 27 | sleep 1 28 | done 29 | 30 | set -e 31 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/test/scala/net/chrisrichardson/microservices/restfulspringboot/UserRegistrationTestConfiguration.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot 2 | 3 | import org.springframework.context.annotation.{Bean, Import, Configuration} 4 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter 5 | import org.springframework.web.client.RestTemplate 6 | import net.chrisrichardson.microservices.restfulspringboot.backend.ScalaObjectMapper 7 | import scala.collection.JavaConversions._ 8 | import java.util.concurrent.LinkedBlockingQueue 9 | 10 | @Configuration 11 | @Import(Array(classOf[UserRegistrationConfiguration])) 12 | class UserRegistrationTestConfiguration { 13 | 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/resources/static/js/registration.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | $("#registration-form").validate({ 4 | 5 | rules: { 6 | email: { 7 | required: true, 8 | email: true 9 | }, 10 | password: { 11 | required: true, 12 | minlength: 5 13 | } 14 | }, 15 | 16 | messages: { 17 | password: { 18 | required: "Please provide a password", 19 | minlength: "Your password must be at least 5 characters long" 20 | }, 21 | email: "Please enter a valid email address" 22 | }, 23 | 24 | submitHandler: function(form) { 25 | form.submit(); 26 | } 27 | }); 28 | 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /eureka-server/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | ext { 4 | springBootVersion = '1.4.0.RELEASE' 5 | } 6 | repositories { 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 11 | } 12 | } 13 | 14 | apply plugin: 'spring-boot' 15 | 16 | dependencyManagement { 17 | imports { 18 | mavenBom 'org.springframework.cloud:spring-cloud-starter-parent:Brixton.SR4' 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'org.springframework.cloud:spring-cloud-starter-eureka-server' 24 | } 25 | 26 | test { 27 | forkEvery 1 28 | } 29 | 30 | repositories { 31 | mavenCentral() 32 | maven { url 'http://repo.spring.io/milestone' } 33 | } 34 | 35 | task wrapper(type: Wrapper) { 36 | gradleVersion = '2.11' 37 | } 38 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/DiscoveryHealthIndicator.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | import java.net.URL 4 | 5 | import com.netflix.discovery.EurekaClient 6 | import org.springframework.beans.factory.annotation.Value 7 | import org.springframework.boot.actuate.health.{Health, HealthIndicator} 8 | 9 | class DiscoveryHealthIndicator(discoveryClient: EurekaClient) extends HealthIndicator{ 10 | 11 | @Value("${user_registration_url}") 12 | var userRegistrationUrl: String = _ 13 | 14 | def health() = { 15 | val virtualHost = new URL(userRegistrationUrl).getHost 16 | try { 17 | val instance = discoveryClient.getNextServerFromEureka(virtualHost, false) 18 | println("HealthCheckInstance=", instance) 19 | Health.up().withDetail("description", s"$virtualHost discovery").build() 20 | } catch { 21 | case e : RuntimeException => 22 | Health.down(e).withDetail("description", s"$virtualHost discovery").build() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/resources/dustjsviews/register.dust: -------------------------------------------------------------------------------- 1 | {>"main_template" title="Registration"/} 2 | 3 | {{.}{/globalErrors} 7 | 8 |
9 |
10 | 11 | 13 | {#fieldErrors.emailAddress}
{.}
{/fieldErrors.emailAddress} 14 |
15 | 16 |
17 | 18 | 20 | {#fieldErrors.password}
{.}
{/fieldErrors.password} 21 |
22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | {/bodyContent} 30 | 31 | 32 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/resources/dustjstemplates/main_template.dust: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {title} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 |
18 | 19 |
Changing what we think is the world - hell yeah!!
20 |
21 |
22 |
23 |
24 | {+bodyContent/} 25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/test/scala/net/chrisrichardson/microservices/restfulspringboot/MyPages.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot 2 | 3 | import org.scalatest.selenium.WebBrowser 4 | import org.openqa.selenium.WebDriver 5 | 6 | trait MyPages { 7 | 8 | this: WebBrowser => 9 | 10 | implicit val webDriver: WebDriver 11 | 12 | class RegistrationPage(baseUrl: String) extends Page { 13 | 14 | val url = baseUrl + "register.html" 15 | val title = "Registration" 16 | 17 | def fillAndSubmit(emailAddress: String, password: String) { 18 | 19 | emailField("emailAddress").value = emailAddress 20 | pwdField("password").value = password 21 | submit() 22 | } 23 | } 24 | 25 | // class registrationconfirmationPage(baseUrl: String)(implicit val webDriver: WebDriver) extends WebBrowser.Page with WebBrowser { 26 | class RegistrationConfirmationPage(baseUrl: String) extends Page { 27 | 28 | val url = baseUrl + "registrationconfirmation.html" 29 | 30 | val title = "Registration Complete" 31 | 32 | def registeredEmailAddress = find("registeredEmailAddress").get.text 33 | 34 | } 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /zipkin-server/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | ext { 4 | springBootVersion = '1.4.0.RELEASE' 5 | } 6 | repositories { 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 11 | } 12 | } 13 | 14 | apply plugin: 'spring-boot' 15 | 16 | dependencyManagement { 17 | imports { 18 | mavenBom 'org.springframework.cloud:spring-cloud-starter-parent:Brixton.SR5' 19 | } 20 | } 21 | 22 | configurations { 23 | all*.exclude group: "net.java.dev.jna", module: "jna" 24 | } 25 | 26 | dependencies { 27 | compile "org.springframework.boot:spring-boot-starter-web" 28 | compile "org.springframework.cloud:spring-cloud-sleuth-zipkin-stream" 29 | compile "io.zipkin.java:zipkin-autoconfigure-ui:1.0.0" 30 | compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit" 31 | compile 'net.java.dev.jna:jna-platform:4.2.2' 32 | } 33 | 34 | test { 35 | forkEvery 1 36 | } 37 | 38 | repositories { 39 | mavenCentral() 40 | maven { url 'http://repo.spring.io/milestone' } 41 | } 42 | 43 | task wrapper(type: Wrapper) { 44 | gradleVersion = '2.11' 45 | } 46 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/backend/RegistrationServiceProxy.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.backend 2 | 3 | import com.netflix.hystrix.contrib.javanica.annotation.{HystrixProperty, HystrixCommand} 4 | import org.springframework.beans.factory.annotation.{Autowired, Value} 5 | import org.springframework.http.HttpStatus 6 | import org.springframework.stereotype.Component 7 | import org.springframework.web.client.{HttpClientErrorException, RestTemplate} 8 | 9 | @Component 10 | class RegistrationServiceProxy @Autowired()(restTemplate: RestTemplate) extends RegistrationService { 11 | 12 | @Value("${user_registration_url}") 13 | var userRegistrationUrl: String = _ 14 | 15 | @HystrixCommand(commandProperties=Array(new HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="800"))) 16 | override def registerUser(emailAddress: String, password: String): Either[RegistrationError, String] = { 17 | try { 18 | val response = restTemplate.postForEntity(userRegistrationUrl, 19 | RegistrationBackendRequest(emailAddress, password), 20 | classOf[RegistrationBackendResponse]) 21 | response.getStatusCode match { 22 | case HttpStatus.OK => 23 | Right(response.getBody.id) 24 | } 25 | } catch { 26 | case e: HttpClientErrorException if e.getStatusCode == HttpStatus.CONFLICT => 27 | Left(DuplicateRegistrationError) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docker-compose-images.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | rabbitmq: 5 | image: rabbitmq:3.5.3-management 6 | ports: 7 | - "5672:5672" 8 | - "15672:15672" 9 | 10 | eureka: 11 | build: ./eureka-server 12 | ports: 13 | - "8761:8761" 14 | 15 | zipkin: 16 | build: ./zipkin-server 17 | ports: 18 | - "9411:9411" 19 | depends_on: 20 | - rabbitmq 21 | environment: 22 | RABBIT_HOST: rabbitmq 23 | 24 | mongodb: 25 | image: mongo:3.0.4 26 | ports: 27 | - "27017:27017" 28 | command: mongod --smallfiles 29 | 30 | restfulservice: 31 | image: spring-boot-restful-service 32 | ports: 33 | - "8081:8080" 34 | depends_on: 35 | - rabbitmq 36 | - mongodb 37 | - eureka 38 | environment: 39 | SPRING_DATA_MONGODB_URI: mongodb://mongodb/userregistration 40 | SPRING_RABBITMQ_HOST: rabbitmq 41 | SPRING_APPLICATION_NAME: registration-service 42 | EUREKA_INSTANCE_PREFER_IP_ADDRESS: "true" 43 | SPRING_PROFILES_ACTIVE: enableEureka 44 | EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka:8761/eureka/ 45 | 46 | web: 47 | image: spring-boot-webapp 48 | ports: 49 | - "8080:8080" 50 | depends_on: 51 | - rabbitmq 52 | - mongodb 53 | - eureka 54 | environment: 55 | USER_REGISTRATION_URL: http://REGISTRATION-SERVICE/user 56 | SPRING_APPLICATION_NAME: registration-web 57 | EUREKA_INSTANCE_PREFER_IP_ADDRESS: "true" 58 | EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka:8761/eureka/ 59 | SPRING_PROFILES_ACTIVE: enableEureka 60 | -------------------------------------------------------------------------------- /spring-boot-restful-service/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | ext { 4 | springBootVersion = '1.3.5.RELEASE' 5 | } 6 | repositories { 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 11 | } 12 | } 13 | 14 | apply plugin: 'scala' 15 | apply plugin: 'spring-boot' 16 | 17 | dependencyManagement { 18 | imports { 19 | mavenBom 'org.springframework.cloud:spring-cloud-starter-parent:Brixton.SR5' 20 | } 21 | } 22 | 23 | dependencies { 24 | compile "org.scala-lang:scala-library:2.10.2" 25 | compile 'com.fasterxml.jackson.module:jackson-module-scala_2.10:2.6.6' 26 | 27 | compile "org.springframework.boot:spring-boot-starter-web" 28 | compile "org.springframework.boot:spring-boot-starter-data-mongodb" 29 | compile "org.springframework.boot:spring-boot-starter-amqp" 30 | compile "org.springframework.boot:spring-boot-starter-actuator" 31 | 32 | // Spring Cloud Sleuth 33 | compile "org.springframework.cloud:spring-cloud-sleuth-stream" 34 | compile "org.springframework.cloud:spring-cloud-starter-sleuth" 35 | compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit" 36 | // Spring Cloud Sleuth 37 | 38 | compile 'org.springframework.cloud:spring-cloud-starter-eureka' 39 | compile "io.springfox:springfox-swagger2:2.1.2" 40 | compile 'io.springfox:springfox-swagger-ui:2.1.2' 41 | 42 | testCompile "org.springframework.boot:spring-boot-starter-test" 43 | } 44 | 45 | test { 46 | forkEvery 1 47 | } 48 | 49 | repositories { 50 | mavenCentral() 51 | maven { url 'http://repo.spring.io/milestone' } 52 | } 53 | 54 | task wrapper(type: Wrapper) { 55 | gradleVersion = '2.11' 56 | } 57 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | 6 | config.vm.box = "ubuntu/trusty64" 7 | 8 | config.vm.provider "virtualbox" do |v| 9 | v.memory = 2048 10 | v.cpus = 2 11 | config.vm.network "forwarded_port", guest: 8080, host: 8080 12 | config.vm.network "forwarded_port", guest: 8081, host: 8081 13 | config.vm.network "forwarded_port", guest: 5672, host: 5672 14 | config.vm.network "forwarded_port", guest: 27017, host: 27017 15 | config.vm.network "forwarded_port", guest: 8761, host: 8761 16 | end 17 | 18 | config.vm.provision "shell", inline: <<-SHELL 19 | #!/bin/sh 20 | 21 | # https://github.com/pussinboots/vagrant-devel/blob/master/provision/packages/java8.sh 22 | 23 | if which java >/dev/null; then 24 | echo "skip java 8 installation" 25 | else 26 | echo "java 8 installation" 27 | apt-get install --yes python-software-properties 28 | add-apt-repository ppa:webupd8team/java 29 | apt-get update -qq 30 | echo debconf shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections 31 | echo debconf shared/accepted-oracle-license-v1-1 seen true | /usr/bin/debconf-set-selections 32 | apt-get install --quiet --yes oracle-java8-installer 33 | yes "" | apt-get -f install 34 | fi 35 | SHELL 36 | 37 | config.vm.provision "docker" do |d| 38 | end 39 | 40 | config.vm.provision "shell", inline: <<-SHELL 41 | if which docker-compose >/dev/null; then 42 | echo "skip docker-compose installation" 43 | else 44 | sudo bash -c "curl -L https://github.com/docker/compose/releases/download/1.6.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose ; chmod +x /usr/local/bin/docker-compose" 45 | fi 46 | SHELL 47 | 48 | end 49 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/UserRegistrationConfiguration.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import net.chrisrichardson.microservices.restfulspringboot.backend.ScalaObjectMapper 5 | import org.springframework.amqp.core.TopicExchange 6 | import org.springframework.amqp.rabbit.connection.ConnectionFactory 7 | import org.springframework.amqp.rabbit.core.RabbitTemplate 8 | import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter 9 | import org.springframework.boot.autoconfigure.SpringBootApplication 10 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient 11 | import org.springframework.context.annotation._ 12 | import org.springframework.cloud.sleuth.sampler.AlwaysSampler 13 | import org.springframework.cloud.sleuth.Sampler 14 | 15 | @SpringBootApplication 16 | class UserRegistrationConfiguration { 17 | 18 | import MessagingNames._ 19 | 20 | @Bean 21 | @Primary 22 | def scalaObjectMapper() = new ScalaObjectMapper 23 | 24 | @Bean 25 | def rabbitTemplate(connectionFactory : ConnectionFactory, objectMapper : ObjectMapper) = { 26 | val template = new RabbitTemplate(connectionFactory) 27 | val jsonConverter = new Jackson2JsonMessageConverter 28 | jsonConverter.setJsonObjectMapper(scalaObjectMapper()) 29 | template.setMessageConverter(jsonConverter) 30 | template 31 | } 32 | 33 | @Bean 34 | def userRegistrationsExchange() = new TopicExchange(exchangeName) 35 | 36 | @Bean 37 | def sampler() : Sampler = new AlwaysSampler() 38 | 39 | } 40 | 41 | @Configuration 42 | @EnableEurekaClient 43 | @Profile(Array("enableEureka")) 44 | class EurekaClientConfiguration { 45 | 46 | } 47 | 48 | object MessagingNames { 49 | val queueName = "user-registration" 50 | val routingKey = queueName 51 | val exchangeName = "user-registrations" 52 | 53 | } 54 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/SwaggerConfig.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot 2 | 3 | 4 | import com.fasterxml.jackson.module.scala.DefaultScalaModule 5 | import com.google.common.base.Predicate 6 | import org.springframework.context.ApplicationListener 7 | import org.springframework.context.annotation.{Bean, Configuration} 8 | import org.springframework.web.context.request.async.DeferredResult 9 | import springfox.documentation.builders.ApiInfoBuilder 10 | import springfox.documentation.builders.PathSelectors.regex 11 | import springfox.documentation.schema.configuration.ObjectMapperConfigured 12 | import springfox.documentation.service.ApiInfo 13 | import springfox.documentation.spi.DocumentationType 14 | import springfox.documentation.spring.web.plugins.Docket 15 | import springfox.documentation.swagger2.annotations.EnableSwagger2 16 | 17 | @Configuration 18 | @EnableSwagger2 19 | class SwaggerConfig { 20 | 21 | 22 | @Bean 23 | def swaggerPaths(): Predicate[String] = regex("/user.*") 24 | 25 | @Bean 26 | def apiInfo(): ApiInfo = { 27 | new ApiInfoBuilder() 28 | .title("Spring Boot rest service") 29 | .description("user registration API") 30 | .contact("Chris Richardson") 31 | .license("Apache License, Version 2.0") 32 | .version("1.0") 33 | .build() 34 | } 35 | 36 | @Bean 37 | def swaggerSpringMvcPlugin(pathPredicate: Predicate[String], apiInfo: ApiInfo): Docket = { 38 | new Docket(DocumentationType.SWAGGER_2) 39 | .genericModelSubstitutes(classOf[DeferredResult[_]]) 40 | .apiInfo(apiInfo) 41 | .select() 42 | .paths(pathPredicate) 43 | .build() 44 | } 45 | 46 | @Bean 47 | def objectMapperInitializer = new ApplicationListener[ObjectMapperConfigured] { 48 | def onApplicationEvent(event: ObjectMapperConfigured) = event.getObjectMapper.registerModule(DefaultScalaModule) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /spring-boot-webapp/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | ext { 4 | springBootVersion = '1.3.5.RELEASE' 5 | } 6 | repositories { 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 11 | } 12 | } 13 | 14 | apply plugin: 'scala' 15 | apply plugin: 'spring-boot' 16 | apply plugin: "io.spring.dependency-management" 17 | 18 | dependencyManagement { 19 | imports { 20 | mavenBom 'org.springframework.cloud:spring-cloud-starter-parent:Brixton.SR5' 21 | } 22 | } 23 | 24 | dependencies { 25 | compile "org.scala-lang:scala-library:2.10.2" 26 | compile 'com.fasterxml.jackson.module:jackson-module-scala_2.10:2.6.6' 27 | compile 'org.apache.commons:commons-io:1.3.2' 28 | 29 | compile "org.springframework.boot:spring-boot-starter-web" 30 | compile "org.springframework.boot:spring-boot-starter-actuator" 31 | 32 | // Spring Cloud Sleuth 33 | compile "org.springframework.cloud:spring-cloud-sleuth-stream" 34 | compile "org.springframework.cloud:spring-cloud-starter-sleuth" 35 | compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit" 36 | // Spring Cloud Sleuth 37 | 38 | compile 'org.springframework.cloud:spring-cloud-starter-eureka' 39 | compile 'org.springframework.cloud:spring-cloud-starter-hystrix' 40 | compile 'org.webjars:bootstrap:3.1.1' 41 | compile 'org.webjars:jquery-validation:1.11.1' 42 | compile 'javax.validation:validation-api:1.1.0.Final' 43 | compile 'org.hibernate:hibernate-validator:5.0.3.Final' 44 | 45 | testCompile "org.springframework.boot:spring-boot-starter-test" 46 | testCompile "org.scalatest:scalatest_2.10:2.1.0" 47 | testCompile "junit:junit:4.11" 48 | testCompile "org.seleniumhq.selenium:selenium-java:2.45.0" 49 | } 50 | 51 | 52 | repositories { 53 | mavenCentral() 54 | maven { url 'http://repo.spring.io/milestone' } 55 | } 56 | 57 | task wrapper(type: Wrapper) { 58 | gradleVersion = '2.11' 59 | } 60 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | rabbitmq: 5 | image: rabbitmq:3.5.3-management 6 | ports: 7 | - "5672:5672" 8 | - "15672:15672" 9 | 10 | mongodb: 11 | image: mongo:3.0.4 12 | ports: 13 | - "27017:27017" 14 | command: mongod --smallfiles 15 | 16 | eureka: 17 | build: ./eureka-server 18 | ports: 19 | - "8761:8761" 20 | 21 | zipkin: 22 | build: ./zipkin-server 23 | ports: 24 | - "9411:9411" 25 | depends_on: 26 | - rabbitmq 27 | environment: 28 | RABBIT_HOST: rabbitmq 29 | 30 | 31 | restfulservice: 32 | build: ./spring-boot-restful-service 33 | ports: 34 | - "8081:8080" 35 | depends_on: 36 | - rabbitmq 37 | - mongodb 38 | - eureka 39 | - zipkin 40 | environment: 41 | SPRING_DATA_MONGODB_URI: mongodb://mongodb/userregistration 42 | SPRING_RABBITMQ_HOST: rabbitmq 43 | SPRING_APPLICATION_NAME: registration-service 44 | SPRING_PROFILES_ACTIVE: enableEureka 45 | EUREKA_INSTANCE_PREFER_IP_ADDRESS: "true" 46 | EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka:8761/eureka/ 47 | SPRING_SLEUTH_ENABLED: "true" 48 | SPRING_SLEUTH_SAMPLER_PERCENTAGE: 1 49 | 50 | web: 51 | build: ./spring-boot-webapp 52 | ports: 53 | - "8080:8080" 54 | depends_on: 55 | - restfulservice 56 | - zipkin 57 | - eureka 58 | environment: 59 | USER_REGISTRATION_URL: http://REGISTRATION-SERVICE/user 60 | SPRING_APPLICATION_NAME: registration-web 61 | SPRING_PROFILES_ACTIVE: enableEureka 62 | EUREKA_INSTANCE_PREFER_IP_ADDRESS: "true" 63 | EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka:8761/eureka/ 64 | SPRING_RABBITMQ_HOST: rabbitmq 65 | SPRING_SLEUTH_ENABLED: "true" 66 | SPRING_SLEUTH_SAMPLER_PERCENTAGE: 1 67 | SPRING_SLEUTH_WEB_SKIPPATTERN: "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|/favicon.ico|/hystrix.stream" 68 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/controllers/UserRegistrationController.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.controllers 2 | 3 | import javax.validation.constraints.{Size, NotNull} 4 | 5 | import org.hibernate.validator.constraints.Email 6 | import org.springframework.validation.annotation.Validated 7 | import org.springframework.web.bind.annotation._ 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.http.HttpStatus 10 | import org.springframework.amqp.rabbit.core.RabbitTemplate 11 | import net.chrisrichardson.microservices.restfulspringboot.MessagingNames 12 | import net.chrisrichardson.microservices.restfulspringboot.backend.{RegisteredUserRepository, RegisteredUser} 13 | import org.springframework.dao.DuplicateKeyException 14 | 15 | import scala.beans.BeanProperty 16 | 17 | 18 | @RestController 19 | class UserRegistrationController @Autowired()(registeredUserRepository: RegisteredUserRepository, rabbitTemplate: RabbitTemplate) { 20 | 21 | import MessagingNames._ 22 | 23 | @RequestMapping(value = Array("/user"), method = Array(RequestMethod.POST)) 24 | def registerUser(@Validated @RequestBody request: RegistrationRequest) = { 25 | val registeredUser = new RegisteredUser(null, request.emailAddress, request.password) 26 | registeredUserRepository.save(registeredUser) 27 | rabbitTemplate.convertAndSend(exchangeName, routingKey, NewRegistrationNotification(registeredUser.id, request.emailAddress, request.password)) 28 | RegistrationResponse(registeredUser.id, request.emailAddress) 29 | } 30 | 31 | @ResponseStatus(value = HttpStatus.CONFLICT, reason = "duplicate email address") 32 | @ExceptionHandler(Array(classOf[DuplicateKeyException])) 33 | def duplicateEmailAddress() {} 34 | 35 | 36 | } 37 | 38 | class RegistrationRequest { 39 | 40 | @BeanProperty 41 | @Email 42 | @NotNull 43 | var emailAddress: String = _ 44 | 45 | @BeanProperty 46 | @NotNull 47 | @Size(min = 8, max = 30) 48 | var password: String = _ 49 | 50 | } 51 | 52 | case class RegistrationResponse(id: String, emailAddress: String) 53 | 54 | 55 | case class NewRegistrationNotification(id: String, emailAddress: String, password: String) 56 | 57 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/test/scala/net/chrisrichardson/microservices/restfulspringboot/UserRegistrationTestConfiguration.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot 2 | 3 | import org.springframework.context.annotation.{Bean, Import, Configuration} 4 | import org.springframework.amqp.core.{BindingBuilder, TopicExchange, Queue} 5 | import org.springframework.amqp.rabbit.connection.ConnectionFactory 6 | import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter 7 | import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer 8 | import MessagingNames._ 9 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter 10 | import org.springframework.web.client.RestTemplate 11 | import net.chrisrichardson.microservices.restfulspringboot.backend.ScalaObjectMapper 12 | import scala.collection.JavaConversions._ 13 | import java.util.concurrent.LinkedBlockingQueue 14 | 15 | @Configuration 16 | @Import(Array(classOf[UserRegistrationConfiguration])) 17 | class UserRegistrationTestConfiguration { 18 | 19 | @Bean 20 | def queue() = new Queue(queueName, false) 21 | 22 | @Bean 23 | def binding(queue: Queue, exchange: TopicExchange) = BindingBuilder.bind(queue).to(exchange).`with`(queueName) 24 | 25 | @Bean 26 | def container(connectionFactory: ConnectionFactory, listenerAdapter: MessageListenerAdapter) = { 27 | val container = new SimpleMessageListenerContainer() 28 | container.setConnectionFactory(connectionFactory) 29 | container.setQueueNames(queueName) 30 | container.setMessageListener(listenerAdapter) 31 | container 32 | } 33 | 34 | @Bean 35 | def receiver() = new Receiver() 36 | 37 | 38 | @Bean 39 | def listenerAdapter(receiver: Receiver) = new MessageListenerAdapter(receiver, "receiveMessage") 40 | 41 | @Bean 42 | def restTemplate(scalaObjectMapper: ScalaObjectMapper) = { 43 | val restTemplate = new RestTemplate() 44 | restTemplate.getMessageConverters foreach { 45 | case mc: MappingJackson2HttpMessageConverter => 46 | mc.setObjectMapper(scalaObjectMapper) 47 | case _ => 48 | } 49 | restTemplate 50 | } 51 | } 52 | 53 | class Receiver { 54 | 55 | private val messages = new LinkedBlockingQueue[String]() 56 | 57 | def receiveMessage(message: Array[Byte]) { 58 | messages.add(new String(message)) 59 | } 60 | 61 | def getMessages = messages.toArray(Array[String]()) 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/dustview/DustView.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.dustview 2 | 3 | import org.springframework.web.servlet.view.AbstractTemplateView 4 | import javax.servlet.http.{HttpServletResponse, HttpServletRequest} 5 | import java.io.File 6 | import scala.collection.JavaConversions._ 7 | import org.springframework.validation.BindingResult 8 | import java.util 9 | 10 | class DustView extends AbstractTemplateView { 11 | 12 | import DustView._ 13 | 14 | // TODO - interesting. Using a thread local but only this class instantiates the particular template. 15 | 16 | override def renderMergedTemplateModel(model: java.util.Map[String, Object], request: HttpServletRequest, response: HttpServletResponse) { 17 | 18 | val templateName = getUrl() 19 | val file = templateName.substring("/WEB-INF/views/".length) 20 | 21 | dustTemplateRendererHolder.get.compileTemplate(templateName, DustTemplateRenderer.ClasspathSource("/dustjsviews/" + file)) 22 | 23 | 24 | val output = dustTemplateRendererHolder.get.renderTemplate(templateName, toJSObject(model)) 25 | response.getOutputStream().write(output.getBytes()) 26 | } 27 | 28 | def toJSObject(javaMap: java.util.Map[String, Object]): AnyRef = { 29 | val jsObject = dustTemplateRendererHolder.get.createObject().asInstanceOf[java.util.Map[String, Object]] 30 | for ((key, value) <- javaMap) { 31 | value match { 32 | case br: BindingResult => 33 | 34 | if (br.getFieldErrors.nonEmpty) { 35 | val toMap: Map[String, Object] = br.getFieldErrors.map { 36 | e => (e.getField, e.getDefaultMessage.asInstanceOf[Object]) 37 | }.toMap 38 | val fieldErrors = toJSObject(toMap) 39 | jsObject.put("fieldErrors", fieldErrors) 40 | } 41 | if (br.getGlobalErrors.nonEmpty) { 42 | val globalErrors: java.util.List[String] = br.getGlobalErrors.map { 43 | e => e.getDefaultMessage 44 | }.toList 45 | jsObject.put("globalErrors", globalErrors) 46 | } 47 | 48 | case _ => jsObject.put(key, value) 49 | } 50 | } 51 | jsObject 52 | } 53 | 54 | } 55 | 56 | object DustView { 57 | val dustTemplateRendererHolder = new ThreadLocal[DustTemplateRenderer] { 58 | override def initialValue() = { 59 | new DustTemplateRenderer(); 60 | } 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/controllers/UserRegistrationController.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.controllers 2 | 3 | import javax.validation.Valid 4 | import javax.validation.constraints.{NotNull, Size} 5 | 6 | import net.chrisrichardson.microservices.restfulspringboot.backend._ 7 | import org.hibernate.validator.constraints.Email 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.stereotype.Controller 10 | import org.springframework.ui.Model 11 | import org.springframework.validation.BindingResult 12 | import org.springframework.web.bind.annotation._ 13 | import org.springframework.web.servlet.mvc.support.RedirectAttributes 14 | 15 | import scala.beans.BeanProperty 16 | 17 | @Controller 18 | class UserRegistrationController @Autowired()(registrationService: RegistrationService) { 19 | 20 | @RequestMapping(value = Array("/register.html"), method = Array(RequestMethod.GET)) 21 | def beginRegister = "register" 22 | 23 | @RequestMapping(value = Array("/register.html"), method = Array(RequestMethod.POST)) 24 | def register(@Valid() @ModelAttribute("registration") request: RegistrationRequest, bindingResult: BindingResult, 25 | redirectAttributes: RedirectAttributes): String = { 26 | if (bindingResult.getErrorCount != 0) 27 | "register" 28 | else 29 | registrationService.registerUser(request.getEmailAddress, request.getPassword) match { 30 | case Right(_) => 31 | redirectAttributes.addAttribute("emailAddress", request.getEmailAddress) 32 | "redirect:registrationconfirmation.html" 33 | case Left(DuplicateRegistrationError) => 34 | bindingResult.rejectValue("emailAddress", "duplicate.email.address", "Email address already registered") 35 | "register" 36 | } 37 | } 38 | 39 | @RequestMapping(value = Array("/registrationconfirmation.html"), method = Array(RequestMethod.GET)) 40 | def registrationconfirmation(@RequestParam emailAddress: String, model: Model) = { 41 | model.addAttribute("emailAddress", emailAddress) 42 | "registrationconfirmation" 43 | } 44 | 45 | } 46 | 47 | class RegistrationRequest { 48 | 49 | @BeanProperty 50 | @Email 51 | @NotNull 52 | var emailAddress: String = _ 53 | 54 | @BeanProperty 55 | @NotNull 56 | @Size(min = 8, max = 30) 57 | var password: String = _ 58 | 59 | } 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /e2e-test/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /eureka-server/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /zipkin-server/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /spring-boot-webapp/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /spring-boot-restful-service/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/UserRegistrationConfiguration.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot 2 | 3 | import com.netflix.discovery.EurekaClient 4 | import net.chrisrichardson.microservices.restfulspringboot.backend.{DiscoveryHealthIndicator, ScalaObjectMapper} 5 | import net.chrisrichardson.microservices.restfulspringboot.dustview.DustViewResolver 6 | import org.springframework.boot.actuate.health.HealthIndicator 7 | import org.springframework.boot.autoconfigure.SpringBootApplication 8 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker 9 | import org.springframework.cloud.client.loadbalancer.LoadBalanced 10 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient 11 | import org.springframework.context.annotation._ 12 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter 13 | import org.springframework.web.client.RestTemplate 14 | import org.springframework.cloud.sleuth.sampler.AlwaysSampler 15 | import org.springframework.cloud.sleuth.Sampler 16 | 17 | import scala.collection.JavaConversions._ 18 | 19 | @SpringBootApplication 20 | @Import(Array(classOf[EurekaClientConfiguration], classOf[NonEurekaClientConfiguration])) 21 | @EnableCircuitBreaker 22 | class UserRegistrationConfiguration { 23 | 24 | 25 | @Bean 26 | @Primary 27 | def scalaObjectMapper() = new ScalaObjectMapper 28 | 29 | @Bean 30 | def dustViewResolver = { 31 | val resolver = new DustViewResolver 32 | resolver.setPrefix("/WEB-INF/views/") 33 | resolver.setSuffix(".dust") 34 | resolver 35 | } 36 | 37 | @Bean 38 | def sampler() : Sampler = new AlwaysSampler() 39 | 40 | 41 | } 42 | 43 | @Configuration 44 | @Profile(Array("!enableEureka")) 45 | class NonEurekaClientConfiguration { 46 | 47 | @Bean 48 | def restTemplate(scalaObjectMapper : ScalaObjectMapper) : RestTemplate = { 49 | val restTemplate = new RestTemplate() 50 | restTemplate.getMessageConverters foreach { 51 | case mc: MappingJackson2HttpMessageConverter => 52 | mc.setObjectMapper(scalaObjectMapper) 53 | case _ => 54 | } 55 | restTemplate 56 | } 57 | } 58 | 59 | @Configuration 60 | @EnableEurekaClient 61 | @Profile(Array("enableEureka")) 62 | class EurekaClientConfiguration { 63 | 64 | @Bean 65 | @LoadBalanced 66 | def restTemplate(scalaObjectMapper : ScalaObjectMapper) : RestTemplate = { 67 | val restTemplate = new RestTemplate() 68 | restTemplate.getMessageConverters foreach { 69 | case mc: MappingJackson2HttpMessageConverter => 70 | mc.setObjectMapper(scalaObjectMapper) 71 | case _ => 72 | } 73 | restTemplate 74 | } 75 | 76 | @Bean 77 | def discoveryHealthIndicator(discoveryClient : EurekaClient ) : HealthIndicator = new DiscoveryHealthIndicator(discoveryClient) 78 | } 79 | -------------------------------------------------------------------------------- /spring-boot-webapp/src/test/scala/net/chrisrichardson/microservices/restfulspringboot/UserRegistrationWebIntegrationTest.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot 2 | 3 | import org.junit.runner.RunWith 4 | import org.scalatest.Matchers._ 5 | import org.scalatest.concurrent.Eventually._ 6 | import org.scalatest.junit.JUnitRunner 7 | import org.scalatest.selenium.Chrome 8 | import org.scalatest.{BeforeAndAfterAll, FlatSpec} 9 | import org.springframework.boot.SpringApplication 10 | 11 | import scala.concurrent.duration._ 12 | 13 | @RunWith(classOf[JUnitRunner]) 14 | class UserRegistrationWebIntegrationTest extends FlatSpec with Chrome with BeforeAndAfterAll with MyPages { 15 | 16 | override def beforeAll { 17 | val sa = new SpringApplication(classOf[UserRegistrationTestConfiguration]) 18 | // sa.setAdditionalProfiles("test") 19 | val ctx = sa.run() 20 | } 21 | 22 | val port = 8080 23 | 24 | val baseUrl = s"http://localhost:${port}/" 25 | 26 | def goto(path: String) = go to (baseUrl + path) 27 | 28 | val registrationPage = new RegistrationPage(baseUrl) 29 | val registrationConfirmationPage = new RegistrationConfirmationPage(baseUrl) 30 | 31 | def makeEmailAddress() = s"foo-${System.currentTimeMillis()}@chrisrichardson.net" 32 | 33 | implicit val longWait = PatienceConfig(10 second, 100 milliseconds) 34 | 35 | it should "register a user" in { 36 | val emailAddress = makeEmailAddress() 37 | 38 | go to registrationPage 39 | 40 | pageTitle should be(registrationPage.title) 41 | 42 | registrationPage.fillAndSubmit(emailAddress, "secret1234") 43 | 44 | eventually { 45 | pageTitle should be(registrationConfirmationPage.title) 46 | }(longWait) 47 | 48 | eventually { 49 | registrationConfirmationPage.registeredEmailAddress should be(emailAddress) 50 | }(longWait) 51 | } 52 | 53 | 54 | 55 | it should "require long passwords" in { 56 | val emailAddress = makeEmailAddress() 57 | 58 | go to registrationPage 59 | 60 | pageTitle should be(registrationPage.title) 61 | 62 | registrationPage.fillAndSubmit(emailAddress, "secret") 63 | 64 | eventually { 65 | pageTitle should be(registrationPage.title) 66 | }(longWait) 67 | 68 | } 69 | 70 | it should "handle duplicate registrations" in { 71 | val emailAddress = makeEmailAddress() 72 | 73 | go to registrationPage 74 | 75 | pageTitle should be(registrationPage.title) 76 | 77 | registrationPage.fillAndSubmit(emailAddress, "secret1234") 78 | 79 | eventually { 80 | pageTitle should be(registrationConfirmationPage.title) 81 | }(longWait) 82 | 83 | go to registrationPage 84 | pageTitle should be(registrationPage.title) 85 | registrationPage.fillAndSubmit(emailAddress, "secret1234") 86 | eventually { 87 | pageTitle should be(registrationPage.title) 88 | pageSource should include ("Email address already registered") 89 | }(longWait) 90 | 91 | } 92 | 93 | override def afterAll() { 94 | webDriver.quit() 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /spring-boot-restful-service/src/test/scala/net/chrisrichardson/microservices/restfulspringboot/UserRegistrationWebIntegrationTest.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot 2 | 3 | import org.junit.runner.RunWith 4 | import org.springframework.context.ConfigurableApplicationContext 5 | import org.springframework.web.client.{HttpClientErrorException, RestTemplate} 6 | import org.springframework.http.HttpStatus 7 | import net.chrisrichardson.microservices.restfulspringboot.controllers.{RegistrationResponse, RegistrationRequest} 8 | import net.chrisrichardson.microservices.restfulspringboot.backend.ScalaObjectMapper 9 | import org.springframework.boot.test.{IntegrationTest, SpringApplicationConfiguration} 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner 11 | import org.springframework.beans.factory.annotation.Autowired 12 | import org.junit.{Assert, Test} 13 | import org.springframework.test.context.web.WebAppConfiguration 14 | 15 | @RunWith(classOf[SpringJUnit4ClassRunner]) 16 | @SpringApplicationConfiguration(classes = Array(classOf[UserRegistrationTestConfiguration])) 17 | @WebAppConfiguration 18 | @IntegrationTest 19 | class UserRegistrationWebIntegrationTest { 20 | 21 | @Autowired 22 | var ctx: ConfigurableApplicationContext = _ 23 | 24 | @Autowired 25 | var scalaObjectMapper: ScalaObjectMapper = _ 26 | 27 | @Autowired 28 | var receiver: Receiver = _ 29 | 30 | @Autowired 31 | var restTemplate: RestTemplate = _ 32 | 33 | def makeRegistrationRequest(emailAddress : String, password : String) : RegistrationRequest = { 34 | val r = new RegistrationRequest 35 | r.emailAddress = emailAddress 36 | r.password = password 37 | r 38 | } 39 | 40 | @Test 41 | def shouldRegisterUser { 42 | val emailAddress = System.currentTimeMillis() + "-a-foo@bar.com" 43 | val request = makeRegistrationRequest(emailAddress, "secret1234") 44 | val response = restTemplate.postForEntity("http://localhost:8080/user", request, classOf[RegistrationResponse]) 45 | Assert.assertEquals(HttpStatus.OK, response.getStatusCode) 46 | eventually { 47 | val receivedMessages = receiver.getMessages 48 | receivedMessages.find { _ contains emailAddress} match { 49 | case Some(_) => 50 | case None => 51 | Assert.fail(receivedMessages.toList + " expected to contain " + emailAddress) 52 | } 53 | } 54 | } 55 | 56 | def eventually(body : => Unit) : Unit = 57 | for (i <- 1 to 30) { 58 | try { 59 | body 60 | return 61 | } catch { 62 | case e : AssertionError if i < 10 => 63 | Thread.sleep(100) 64 | } 65 | } 66 | 67 | @Test 68 | def shouldRejectDuplicateUser { 69 | val emailAddress = System.currentTimeMillis() + "-b-foo@bar.com" 70 | val request = makeRegistrationRequest(emailAddress, "secret1234") 71 | val response1 = restTemplate.postForEntity("http://localhost:8080/user", request, classOf[RegistrationResponse]) 72 | Assert.assertEquals(HttpStatus.OK, response1.getStatusCode) 73 | try { 74 | restTemplate.postForEntity("http://localhost:8080/user", request, classOf[RegistrationResponse]) 75 | Assert.fail("expected user registration to fail because of duplicate") 76 | } catch { 77 | case e: HttpClientErrorException if e.getStatusCode == HttpStatus.CONFLICT => 78 | } 79 | } 80 | 81 | @Test 82 | def shouldRejectInvalidRequest{ 83 | val request = makeRegistrationRequest(null, null) 84 | try { 85 | restTemplate.postForEntity("http://localhost:8080/user", request, classOf[RegistrationResponse]) 86 | Assert.fail("expected user registration to fail because of bad request") 87 | } catch { 88 | case e: HttpClientErrorException if e.getStatusCode == HttpStatus.BAD_REQUEST => 89 | } 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /spring-boot-webapp/src/main/scala/net/chrisrichardson/microservices/restfulspringboot/dustview/DustTemplateRenderer.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.restfulspringboot.dustview 2 | 3 | import javax.script.{Invocable, ScriptEngineManager} 4 | import java.io.{File, InputStreamReader} 5 | 6 | import org.slf4j.LoggerFactory 7 | import org.springframework.util.Assert 8 | 9 | import scala.concurrent.{Await, Promise} 10 | import org.apache.commons.io.{IOUtils, FileUtils} 11 | import java.util.concurrent.ConcurrentHashMap 12 | import scala.concurrent.duration._ 13 | 14 | class DustTemplateRenderer { 15 | 16 | import DustTemplateRenderer._ 17 | 18 | val logger = LoggerFactory.getLogger(getClass) 19 | 20 | logger.debug("Creating new engine!!!") 21 | val engineManager = new ScriptEngineManager() 22 | val engine = engineManager.getEngineByName("nashorn") 23 | Assert.notNull(engine, "Not running Java 8") 24 | 25 | engine.eval(new InputStreamReader(classOf[DustView].getResourceAsStream("/dustjs/dust-full.js"))) 26 | engine.eval(new InputStreamReader(classOf[DustView].getResourceAsStream("/dustjs/dust-wrapper.js"))) 27 | val dust = engine.eval("dust") 28 | val myCallback = engine.eval("myDustCallback") 29 | val outputHolder = engine.eval("outputHolder") 30 | val invocable = engine.asInstanceOf[Invocable] 31 | val loadedTemplates = scala.collection.mutable.HashSet[String]() 32 | 33 | compileTemplate("main_template", ClasspathSource("/dustjstemplates/main_template.dust")) 34 | 35 | logger.debug("done creating new engine!!!") 36 | 37 | 38 | def compileTemplateFile(templateName : String, file : File) = 39 | compileTemplate(templateName, FileSource(file)) 40 | 41 | def compileTemplate(templateName : String, source : TemplateSource) 42 | { if (!loadedTemplates.contains(templateName)) { 43 | logger.debug("loaded template " + templateName) 44 | 45 | 46 | var compiledTemplate = compiledTemplates.get(templateName) 47 | if (compiledTemplate == null) { 48 | logger.debug("Compiling template " + templateName) 49 | val template = source.getString 50 | compiledTemplate = invocable.invokeMethod(dust, "compile", template, templateName) 51 | compiledTemplates.putIfAbsent(templateName, compiledTemplate) 52 | compiledTemplate = compiledTemplates.get(templateName) 53 | } else { 54 | logger.debug("Found precompiled template " + templateName) 55 | } 56 | invocable.invokeMethod(dust, "loadSource", compiledTemplate) 57 | loadedTemplates.add(templateName) 58 | } else { 59 | logger.debug("template already loaded " + templateName) 60 | } 61 | } 62 | 63 | class MyJavaCallback(p : Promise[String]) { 64 | 65 | def done(err : Any, out : Any) { 66 | if (err == null) 67 | p.success(out.asInstanceOf[String]) 68 | else { 69 | p.failure(new RuntimeException(err.toString)) 70 | } 71 | } 72 | } 73 | 74 | 75 | def renderTemplate(templateName : String, data : Object) = { 76 | val p = Promise[String]() 77 | invocable.invokeMethod(outputHolder, "setDone", new MyJavaCallback(p) ) 78 | invocable.invokeMethod(dust, "render", templateName, data, myCallback) 79 | 80 | Await.result(p.future, 500 millis) 81 | 82 | } 83 | 84 | def createObject() = engine.eval("Object.create(Object)") 85 | } 86 | 87 | object DustTemplateRenderer { 88 | trait TemplateSource { 89 | def getString : String 90 | } 91 | 92 | case class FileSource(file : File) extends TemplateSource { 93 | def getString = FileUtils.readFileToString(file) 94 | } 95 | 96 | case class ClasspathSource(resource : String) extends TemplateSource { 97 | def getString = IOUtils.toString(getClass.getResourceAsStream(resource)) 98 | } 99 | 100 | 101 | val compiledTemplates = new ConcurrentHashMap[String, Object]() 102 | } -------------------------------------------------------------------------------- /e2e-test/src/test/scala/net/chrisrichardson/microservices/e2etest/EndToEndTest.scala: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.microservices.e2etest 2 | 3 | import org.junit.runner.RunWith 4 | import org.openqa.selenium.WebDriver 5 | import org.scalatest.BeforeAndAfterAll 6 | import org.scalatest.FlatSpec 7 | import org.scalatest.Matchers._ 8 | import org.scalatest.concurrent.Eventually._ 9 | import org.scalatest.junit.JUnitRunner 10 | import org.scalatest.selenium.Chrome 11 | import org.scalatest.selenium.WebBrowser 12 | import org.scalatest.selenium.WebBrowser.Page 13 | import org.scalatest.selenium.WebBrowser.go 14 | 15 | import org.junit.runner.RunWith 16 | import org.scalatest.Matchers._ 17 | import org.scalatest.concurrent.Eventually._ 18 | import org.scalatest.junit.JUnitRunner 19 | import org.scalatest.selenium.Chrome 20 | import org.scalatest.{BeforeAndAfterAll, FlatSpec} 21 | 22 | import scala.concurrent.duration._ 23 | 24 | 25 | @RunWith(classOf[JUnitRunner]) 26 | class EndToEndTest extends FlatSpec with Chrome with BeforeAndAfterAll with MyPages { 27 | 28 | val port = "8080" 29 | val host = System.getenv("DOCKER_HOST_IP") 30 | 31 | val baseUrl = s"http://$host:$port/" 32 | 33 | def goto(path: String) = go to (baseUrl + path) 34 | 35 | val registrationPage = new RegistrationPage(baseUrl) 36 | val registrationConfirmationPage = new RegistrationConfirmationPage(baseUrl) 37 | 38 | def makeEmailAddress() = s"foo-${System.currentTimeMillis()}@chrisrichardson.net" 39 | 40 | implicit val longWait = PatienceConfig(10 second, 100 milliseconds) 41 | 42 | it should "register a user" in { 43 | val emailAddress = makeEmailAddress() 44 | 45 | go to registrationPage 46 | 47 | pageTitle should be(registrationPage.title) 48 | 49 | registrationPage.fillAndSubmit(emailAddress, "secret1234") 50 | 51 | eventually { 52 | pageTitle should be(registrationConfirmationPage.title) 53 | }(longWait) 54 | 55 | eventually { 56 | registrationConfirmationPage.registeredEmailAddress should be(emailAddress) 57 | }(longWait) 58 | } 59 | 60 | 61 | 62 | it should "require long passwords" in { 63 | val emailAddress = makeEmailAddress() 64 | 65 | go to registrationPage 66 | 67 | pageTitle should be(registrationPage.title) 68 | 69 | registrationPage.fillAndSubmit(emailAddress, "secret") 70 | 71 | eventually { 72 | pageTitle should be(registrationPage.title) 73 | }(longWait) 74 | 75 | } 76 | 77 | it should "handle duplicate registrations" in { 78 | val emailAddress = makeEmailAddress() 79 | 80 | go to registrationPage 81 | 82 | pageTitle should be(registrationPage.title) 83 | 84 | registrationPage.fillAndSubmit(emailAddress, "secret1234") 85 | 86 | eventually { 87 | pageTitle should be(registrationConfirmationPage.title) 88 | }(longWait) 89 | 90 | go to registrationPage 91 | pageTitle should be(registrationPage.title) 92 | registrationPage.fillAndSubmit(emailAddress, "secret1234") 93 | eventually { 94 | pageTitle should be(registrationPage.title) 95 | pageSource should include ("Email address already registered") 96 | }(longWait) 97 | 98 | } 99 | 100 | override def afterAll() { 101 | webDriver.quit() 102 | } 103 | 104 | } 105 | trait MyPages { 106 | 107 | this: WebBrowser => 108 | 109 | implicit val webDriver: WebDriver 110 | 111 | class RegistrationPage(baseUrl: String) extends Page { 112 | 113 | val url = baseUrl + "register.html" 114 | val title = "Registration" 115 | 116 | def fillAndSubmit(emailAddress: String, password: String) { 117 | 118 | emailField("emailAddress").value = emailAddress 119 | pwdField("password").value = password 120 | submit() 121 | } 122 | } 123 | 124 | // class registrationconfirmationPage(baseUrl: String)(implicit val webDriver: WebDriver) extends WebBrowser.Page with WebBrowser { 125 | class RegistrationConfirmationPage(baseUrl: String) extends Page { 126 | 127 | val url = baseUrl + "registrationconfirmation.html" 128 | 129 | val title = "Registration Complete" 130 | 131 | def registeredEmailAddress = find("registeredEmailAddress").get.text 132 | 133 | } 134 | 135 | } 136 | 137 | -------------------------------------------------------------------------------- /spring-boot-webapp/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.springframework.cloud 6 | spring-cloud-starter-parent 7 | Angel.SR3 8 | 9 | net.chrisrichardson.springbootexamples 10 | spring-boot-user-registration-webapp 11 | 1.0-SNAPSHOT 12 | 13 | 14 | 15 | 16 | org.scala-lang 17 | scala-library 18 | 2.10.3 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-eureka 24 | 25 | 26 | 27 | org.apache.commons 28 | commons-io 29 | 1.3.2 30 | 31 | 32 | 33 | com.fasterxml.jackson.module 34 | jackson-module-scala_2.10 35 | 2.3.1 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-web 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-actuator 45 | 46 | 47 | 48 | 49 | org.webjars 50 | bootstrap 51 | 3.1.1 52 | 53 | 54 | 55 | org.webjars 56 | jquery-validation 57 | 1.11.1 58 | 59 | 60 | 61 | javax.validation 62 | validation-api 63 | 1.1.0.Final 64 | 65 | 66 | org.hibernate 67 | hibernate-validator 68 | 5.0.3.Final 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-test 74 | test 75 | 76 | 77 | 78 | org.scalatest 79 | scalatest_2.10 80 | 2.1.0 81 | test 82 | 83 | 84 | 85 | junit 86 | junit 87 | 4.11 88 | test 89 | 90 | 91 | 92 | org.seleniumhq.selenium 93 | selenium-java 94 | 2.45.0 95 | test 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-maven-plugin 104 | 105 | 106 | org.scala-tools 107 | maven-scala-plugin 108 | 109 | 110 | 111 | compile 112 | testCompile 113 | 114 | 115 | 116 | 117 | src/main/scala 118 | 119 | -Xms128m 120 | -Xmx1024m 121 | 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-compiler-plugin 127 | 128 | 1.8 129 | 1.8 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /e2e-test/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /eureka-server/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /zipkin-server/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /spring-boot-webapp/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /spring-boot-restful-service/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple example of microservices that is described in this [series of blog posts](http://plainoldobjects.com/2014/11/16/deploying-spring-boot-based-microservices-with-docker/). 2 | 3 | There are two services: 4 | * RESTful service - exposes a REST API for registering a user. 5 | It saves the user registration in MongoDB and posts a message to RabbitMQ. 6 | It is the example code for the article [Building microservices with Spring Boot - part 1](http://plainoldobjects.com/2014/04/01/building-microservices-with-spring-boot-part1/). 7 | 8 | * Web application - implements the user registration UI and invokes the RESTful service. 9 | It is the example code for the article [Building microservices with Spring Boot - part 2](https://plainoldobjects.com/2014/05/05/building-microservices-with-spring-boot-part-2/). 10 | 11 | The services are written in Scala and use the following technologies. 12 | 13 | * Spring Boot 14 | * Spring Cloud 15 | * Netflix OSS Eureka 16 | * RabbitMQ 17 | 18 | Note: There are [other example microservice applications](http://eventuate.io/exampleapps.html). 19 | 20 | # Building and running the microservices 21 | 22 | This project uses with [Docker Compose](https://docs.docker.com/compose/) to run the services as well as RabbitMQ and MongoDB. 23 | 24 | The `spring-boot-webapp` project uses Selenium to test the web UI using the Chrome browser. 25 | You will need to install [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/getting-started). 26 | On Mac OSX you can run ` brew cask install chromedriver`. 27 | 28 | 29 | ## The quick way 30 | 31 | The quickest way to build and run the services on Linux/Mac OSX is with the following commands: 32 | 33 | ``` 34 | . ./set-env.sh 35 | ./gradle-all.sh assemble 36 | docker-compose up -d 37 | ./show-urls.sh 38 | ``` 39 | 40 | Otherwise, follow these instructions. 41 | 42 | ## Running MongoDB and RabbitMQ 43 | 44 | The RESTful service uses RabbitMQ and MongoDB. 45 | The easier way to run them is to using Docker: 46 | 47 | ``` 48 | docker-compose up -d mongodb rabbitmq 49 | ``` 50 | 51 | You also need to set some environment variables so that the services can connect to them: 52 | 53 | ``` 54 | export DOCKER_HOST_IP=$(docker-machine ip default 2>/dev/null) 55 | 56 | export SPRING_DATA_MONGODB_URI=mongodb://${DOCKER_HOST_IP}/userregistration 57 | export SPRING_RABBITMQ_HOST=${DOCKER_HOST_IP} 58 | ``` 59 | 60 | ## Build the Eureka server 61 | 62 | This application uses Netflix OSS Eureka for service discovery. 63 | Build the Spring Cloud based Eureka server using the following commands: 64 | 65 | ``` 66 | cd eureka-server 67 | ./gradlew build 68 | ``` 69 | 70 | ## Build the Zipkin server 71 | 72 | This application uses Zipkin for distributed tracing. 73 | Build the Zipkin server using the following commands: 74 | 75 | ``` 76 | cd zipkin-server 77 | ./gradlew build 78 | ``` 79 | 80 | ## Building the RESTful service 81 | 82 | Use the following commands to build the RESTful service: 83 | 84 | ``` 85 | cd spring-boot-restful-service 86 | ./gradlew build 87 | ``` 88 | 89 | ## Running the RESTful service 90 | 91 | You can run the service by using the following command in the top-level directory: 92 | 93 | ``` 94 | docker-compose up -d restfulservice 95 | ``` 96 | 97 | ## Using the RESTful service 98 | 99 | Once the service has started, you can send a registration request using: 100 | 101 | ``` 102 | ./register-user.sh 103 | ``` 104 | 105 | You can examine the MongoDB database using the following commands 106 | 107 | ``` 108 | $ ./mongodb-cli.sh 109 | > show dbs; 110 | local 0.031GB 111 | mydb 0.031GB 112 | userregistration 0.031GB 113 | > use userregistration; 114 | switched to db userregistration 115 | > 116 | > 117 | > show collections; 118 | registeredUser 119 | system.indexes 120 | > 121 | > 122 | > db.registeredUser.find() 123 | { "_id" : ObjectId("55a99b0993860551c6020e9d"), "_class" : "net.chrisrichardson.microservices.restfulspringboot.backend.RegisteredUser", "emailAddress" : "1437178632863-b-foo@bar.com", "password" : "secret" } 124 | > exit 125 | $ 126 | ``` 127 | 128 | ## Building the web application 129 | 130 | Since the web application invokes the RESTful service you must set the following environment variable: 131 | 132 | ``` 133 | export USER_REGISTRATION_URL=http://${DOCKER_HOST_IP}:8081/user 134 | ``` 135 | 136 | Next, use the following commands to build the web application: 137 | 138 | ``` 139 | cd spring-boot-webapp 140 | ./gradlew build 141 | ``` 142 | 143 | ## Running the web application 144 | 145 | Run the web application using the following command in the top-level directory: 146 | 147 | ``` 148 | docker-compose up -d web 149 | ``` 150 | 151 | ## Using the web application 152 | 153 | You can access the web application by visiting the following URL: `http://${DOCKER_HOST_IP?}:8080/register.html` 154 | 155 | There are also other URLs that you can visit. 156 | The following command will wait until the services are available and displays the URLs: 157 | 158 | ``` 159 | ./show-urls.sh 160 | ``` 161 | 162 | # Building and running Docker images 163 | 164 | The previous instructions deployed the services as Docker containers without actually packaging the services as Docker images. 165 | The `docker-compose.yml` file ran the image `java:openjdk-8u91-jdk` and used volume mapping to make the Spring Boot jar files accessible. 166 | Follow these instructions to build and run the Docker images. 167 | 168 | ## Building the images 169 | 170 | You can build the images by running the following command: 171 | 172 | ``` 173 | ./build-docker-images.sh 174 | ``` 175 | 176 | This script is a simple wrapper around `docker build`. 177 | 178 | ## Running the images 179 | 180 | You can now run the Docker images using the `docker-compose` command with `docker-compose-images.yml`: 181 | 182 | ``` 183 | docker-compose -f docker-compose-images.yml up -d 184 | ``` 185 | 186 | The following command will wait until the services are available and displays the URLs: 187 | 188 | ``` 189 | ./show-urls.sh 190 | ``` 191 | --------------------------------------------------------------------------------