├── README.md ├── customer ├── pom.xml ├── run.cmd └── src │ ├── main │ ├── java │ │ └── de │ │ │ └── michlb │ │ │ └── demo │ │ │ └── customer │ │ │ ├── Application.java │ │ │ ├── constant │ │ │ ├── ApiConstants.java │ │ │ └── GlobalBusinessCodes.java │ │ │ ├── controller │ │ │ ├── BuildController.java │ │ │ ├── CustomerController.java │ │ │ ├── HelpController.java │ │ │ └── PingController.java │ │ │ ├── feign │ │ │ ├── FeignConfiguration.java │ │ │ ├── client │ │ │ │ └── AccountClient.java │ │ │ └── interceptor │ │ │ │ └── TraceRequestInterceptor.java │ │ │ └── model │ │ │ └── Customer.java │ └── resources │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── bootstrap.yml │ │ ├── git-build.properties │ │ └── logback.xml │ └── test │ ├── java │ └── de │ │ └── michlb │ │ └── demo │ │ └── customer │ │ ├── PingControllerIntegrationTest.java │ │ └── PingControllerTest.java │ └── jmeter │ └── Microservice test plan.jmx ├── eureka ├── pom.xml ├── run.cmd └── src │ └── main │ ├── java │ └── de │ │ └── michlb │ │ └── demo │ │ └── eureka │ │ └── Application.java │ └── resources │ ├── application.yml │ └── banner.txt ├── product ├── pom.xml ├── run.cmd └── src │ ├── main │ ├── java │ │ └── de │ │ │ └── michlb │ │ │ └── demo │ │ │ └── product │ │ │ ├── Application.java │ │ │ ├── constant │ │ │ ├── ApiConstants.java │ │ │ └── GlobalBusinessCodes.java │ │ │ ├── controller │ │ │ ├── BuildController.java │ │ │ ├── HelpController.java │ │ │ ├── PingController.java │ │ │ └── ProductDetailController.java │ │ │ ├── feign │ │ │ ├── FeignConfiguration.java │ │ │ ├── client │ │ │ │ └── AccountClient.java │ │ │ └── interceptor │ │ │ │ └── TraceRequestInterceptor.java │ │ │ └── model │ │ │ └── Product.java │ └── resources │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── bootstrap.yml │ │ ├── git-build.properties │ │ └── logback.xml │ └── test │ ├── java │ └── de │ │ └── michlb │ │ └── demo │ │ └── product │ │ ├── PingControllerIntegrationTest.java │ │ └── PingControllerTest.java │ └── jmeter │ └── Microservice test plan.jmx └── zuul ├── pom.xml ├── run.cmd └── src └── main ├── java └── de │ └── michlb │ └── demo │ └── zuul │ ├── Application.java │ ├── bff │ └── controller │ │ └── ProductController.java │ └── feign │ ├── CustomerClient.java │ └── ProductClient.java └── resources ├── banner.txt └── bootstrap.yml /README.md: -------------------------------------------------------------------------------- 1 | # Sample for Zuul usage with integrated BFF Pattern Controller 2 | 3 | ## eureka 4 | Netfilx Eureka Discovery Server 5 | 6 | ## zuul 7 | Netfilx Zuul Proxy Server 8 | 9 | ## product 10 | Spring Boot RESTful service for a simple sample 11 | - gives a simple Product object as JSON 12 | 13 | ## customer 14 | Spring Boot RESTful service for a simple sample 15 | - gives a simple Customer object as JSON 16 | 17 | ## Check Results 18 | 19 | [http://localhost:8071/](http://localhost:8071/) Eureka Server 20 | 21 | [http://localhost:8081/mappings](http://localhost:8081/mappings) Zuul Server mappings 22 | 23 | [http://localhost:8082/api/product/detail](http://localhost:8082/api/product/detail) Product Service direct - detail 24 | 25 | [http://localhost:8081/product/api/product/detail](http://localhost:8081/product/api/product/detail) Product Service via Zuul - detail 26 | 27 | [http://localhost:8083/api/customer/info](http://localhost:8083/api/customer/info) Customer Service direct - info 28 | 29 | [http://localhost:8081/customer/api/customer/info](http://localhost:8081/customer/api/customer/info) Customer Service via Zuul - info 30 | 31 | [http://localhost:8081/bff/product/detail](http://localhost:8081/bff/product/detail) Call Zuul for aggregated info 32 | -------------------------------------------------------------------------------- /customer/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.cloud 8 | spring-cloud-starter-parent 9 | Brixton.RELEASE 10 | 11 | 12 | 13 | 14 | de.michlb.demo 15 | customer 16 | 1.0.0-SNAPSHOT 17 | jar 18 | customer 19 | Module contains the restful controllers. 20 | 21 | 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | 29 | 30 | com.shedhack.trace 31 | trace-request-api 32 | 1.0.1 33 | 34 | 35 | 36 | com.shedhack.trace 37 | trace-request-filter 38 | 1.0.3 39 | 40 | 41 | 42 | com.shedhack.trace 43 | trace-request-jpa 44 | 1.0.2 45 | 46 | 47 | 48 | com.shedhack.exception 49 | exception-controller-spring 50 | 1.0.5 51 | 52 | 53 | 54 | com.shedhack.exception 55 | exception-core 56 | 1.0.4 57 | 58 | 59 | 60 | com.shedhack.thread 61 | thread-context-aspect 62 | 1.0.8 63 | 64 | 65 | 66 | com.shedhack.spring 67 | spring-actuator 68 | 1.0.1 69 | 70 | 71 | 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-web 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-starter-actuator 81 | 82 | 83 | 84 | org.springframework.boot 85 | spring-boot-actuator-docs 86 | 87 | 88 | 89 | org.webjars 90 | hal-browser 91 | 92 | 93 | 94 | 96 | 97 | org.hsqldb 98 | hsqldb 99 | runtime 100 | 101 | 102 | 103 | 104 | org.springframework.cloud 105 | spring-cloud-starter-config 106 | 107 | 108 | 109 | org.springframework.cloud 110 | spring-cloud-starter-eureka 111 | 112 | 113 | 114 | org.springframework.cloud 115 | spring-cloud-starter-ribbon 116 | 117 | 118 | 119 | org.springframework.cloud 120 | spring-cloud-starter-feign 121 | compile 122 | 123 | 124 | 125 | org.springframework.cloud 126 | spring-cloud-starter-hystrix 127 | 128 | 129 | 130 | org.springframework.cloud 131 | spring-cloud-starter-hystrix-dashboard 132 | 133 | 134 | 135 | 136 | io.springfox 137 | springfox-swagger2 138 | 2.4.0 139 | 140 | 141 | 142 | io.springfox 143 | springfox-swagger-ui 144 | 2.4.0 145 | 146 | 147 | 148 | 149 | 150 | org.springframework.boot 151 | spring-boot-starter-test 152 | test 153 | 154 | 155 | 156 | 157 | 158 | org.springframework.boot 159 | spring-boot-devtools 160 | 161 | 162 | 163 | 164 | 165 | customer 166 | 167 | 168 | 169 | 170 | src/main/resources 171 | true 172 | 173 | **/*.properties 174 | **/*.yml 175 | 176 | 177 | 178 | src/main/resources 179 | false 180 | 181 | **/*.xml 182 | 183 | 184 | 185 | 186 | 187 | 189 | 190 | org.apache.maven.plugins 191 | maven-resources-plugin 192 | 2.7 193 | 194 | 195 | @ 196 | 197 | false 198 | 199 | 200 | 201 | 202 | 203 | org.springframework.boot 204 | spring-boot-maven-plugin 205 | 206 | 207 | 208 | true 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /customer/run.cmd: -------------------------------------------------------------------------------- 1 | title run:customer:8083 2 | mvn spring-boot:run -Drun.jvmArguments="-Dserver.port=8083 -Xms128m -Xmx256m" 3 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/Application.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer; 2 | 3 | import de.michlb.demo.customer.constant.ApiConstants; 4 | import com.shedhack.exception.controller.spring.config.EnableExceptionController; 5 | import com.shedhack.spring.actuator.config.EnableActuatorsAndInterceptors; 6 | import com.shedhack.spring.actuator.interceptor.TraceRequestHandler; 7 | import com.shedhack.thread.context.config.EnableThreadContextAspect; 8 | import com.shedhack.trace.request.api.service.TraceRequestService; 9 | import com.shedhack.trace.request.filter.DefaultLoggingHandler; 10 | import com.shedhack.trace.request.filter.RequestTraceFilter; 11 | import com.shedhack.trace.request.jpa.config.EnableTraceRequestJpa; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.boot.SpringApplication; 15 | import org.springframework.boot.autoconfigure.SpringBootApplication; 16 | import org.springframework.boot.context.embedded.FilterRegistrationBean; 17 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 18 | import org.springframework.cloud.netflix.feign.EnableFeignClients; 19 | import org.springframework.cloud.netflix.hystrix.EnableHystrix; 20 | import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.PropertySource; 23 | import org.springframework.context.annotation.PropertySources; 24 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 25 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 26 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 27 | import springfox.documentation.builders.ApiInfoBuilder; 28 | import springfox.documentation.builders.PathSelectors; 29 | import springfox.documentation.builders.RequestHandlerSelectors; 30 | import springfox.documentation.service.ApiInfo; 31 | import springfox.documentation.spi.DocumentationType; 32 | import springfox.documentation.spring.web.plugins.Docket; 33 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 34 | 35 | import java.util.Arrays; 36 | 37 | @SpringBootApplication 38 | @EnableSwagger2 39 | @EnableTraceRequestJpa 40 | @EnableExceptionController 41 | @EnableThreadContextAspect 42 | @EnableActuatorsAndInterceptors 43 | @PropertySources(value = { 44 | @PropertySource(value = "classpath:/git-build.properties") 45 | }) 46 | @EnableDiscoveryClient 47 | @EnableFeignClients 48 | @EnableHystrix 49 | @EnableHystrixDashboard 50 | public class Application extends WebMvcConfigurerAdapter { 51 | 52 | // -------------------- 53 | // Bean configurations: 54 | // -------------------- 55 | 56 | @Value("${spring.application.name}") 57 | private String appName; 58 | 59 | /** 60 | * Service is configured via {@link EnableTraceRequestJpa} 61 | */ 62 | @Autowired 63 | private TraceRequestService jpaTraceRequestService; 64 | 65 | /** 66 | * Service is configured via @EnableActuatorsAndInterceptors 67 | */ 68 | @Autowired 69 | private TraceRequestHandler traceRequestHandler; 70 | 71 | /** 72 | * Filter records and logs all HTTP requests. 73 | * This requires an implementation of {@link TraceRequestService}. 74 | */ 75 | @Bean 76 | public FilterRegistrationBean requestIdFilterRegistrationBean() { 77 | FilterRegistrationBean filter = new FilterRegistrationBean(); 78 | filter.setFilter(new RequestTraceFilter(appName, jpaTraceRequestService, 79 | Arrays.asList(new DefaultLoggingHandler(), traceRequestHandler))); 80 | filter.addUrlPatterns(ApiConstants.API_ROOT + "/*"); 81 | 82 | return filter; 83 | } 84 | 85 | // --------------------------------------- 86 | // Swagger setup for the API documentation 87 | // --------------------------------------- 88 | 89 | // If you add spring security then you can easily secure these resources. 90 | 91 | @Override 92 | public void addViewControllers(ViewControllerRegistry registry) { 93 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT + "/v2/api-docs", "/v2/api-docs"); 94 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT + "/configuration/ui", "/configuration/ui"); 95 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT +"/configuration/security", "/configuration/security"); 96 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT +"/swagger-resources", "/swagger-resources"); 97 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT, "/swagger-ui.html"); 98 | } 99 | 100 | @Override 101 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 102 | registry 103 | .addResourceHandler("/" + ApiConstants.API_DOCS_ROOT + "/**") 104 | .addResourceLocations("classpath:/META-INF/resources/"); 105 | } 106 | 107 | public Docket createRestApi() { 108 | return new Docket(DocumentationType.SWAGGER_2) 109 | .apiInfo(apiInfo()) 110 | .select() 111 | .apis(RequestHandlerSelectors.basePackage("de.michlb.demo.customer.rest.controller")) 112 | .paths(PathSelectors.any()) 113 | .build(); 114 | } 115 | 116 | private ApiInfo apiInfo() { 117 | return new ApiInfoBuilder() 118 | .title("API Documentation") 119 | .description("API Documentation") 120 | .termsOfServiceUrl("") 121 | .version("1.0") 122 | .build(); 123 | } 124 | 125 | /** 126 | * Future datasource beans need to marked with {@link org.springframework.context.annotation.Primary}. 127 | * to prevent conflicts with the DS used by EnableTraceRequestJpa. 128 | */ 129 | 130 | // --------------------------- 131 | // Main method to start Spring 132 | // --------------------------- 133 | public static void main(String[] args) { 134 | SpringApplication.run(Application.class, args); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/constant/ApiConstants.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.constant; 2 | 3 | /** 4 | *
 5 |  *     Static string representation of the API's endpoints.
 6 |  * 
7 | */ 8 | public class ApiConstants { 9 | 10 | public static final String API_ROOT = "/api"; 11 | 12 | public static final String API_HELP = API_ROOT + "/help"; 13 | 14 | public static final String API_PING = API_ROOT + "/ping"; 15 | 16 | public static final String API_DOCS_ROOT = "/docs"; 17 | 18 | // Management root URI 19 | public static final String API_MAN_ROOT ="/admin"; 20 | 21 | public static final String API_MAN_BUILD= API_MAN_ROOT + "/build"; 22 | 23 | // Specific URI 24 | public static final String API_CUSTOMER_INFO = API_ROOT + "/customer/info"; 25 | } 26 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/constant/GlobalBusinessCodes.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.constant; 2 | 3 | import com.shedhack.exception.core.BusinessCode; 4 | 5 | /** 6 | *
 7 |  *     Business Codes which are shared with clients.
 8 |  * 
9 | */ 10 | public enum GlobalBusinessCodes implements BusinessCode { 11 | 12 | C001("something"), 13 | C002("something+1"); 14 | 15 | private final String description; 16 | 17 | GlobalBusinessCodes(String description) { 18 | this.description = description; 19 | } 20 | 21 | public String getDescription() { 22 | return this.description; 23 | } 24 | 25 | public String getCode() { 26 | return this.name(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/controller/BuildController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import javax.annotation.PostConstruct; 8 | import java.util.Map; 9 | import java.util.TreeMap; 10 | 11 | import de.michlb.demo.customer.constant.ApiConstants; 12 | 13 | /** 14 | *
Provides build/git related details
15 | */ 16 | @RestController 17 | public class BuildController { 18 | 19 | private TreeMap build = new TreeMap<>(); 20 | 21 | @Value("${git.commit.id.abbrev}") 22 | private String gitCommitAbbrevVal; 23 | private static String gitCommitAbbrevKey = "git.commit.id.abbrev"; 24 | 25 | @Value("${git.commit.user.email}") 26 | private String gitCommitEmailVal; 27 | private static String gitCommitEmailKey = "git.commit.user.email"; 28 | 29 | @Value("${git.commit.message.full}") 30 | private String gitCommitMsgVal; 31 | private static String gitCommitMsgKey = "git.commit.message.full"; 32 | 33 | @Value("${git.commit.id}") 34 | private String gitCommitIdVal; 35 | private static String gitCommitIdKey = "git.commit.id"; 36 | 37 | @Value("${git.commit.user.name}") 38 | private String gitCommitUsernameVal; 39 | private static String gitCommitUsernamekey = "git.commit.user.name"; 40 | 41 | @Value("${git.commit.id.describe}") 42 | private String gitCommitDescVal; 43 | private static String gitCommitDescKey = "git.commit.id.describe"; 44 | 45 | @Value("${git.build.user.email}") 46 | private String gitBuildEmailVal; 47 | private static String gitBuildEmailKey = "git.build.user.email"; 48 | 49 | @Value("${git.branch}") 50 | private String gitBranchVal; 51 | private static String gitBranchKey = "git.branch"; 52 | 53 | @Value("${git.commit.time}") 54 | private String gitCommitTimeVal; 55 | private static String gitCommitTimeKey = "git.commit.time"; 56 | 57 | @Value("${git.build.time}") 58 | private String gitBuildTimeVal; 59 | private static String gitBuildTimeKey = "git.build.time"; 60 | 61 | @Value("${git.remote.origin.url}") 62 | private String gitOriginVal; 63 | private static String gitOriginKey = "git.remote.origin.url"; 64 | 65 | @Value("${info.app.maven.version}") 66 | private String mavenVersionVal; 67 | private static String mavenVersionKey = "maven.version"; 68 | 69 | @Value("${info.app.maven.artifactId}") 70 | private String mavenArtifactIdVal; 71 | private static String mavenArtifactIdKey = "maven.artifactId"; 72 | 73 | @Value("${info.app.maven.groupId}") 74 | private String mavenGroupIdVal; 75 | private static String mavenGroupIdKey = "maven.groupId"; 76 | 77 | @Value("${spring.profiles.active}") 78 | private String springProfileVal; 79 | private static String springProfileKey = "spring.profiles.active"; 80 | 81 | @Value("${spring.application.name}") 82 | private String springAppNameVal; 83 | private static String springAppNameKey = "spring.application.name"; 84 | 85 | @Value("${server.port}") 86 | private int springPortVal; 87 | private static String springPortKey = "spring.server.port"; 88 | 89 | @PostConstruct 90 | public void setup() { 91 | build.put(springProfileKey, springProfileVal); 92 | build.put(springAppNameKey, springAppNameVal); 93 | build.put(springPortKey, springPortVal); 94 | build.put(mavenVersionKey, mavenVersionVal); 95 | build.put(mavenArtifactIdKey, mavenArtifactIdVal); 96 | build.put(mavenGroupIdKey, mavenGroupIdVal); 97 | build.put(gitCommitAbbrevKey, gitCommitAbbrevVal); 98 | build.put(gitCommitEmailKey, gitCommitEmailVal); 99 | build.put(gitCommitMsgKey, gitCommitMsgVal); 100 | build.put(gitCommitIdKey, gitCommitIdVal); 101 | build.put(gitCommitUsernamekey, gitCommitUsernameVal); 102 | build.put(gitCommitDescKey, gitCommitDescVal); 103 | build.put(gitBuildEmailKey, gitBuildEmailVal); 104 | build.put(gitBranchKey, gitBranchVal); 105 | build.put(gitCommitTimeKey, gitCommitTimeVal); 106 | build.put(gitBuildTimeKey, gitBuildTimeVal); 107 | build.put(gitOriginKey, gitOriginVal); 108 | } 109 | 110 | @RequestMapping(ApiConstants.API_MAN_BUILD) 111 | public Map build() { 112 | return build; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/controller/CustomerController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.controller; 2 | 3 | import de.michlb.demo.customer.constant.ApiConstants; 4 | import de.michlb.demo.customer.model.Customer; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * This is an example controller with a feign client. 15 | */ 16 | @RestController 17 | public class CustomerController { 18 | 19 | /** 20 | * Used to provide tools a HTTP 200 OK when service is running. 21 | */ 22 | @RequestMapping(path = ApiConstants.API_CUSTOMER_INFO) 23 | public ResponseEntity info() { 24 | Customer customer = new Customer(); 25 | 26 | customer.setFirstname("Michael"); 27 | customer.setLastname("B."); 28 | 29 | return new ResponseEntity<>(customer, HttpStatus.OK); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/controller/HelpController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.controller; 2 | 3 | import de.michlb.demo.customer.constant.ApiConstants; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import de.michlb.demo.customer.constant.GlobalBusinessCodes; 14 | 15 | /** 16 | *
17 |  *      Controller provides clients with a definitive list of all HTTP and Business codes.
18 |  * 
19 | */ 20 | @RestController 21 | public class HelpController { 22 | 23 | private Map businessCodes = new HashMap<>(); 24 | private Map httpCodes = new HashMap<>(); 25 | private Map> codes = new HashMap<>(); 26 | 27 | 28 | @PostConstruct 29 | public void setup(){ 30 | 31 | for(GlobalBusinessCodes code : GlobalBusinessCodes.values()) { 32 | businessCodes.put(code.getCode(), code.getDescription()); 33 | } 34 | 35 | codes.put("businessCodes", businessCodes); 36 | 37 | 38 | for(HttpStatus httpStatus : HttpStatus.values()) { 39 | httpCodes.put(httpStatus.value(), httpStatus.getReasonPhrase()); 40 | } 41 | 42 | codes.put("httpCodes", httpCodes); 43 | } 44 | 45 | @RequestMapping(path = ApiConstants.API_HELP) 46 | public ResponseEntity help(){ 47 | return new ResponseEntity(codes, HttpStatus.OK); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/controller/PingController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.controller; 2 | 3 | import de.michlb.demo.customer.feign.client.AccountClient; 4 | import de.michlb.demo.customer.constant.GlobalBusinessCodes; 5 | import de.michlb.demo.customer.constant.ApiConstants; 6 | import com.shedhack.exception.core.BusinessException; 7 | import com.shedhack.thread.context.annotation.ThreadContext; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.*; 17 | 18 | /** 19 | * This is an example controller with a feign client. 20 | */ 21 | @RestController 22 | public class PingController { 23 | 24 | /** 25 | * Used to provide tools a HTTP 200 OK when service is running. 26 | */ 27 | @RequestMapping(path = ApiConstants.API_PING) 28 | public ResponseEntity ping(){ 29 | return new ResponseEntity<>("Application is running.....", HttpStatus.OK); 30 | } 31 | 32 | // --------------------------------- 33 | // Methods for demostration purposes 34 | // --------------------------------- 35 | 36 | @Autowired 37 | private AccountClient client; 38 | 39 | /** 40 | * THIS METHOD IS A FEIGN EXAMPLE, PLEASE SEE AccountClient 41 | * Remove when you're happy. 42 | */ 43 | @ThreadContext 44 | @RequestMapping(value = "/api/accounts", method = RequestMethod.GET) 45 | public ResponseEntity> getAllAccountBalances() { 46 | 47 | List users = Arrays.asList("Batman", "Joker", "Superman", "Lex"); 48 | Map balances = new HashMap(users.size()); 49 | 50 | for(String user : users) { 51 | balances.put(user, client.getBalance(1).getBody()); 52 | } 53 | 54 | return new ResponseEntity<>(balances, HttpStatus.OK); 55 | } 56 | 57 | /** 58 | * THIS METHOD IS A FEIGN EXAMPLE, PLEASE SEE AccountClient. 59 | * Remove when you're happy. 60 | */ 61 | @ThreadContext 62 | @RequestMapping(value = "/api/accounts/problem", method = RequestMethod.GET) 63 | public ResponseEntity> getAllAccountBalancesThrowException() { 64 | 65 | List users = Arrays.asList("Batman", "Joker", "Superman", "Lex"); 66 | Map balances = new HashMap(users.size()); 67 | 68 | for(String user : users) { 69 | balances.put(user, client.getBalance(11).getBody()); 70 | } 71 | 72 | return new ResponseEntity<>(balances, HttpStatus.OK); 73 | } 74 | 75 | /** 76 | * THIS METHOD IS A FEIGN EXAMPLE, PLEASE SEE AccountClient. 77 | * Remove when you're happy. 78 | */ 79 | @ThreadContext 80 | @RequestMapping(value = "/api/accounts/{id}/balance", method = RequestMethod.GET) 81 | public ResponseEntity getBalance(@PathVariable("id") long id) { 82 | 83 | // Lets throw an exception if the id > 10 84 | if(id > 10) { 85 | throw BusinessException.builder(new IllegalArgumentException("Invalid account number")).generateId() 86 | .withBusinessCode(GlobalBusinessCodes.C001).withParam("id", id).build(); 87 | } 88 | 89 | Random rn = new Random(); 90 | int range = 1000000000 - 10 + 1; 91 | int randomNum = rn.nextInt(range) + 10; 92 | 93 | return new ResponseEntity<>(randomNum, HttpStatus.OK); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/feign/FeignConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.feign; 2 | 3 | import de.michlb.demo.customer.feign.interceptor.TraceRequestInterceptor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | *
 9 |  *     Configuration for Feign Clients.
10 |  * 
11 | */ 12 | @Configuration 13 | public class FeignConfiguration { 14 | 15 | @Bean 16 | public TraceRequestInterceptor traceRequestInterceptor() { 17 | return new TraceRequestInterceptor(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/feign/client/AccountClient.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.feign.client; 2 | 3 | 4 | import de.michlb.demo.customer.feign.FeignConfiguration; 5 | import org.springframework.cloud.netflix.feign.FeignClient; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestMethod; 10 | 11 | @FeignClient(name = "${account.service.name}", configuration = FeignConfiguration.class) 12 | public interface AccountClient { 13 | 14 | @RequestMapping(value = "/api/accounts/{id}/balance", method = RequestMethod.GET) 15 | ResponseEntity getBalance(@PathVariable("id") long id); 16 | } 17 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/feign/interceptor/TraceRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.feign.interceptor; 2 | 3 | import com.shedhack.trace.request.api.constant.HttpHeaderKeysEnum; 4 | import com.shedhack.trace.request.api.threadlocal.RequestThreadLocalHelper; 5 | import feign.RequestInterceptor; 6 | import feign.RequestTemplate; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | *
11 |  *     Before any requests are made Feign Clients we set some header properties.
12 |  *     These properties have been set by the Trace Request Filter.
13 |  * 
14 | */ 15 | @Component 16 | public class TraceRequestInterceptor implements RequestInterceptor { 17 | 18 | @Override 19 | public void apply(RequestTemplate requestTemplate) { 20 | 21 | if( RequestThreadLocalHelper.get() !=null) { 22 | 23 | requestTemplate.header(HttpHeaderKeysEnum.CALLER_ID.key(), RequestThreadLocalHelper.get().getRequestId()); 24 | requestTemplate.header(HttpHeaderKeysEnum.GROUP_ID.key(), RequestThreadLocalHelper.get().getGroupId()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /customer/src/main/java/de/michlb/demo/customer/model/Customer.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer.model; 2 | 3 | public class Customer { 4 | 5 | private String firstname; 6 | private String lastname; 7 | 8 | public String getFirstname() { 9 | return firstname; 10 | } 11 | 12 | public void setFirstname(String firstname) { 13 | this.firstname = firstname; 14 | } 15 | 16 | public String getLastname() { 17 | return lastname; 18 | } 19 | 20 | public void setLastname(String lastname) { 21 | this.lastname = lastname; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /customer/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # When you're ready to move to spring-cloud-config then this file should be served by that service 2 | # Eureka server has been added and is available as a sibling to the rest module. 3 | 4 | spring: 5 | application.version: @pom.version@ 6 | 7 | server: 8 | port: 8080 9 | 10 | # When Eureka is available set accordingly 11 | eureka: 12 | client: 13 | serviceUrl: 14 | defaultZone: http://localhost:8071/eureka/ 15 | instance: 16 | status-page-url: ${management.contextPath}/info 17 | health-check-url-path: ${management.contextPath}/health 18 | 19 | management.contextPath: /admin 20 | #management.port: 8081 21 | #management.security.enabled: true 22 | #security.basic.enabled: true 23 | #security.user.name: admin 24 | #security.user.password: admin 25 | 26 | # Custom actuators 27 | exception.interceptor.queue.size: 50 28 | exception.interceptor.endpoint: exceptions 29 | exception.interceptor.stacktrace: true 30 | trace.interceptor.queue.size: 50 31 | trace.interceptor.endpoint: requests 32 | 33 | 34 | # Trace Request JPA requires these settings 35 | # Please modify for your desired DB. Currently uses an inmemory instance of HSQL. 36 | trace-request: 37 | hibernate: 38 | hbm2ddl.auto: create-drop 39 | dialect: org.hibernate.dialect.HSQLDialect 40 | jdbc: 41 | driverClassName: org.hsqldb.jdbc.JDBCDriver 42 | url: jdbc:hsqldb:file:request 43 | user: SA 44 | pass: 45 | 46 | # Properties used by /admin/info and /admin/build endpoints. 47 | info.app.name: ${spring.application.name} 48 | info.app.profile: ${spring.profiles.active} 49 | info.app.maven.version: @pom.version@ 50 | info.app.maven.artifactId: @pom.artifactId@ 51 | info.app.maven.groupId: @pom.groupId@ 52 | 53 | # If you wish to disable hystrix uncomment 54 | #feign.hystrix.enabled: false 55 | 56 | # Default feign requests run in different threads 57 | hystrix.command.default.execution.isolation.strategy: SEMAPHORE 58 | 59 | # Please remove this setting - added for demo, see AccountClient 60 | account.service.name: ${spring.application.name} 61 | 62 | --- 63 | spring: 64 | profiles: development 65 | 66 | 67 | --- 68 | spring: 69 | profiles: test 70 | 71 | 72 | --- 73 | spring: 74 | profiles: production 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /customer/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | . ____ _ __ _ _ 2 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ 3 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 4 | \\/ ___)| |_)| | | | | || (_| | ) ) ) ) 5 | ' |____| .__|_| |_|_| |_\__, | / / / / 6 | =========|_|==============|___/=/_/_/_/ 7 | :: Spring Boot :: 8 | :: ${spring.application.name} :: v${spring.application.version} 9 | :: profile - ${spring.profiles.active} 10 | 11 | ${spring.application.name} has successfully started on port ${server.port}........... 12 | -------------------------------------------------------------------------------- /customer/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: 4 | active: development 5 | application: 6 | name: customer 7 | 8 | # If you wish to use Spring Cloud Config then set the path accordingly 9 | #spring.cloud.config.uri: http://localhost:8070 10 | -------------------------------------------------------------------------------- /customer/src/main/resources/git-build.properties: -------------------------------------------------------------------------------- 1 | git.commit.id.abbrev= 2 | git.commit.user.email= 3 | git.commit.message.full= 4 | git.commit.id= 5 | git.commit.message.short= 6 | git.commit.user.name= 7 | git.build.user.name= 8 | git.commit.id.describe= 9 | git.build.user.email= 10 | git.branch= 11 | git.commit.time= 12 | git.build.time= 13 | git.remote.origin.url= -------------------------------------------------------------------------------- /customer/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /customer/src/test/java/de/michlb/demo/customer/PingControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer; 2 | 3 | import de.michlb.demo.customer.constant.ApiConstants; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.test.IntegrationTest; 9 | import org.springframework.boot.test.SpringApplicationConfiguration; 10 | import org.springframework.boot.test.TestRestTemplate; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | import org.springframework.test.context.web.WebAppConfiguration; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | import java.net.URL; 17 | 18 | import static org.hamcrest.Matchers.equalTo; 19 | import static org.junit.Assert.assertThat; 20 | 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | @SpringApplicationConfiguration(classes = Application.class) 23 | @WebAppConfiguration 24 | @IntegrationTest({"server.port=0"}) 25 | public class PingControllerIntegrationTest { 26 | 27 | @Value("${local.server.port}") 28 | private int port; 29 | 30 | private URL base; 31 | private RestTemplate template; 32 | 33 | @Before 34 | public void setUp() throws Exception { 35 | this.base = new URL("http://localhost:" + port + "/" + ApiConstants.API_PING); 36 | template = new TestRestTemplate(); 37 | } 38 | 39 | @Test 40 | public void checkPing() throws Exception { 41 | ResponseEntity response = template.getForEntity(base.toString(), String.class); 42 | assertThat(response.getBody(), equalTo("Application is running.....")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /customer/src/test/java/de/michlb/demo/customer/PingControllerTest.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.customer; 2 | 3 | import de.michlb.demo.customer.constant.ApiConstants; 4 | import de.michlb.demo.customer.controller.PingController; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.boot.test.SpringApplicationConfiguration; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.mock.web.MockServletContext; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | import org.springframework.test.context.web.WebAppConfiguration; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 16 | 17 | import static org.hamcrest.Matchers.equalTo; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | @SpringApplicationConfiguration(classes = MockServletContext.class) 23 | @WebAppConfiguration 24 | public class PingControllerTest { 25 | 26 | private MockMvc mvc; 27 | 28 | @Before 29 | public void setUp() throws Exception { 30 | mvc = MockMvcBuilders.standaloneSetup(new PingController()).build(); 31 | } 32 | 33 | @Test 34 | public void checkPing() throws Exception { 35 | mvc.perform(MockMvcRequestBuilders.get(ApiConstants.API_PING).accept(MediaType.APPLICATION_JSON)) 36 | .andExpect(status().isOk()) 37 | .andExpect(content().string(equalTo("Application is running....."))); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /customer/src/test/jmeter/Microservice test plan.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | host 18 | ${__P(host, localhost)} 19 | = 20 | 21 | 22 | port 23 | ${__P(port, 8080)} 24 | = 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Content-Type 33 | application/json 34 | 35 | 36 | 37 | 38 | 39 | continue 40 | 41 | false 42 | 1 43 | 44 | 10 45 | 1 46 | 1460289476000 47 | 1460289476000 48 | false 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ${host} 58 | ${port} 59 | 60 | 61 | 62 | 63 | /api/ping 64 | GET 65 | true 66 | false 67 | true 68 | false 69 | false 70 | 71 | 72 | 73 | 74 | 75 | 200 76 | 77 | 78 | Assertion.response_code 79 | false 80 | 2 81 | 82 | 83 | 84 | 85 | false 86 | 87 | saveConfig 88 | 89 | 90 | true 91 | true 92 | true 93 | 94 | true 95 | true 96 | true 97 | true 98 | false 99 | true 100 | true 101 | false 102 | false 103 | false 104 | false 105 | false 106 | false 107 | false 108 | false 109 | 0 110 | true 111 | true 112 | 113 | 114 | 115 | 116 | 117 | 118 | false 119 | 120 | saveConfig 121 | 122 | 123 | true 124 | true 125 | true 126 | 127 | true 128 | true 129 | true 130 | true 131 | false 132 | true 133 | true 134 | false 135 | false 136 | false 137 | false 138 | false 139 | false 140 | false 141 | false 142 | 0 143 | true 144 | true 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /eureka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | org.springframework.cloud 8 | spring-cloud-starter-parent 9 | Brixton.RELEASE 10 | 11 | 12 | 13 | 14 | 4.0.0 15 | 16 | de.michlb.demo 17 | eureka 18 | Cloud Eureka Server 19 | 20 | 21 | UTF-8 22 | de.michlb.demo.eureka.Application 23 | 1.8 24 | 25 | 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-starter-eureka-server 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | 39 | 40 | eureka 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /eureka/run.cmd: -------------------------------------------------------------------------------- 1 | title run:eureka:8071 2 | mvn spring-boot:run -Drun.jvmArguments="-Dserver.port=8071 -Xms128m -Xmx256m" 3 | -------------------------------------------------------------------------------- /eureka/src/main/java/de/michlb/demo/eureka/Application.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.eureka; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaServer 9 | public class Application { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(Application.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /eureka/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: eureka 4 | profiles: default 5 | 6 | server: 7 | port: 8071 8 | 9 | eureka: 10 | client: 11 | registerWithEureka: false 12 | fetchRegistry: false 13 | serviceUrl: 14 | defaultZone: http://localhost:8071/eureka/ 15 | server: 16 | waitTimeInMsWhenSyncEmpty: 0 17 | enable-self-preservation: false 18 | 19 | instance: 20 | hostname: eureka 21 | -------------------------------------------------------------------------------- /eureka/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | . ____ _ __ _ _ 2 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ 3 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 4 | \\/ ___)| |_)| | | | | || (_| | ) ) ) ) 5 | ' |____| .__|_| |_|_| |_\__, | / / / / 6 | =========|_|==============|___/=/_/_/_/ 7 | :: Spring Boot :: 8 | :: ${spring.application.name} :: v${spring.application.version} 9 | :: profile - ${spring.profiles.active} 10 | 11 | ${spring.application.name} has successfully started on port ${server.port}........... 12 | 13 | ======================= 14 | 15 | EUREKA SERVER 16 | 17 | ======================= 18 | -------------------------------------------------------------------------------- /product/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.cloud 8 | spring-cloud-starter-parent 9 | Brixton.RELEASE 10 | 11 | 12 | 13 | 14 | de.michlb.demo 15 | product 16 | 1.0.0-SNAPSHOT 17 | jar 18 | product 19 | Module contains the restful controllers. 20 | 21 | 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | 29 | 30 | com.shedhack.trace 31 | trace-request-api 32 | 1.0.1 33 | 34 | 35 | 36 | com.shedhack.trace 37 | trace-request-filter 38 | 1.0.3 39 | 40 | 41 | 42 | com.shedhack.trace 43 | trace-request-jpa 44 | 1.0.2 45 | 46 | 47 | 48 | com.shedhack.exception 49 | exception-controller-spring 50 | 1.0.5 51 | 52 | 53 | 54 | com.shedhack.exception 55 | exception-core 56 | 1.0.4 57 | 58 | 59 | 60 | com.shedhack.thread 61 | thread-context-aspect 62 | 1.0.8 63 | 64 | 65 | 66 | com.shedhack.spring 67 | spring-actuator 68 | 1.0.1 69 | 70 | 71 | 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-web 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-starter-actuator 81 | 82 | 83 | 84 | org.springframework.boot 85 | spring-boot-actuator-docs 86 | 87 | 88 | 89 | org.webjars 90 | hal-browser 91 | 92 | 93 | 94 | 96 | 97 | org.hsqldb 98 | hsqldb 99 | runtime 100 | 101 | 102 | 103 | 104 | org.springframework.cloud 105 | spring-cloud-starter-config 106 | 107 | 108 | 109 | org.springframework.cloud 110 | spring-cloud-starter-eureka 111 | 112 | 113 | 114 | org.springframework.cloud 115 | spring-cloud-starter-ribbon 116 | 117 | 118 | 119 | org.springframework.cloud 120 | spring-cloud-starter-feign 121 | compile 122 | 123 | 124 | 125 | org.springframework.cloud 126 | spring-cloud-starter-hystrix 127 | 128 | 129 | 130 | org.springframework.cloud 131 | spring-cloud-starter-hystrix-dashboard 132 | 133 | 134 | 135 | 136 | io.springfox 137 | springfox-swagger2 138 | 2.4.0 139 | 140 | 141 | 142 | io.springfox 143 | springfox-swagger-ui 144 | 2.4.0 145 | 146 | 147 | 148 | 149 | 150 | org.springframework.boot 151 | spring-boot-starter-test 152 | test 153 | 154 | 155 | 156 | 157 | 158 | org.springframework.boot 159 | spring-boot-devtools 160 | 161 | 162 | 163 | 164 | 165 | product 166 | 167 | 168 | 169 | 170 | src/main/resources 171 | true 172 | 173 | **/*.properties 174 | **/*.yml 175 | 176 | 177 | 178 | src/main/resources 179 | false 180 | 181 | **/*.xml 182 | 183 | 184 | 185 | 186 | 187 | 189 | 190 | org.apache.maven.plugins 191 | maven-resources-plugin 192 | 2.7 193 | 194 | 195 | @ 196 | 197 | false 198 | 199 | 200 | 201 | 202 | 203 | org.springframework.boot 204 | spring-boot-maven-plugin 205 | 206 | 207 | 208 | true 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /product/run.cmd: -------------------------------------------------------------------------------- 1 | title run:product:8082 2 | mvn spring-boot:run -Drun.jvmArguments="-Dserver.port=8082 -Xms128m -Xmx256m" 3 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/Application.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product; 2 | 3 | import de.michlb.demo.product.constant.ApiConstants; 4 | import com.shedhack.exception.controller.spring.config.EnableExceptionController; 5 | import com.shedhack.spring.actuator.config.EnableActuatorsAndInterceptors; 6 | import com.shedhack.spring.actuator.interceptor.TraceRequestHandler; 7 | import com.shedhack.thread.context.config.EnableThreadContextAspect; 8 | import com.shedhack.trace.request.api.service.TraceRequestService; 9 | import com.shedhack.trace.request.filter.DefaultLoggingHandler; 10 | import com.shedhack.trace.request.filter.RequestTraceFilter; 11 | import com.shedhack.trace.request.jpa.config.EnableTraceRequestJpa; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.boot.SpringApplication; 15 | import org.springframework.boot.autoconfigure.SpringBootApplication; 16 | import org.springframework.boot.context.embedded.FilterRegistrationBean; 17 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 18 | import org.springframework.cloud.netflix.feign.EnableFeignClients; 19 | import org.springframework.cloud.netflix.hystrix.EnableHystrix; 20 | import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.PropertySource; 23 | import org.springframework.context.annotation.PropertySources; 24 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 25 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 26 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 27 | import springfox.documentation.builders.ApiInfoBuilder; 28 | import springfox.documentation.builders.PathSelectors; 29 | import springfox.documentation.builders.RequestHandlerSelectors; 30 | import springfox.documentation.service.ApiInfo; 31 | import springfox.documentation.spi.DocumentationType; 32 | import springfox.documentation.spring.web.plugins.Docket; 33 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 34 | 35 | import java.util.Arrays; 36 | 37 | @SpringBootApplication 38 | @EnableSwagger2 39 | @EnableTraceRequestJpa 40 | @EnableExceptionController 41 | @EnableThreadContextAspect 42 | @EnableActuatorsAndInterceptors 43 | @PropertySources(value = { 44 | @PropertySource(value = "classpath:/git-build.properties") 45 | }) 46 | @EnableDiscoveryClient 47 | @EnableFeignClients 48 | @EnableHystrix 49 | @EnableHystrixDashboard 50 | public class Application extends WebMvcConfigurerAdapter { 51 | 52 | // -------------------- 53 | // Bean configurations: 54 | // -------------------- 55 | 56 | @Value("${spring.application.name}") 57 | private String appName; 58 | 59 | /** 60 | * Service is configured via {@link EnableTraceRequestJpa} 61 | */ 62 | @Autowired 63 | private TraceRequestService jpaTraceRequestService; 64 | 65 | /** 66 | * Service is configured via @EnableActuatorsAndInterceptors 67 | */ 68 | @Autowired 69 | private TraceRequestHandler traceRequestHandler; 70 | 71 | /** 72 | * Filter records and logs all HTTP requests. 73 | * This requires an implementation of {@link TraceRequestService}. 74 | */ 75 | @Bean 76 | public FilterRegistrationBean requestIdFilterRegistrationBean() { 77 | FilterRegistrationBean filter = new FilterRegistrationBean(); 78 | filter.setFilter(new RequestTraceFilter(appName, jpaTraceRequestService, 79 | Arrays.asList(new DefaultLoggingHandler(), traceRequestHandler))); 80 | filter.addUrlPatterns(ApiConstants.API_ROOT + "/*"); 81 | 82 | return filter; 83 | } 84 | 85 | // --------------------------------------- 86 | // Swagger setup for the API documentation 87 | // --------------------------------------- 88 | 89 | // If you add spring security then you can easily secure these resources. 90 | 91 | @Override 92 | public void addViewControllers(ViewControllerRegistry registry) { 93 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT + "/v2/api-docs", "/v2/api-docs"); 94 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT + "/configuration/ui", "/configuration/ui"); 95 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT +"/configuration/security", "/configuration/security"); 96 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT +"/swagger-resources", "/swagger-resources"); 97 | registry.addRedirectViewController(ApiConstants.API_DOCS_ROOT, "/swagger-ui.html"); 98 | } 99 | 100 | @Override 101 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 102 | registry 103 | .addResourceHandler("/" + ApiConstants.API_DOCS_ROOT + "/**") 104 | .addResourceLocations("classpath:/META-INF/resources/"); 105 | } 106 | 107 | public Docket createRestApi() { 108 | return new Docket(DocumentationType.SWAGGER_2) 109 | .apiInfo(apiInfo()) 110 | .select() 111 | .apis(RequestHandlerSelectors.basePackage("de.michlb.demo.product.rest.controller")) 112 | .paths(PathSelectors.any()) 113 | .build(); 114 | } 115 | 116 | private ApiInfo apiInfo() { 117 | return new ApiInfoBuilder() 118 | .title("API Documentation") 119 | .description("API Documentation") 120 | .termsOfServiceUrl("") 121 | .version("1.0") 122 | .build(); 123 | } 124 | 125 | /** 126 | * Future datasource beans need to marked with {@link org.springframework.context.annotation.Primary}. 127 | * to prevent conflicts with the DS used by EnableTraceRequestJpa. 128 | */ 129 | 130 | // --------------------------- 131 | // Main method to start Spring 132 | // --------------------------- 133 | public static void main(String[] args) { 134 | SpringApplication.run(Application.class, args); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/constant/ApiConstants.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.constant; 2 | 3 | /** 4 | *
 5 |  *     Static string representation of the API's endpoints.
 6 |  * 
7 | */ 8 | public class ApiConstants { 9 | 10 | public static final String API_ROOT = "/api"; 11 | 12 | public static final String API_HELP = API_ROOT + "/help"; 13 | 14 | public static final String API_PING = API_ROOT + "/ping"; 15 | 16 | public static final String API_DOCS_ROOT = "/docs"; 17 | 18 | // Management root URI 19 | public static final String API_MAN_ROOT ="/admin"; 20 | 21 | public static final String API_MAN_BUILD= API_MAN_ROOT + "/build"; 22 | 23 | // Specific URI 24 | public static final String API_PRODUCT_DETAIL = API_ROOT + "/product/detail"; 25 | } 26 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/constant/GlobalBusinessCodes.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.constant; 2 | 3 | import com.shedhack.exception.core.BusinessCode; 4 | 5 | /** 6 | *
 7 |  *     Business Codes which are shared with clients.
 8 |  * 
9 | */ 10 | public enum GlobalBusinessCodes implements BusinessCode { 11 | 12 | C001("something"), 13 | C002("something+1"); 14 | 15 | private final String description; 16 | 17 | GlobalBusinessCodes(String description) { 18 | this.description = description; 19 | } 20 | 21 | public String getDescription() { 22 | return this.description; 23 | } 24 | 25 | public String getCode() { 26 | return this.name(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/controller/BuildController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import javax.annotation.PostConstruct; 8 | import java.util.Map; 9 | import java.util.TreeMap; 10 | 11 | import de.michlb.demo.product.constant.ApiConstants; 12 | 13 | /** 14 | *
Provides build/git related details
15 | */ 16 | @RestController 17 | public class BuildController { 18 | 19 | private TreeMap build = new TreeMap<>(); 20 | 21 | @Value("${git.commit.id.abbrev}") 22 | private String gitCommitAbbrevVal; 23 | private static String gitCommitAbbrevKey = "git.commit.id.abbrev"; 24 | 25 | @Value("${git.commit.user.email}") 26 | private String gitCommitEmailVal; 27 | private static String gitCommitEmailKey = "git.commit.user.email"; 28 | 29 | @Value("${git.commit.message.full}") 30 | private String gitCommitMsgVal; 31 | private static String gitCommitMsgKey = "git.commit.message.full"; 32 | 33 | @Value("${git.commit.id}") 34 | private String gitCommitIdVal; 35 | private static String gitCommitIdKey = "git.commit.id"; 36 | 37 | @Value("${git.commit.user.name}") 38 | private String gitCommitUsernameVal; 39 | private static String gitCommitUsernamekey = "git.commit.user.name"; 40 | 41 | @Value("${git.commit.id.describe}") 42 | private String gitCommitDescVal; 43 | private static String gitCommitDescKey = "git.commit.id.describe"; 44 | 45 | @Value("${git.build.user.email}") 46 | private String gitBuildEmailVal; 47 | private static String gitBuildEmailKey = "git.build.user.email"; 48 | 49 | @Value("${git.branch}") 50 | private String gitBranchVal; 51 | private static String gitBranchKey = "git.branch"; 52 | 53 | @Value("${git.commit.time}") 54 | private String gitCommitTimeVal; 55 | private static String gitCommitTimeKey = "git.commit.time"; 56 | 57 | @Value("${git.build.time}") 58 | private String gitBuildTimeVal; 59 | private static String gitBuildTimeKey = "git.build.time"; 60 | 61 | @Value("${git.remote.origin.url}") 62 | private String gitOriginVal; 63 | private static String gitOriginKey = "git.remote.origin.url"; 64 | 65 | @Value("${info.app.maven.version}") 66 | private String mavenVersionVal; 67 | private static String mavenVersionKey = "maven.version"; 68 | 69 | @Value("${info.app.maven.artifactId}") 70 | private String mavenArtifactIdVal; 71 | private static String mavenArtifactIdKey = "maven.artifactId"; 72 | 73 | @Value("${info.app.maven.groupId}") 74 | private String mavenGroupIdVal; 75 | private static String mavenGroupIdKey = "maven.groupId"; 76 | 77 | @Value("${spring.profiles.active}") 78 | private String springProfileVal; 79 | private static String springProfileKey = "spring.profiles.active"; 80 | 81 | @Value("${spring.application.name}") 82 | private String springAppNameVal; 83 | private static String springAppNameKey = "spring.application.name"; 84 | 85 | @Value("${server.port}") 86 | private int springPortVal; 87 | private static String springPortKey = "spring.server.port"; 88 | 89 | @PostConstruct 90 | public void setup() { 91 | build.put(springProfileKey, springProfileVal); 92 | build.put(springAppNameKey, springAppNameVal); 93 | build.put(springPortKey, springPortVal); 94 | build.put(mavenVersionKey, mavenVersionVal); 95 | build.put(mavenArtifactIdKey, mavenArtifactIdVal); 96 | build.put(mavenGroupIdKey, mavenGroupIdVal); 97 | build.put(gitCommitAbbrevKey, gitCommitAbbrevVal); 98 | build.put(gitCommitEmailKey, gitCommitEmailVal); 99 | build.put(gitCommitMsgKey, gitCommitMsgVal); 100 | build.put(gitCommitIdKey, gitCommitIdVal); 101 | build.put(gitCommitUsernamekey, gitCommitUsernameVal); 102 | build.put(gitCommitDescKey, gitCommitDescVal); 103 | build.put(gitBuildEmailKey, gitBuildEmailVal); 104 | build.put(gitBranchKey, gitBranchVal); 105 | build.put(gitCommitTimeKey, gitCommitTimeVal); 106 | build.put(gitBuildTimeKey, gitBuildTimeVal); 107 | build.put(gitOriginKey, gitOriginVal); 108 | } 109 | 110 | @RequestMapping(ApiConstants.API_MAN_BUILD) 111 | public Map build() { 112 | return build; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/controller/HelpController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.controller; 2 | 3 | import de.michlb.demo.product.constant.ApiConstants; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import de.michlb.demo.product.constant.GlobalBusinessCodes; 14 | 15 | /** 16 | *
17 |  *      Controller provides clients with a definitive list of all HTTP and Business codes.
18 |  * 
19 | */ 20 | @RestController 21 | public class HelpController { 22 | 23 | private Map businessCodes = new HashMap<>(); 24 | private Map httpCodes = new HashMap<>(); 25 | private Map> codes = new HashMap<>(); 26 | 27 | 28 | @PostConstruct 29 | public void setup(){ 30 | 31 | for(GlobalBusinessCodes code : GlobalBusinessCodes.values()) { 32 | businessCodes.put(code.getCode(), code.getDescription()); 33 | } 34 | 35 | codes.put("businessCodes", businessCodes); 36 | 37 | 38 | for(HttpStatus httpStatus : HttpStatus.values()) { 39 | httpCodes.put(httpStatus.value(), httpStatus.getReasonPhrase()); 40 | } 41 | 42 | codes.put("httpCodes", httpCodes); 43 | } 44 | 45 | @RequestMapping(path = ApiConstants.API_HELP) 46 | public ResponseEntity help(){ 47 | return new ResponseEntity(codes, HttpStatus.OK); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/controller/PingController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.controller; 2 | 3 | import de.michlb.demo.product.feign.client.AccountClient; 4 | import de.michlb.demo.product.constant.GlobalBusinessCodes; 5 | import de.michlb.demo.product.constant.ApiConstants; 6 | import com.shedhack.exception.core.BusinessException; 7 | import com.shedhack.thread.context.annotation.ThreadContext; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.*; 17 | 18 | /** 19 | * This is an example controller with a feign client. 20 | */ 21 | @RestController 22 | public class PingController { 23 | 24 | /** 25 | * Used to provide tools a HTTP 200 OK when service is running. 26 | */ 27 | @RequestMapping(path = ApiConstants.API_PING) 28 | public ResponseEntity ping(){ 29 | return new ResponseEntity<>("Application is running.....", HttpStatus.OK); 30 | } 31 | 32 | // --------------------------------- 33 | // Methods for demostration purposes 34 | // --------------------------------- 35 | 36 | @Autowired 37 | private AccountClient client; 38 | 39 | /** 40 | * THIS METHOD IS A FEIGN EXAMPLE, PLEASE SEE AccountClient 41 | * Remove when you're happy. 42 | */ 43 | @ThreadContext 44 | @RequestMapping(value = "/api/accounts", method = RequestMethod.GET) 45 | public ResponseEntity> getAllAccountBalances() { 46 | 47 | List users = Arrays.asList("Batman", "Joker", "Superman", "Lex"); 48 | Map balances = new HashMap(users.size()); 49 | 50 | for(String user : users) { 51 | balances.put(user, client.getBalance(1).getBody()); 52 | } 53 | 54 | return new ResponseEntity<>(balances, HttpStatus.OK); 55 | } 56 | 57 | /** 58 | * THIS METHOD IS A FEIGN EXAMPLE, PLEASE SEE AccountClient. 59 | * Remove when you're happy. 60 | */ 61 | @ThreadContext 62 | @RequestMapping(value = "/api/accounts/problem", method = RequestMethod.GET) 63 | public ResponseEntity> getAllAccountBalancesThrowException() { 64 | 65 | List users = Arrays.asList("Batman", "Joker", "Superman", "Lex"); 66 | Map balances = new HashMap(users.size()); 67 | 68 | for(String user : users) { 69 | balances.put(user, client.getBalance(11).getBody()); 70 | } 71 | 72 | return new ResponseEntity<>(balances, HttpStatus.OK); 73 | } 74 | 75 | /** 76 | * THIS METHOD IS A FEIGN EXAMPLE, PLEASE SEE AccountClient. 77 | * Remove when you're happy. 78 | */ 79 | @ThreadContext 80 | @RequestMapping(value = "/api/accounts/{id}/balance", method = RequestMethod.GET) 81 | public ResponseEntity getBalance(@PathVariable("id") long id) { 82 | 83 | // Lets throw an exception if the id > 10 84 | if(id > 10) { 85 | throw BusinessException.builder(new IllegalArgumentException("Invalid account number")).generateId() 86 | .withBusinessCode(GlobalBusinessCodes.C001).withParam("id", id).build(); 87 | } 88 | 89 | Random rn = new Random(); 90 | int range = 1000000000 - 10 + 1; 91 | int randomNum = rn.nextInt(range) + 10; 92 | 93 | return new ResponseEntity<>(randomNum, HttpStatus.OK); 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/controller/ProductDetailController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.controller; 2 | 3 | import de.michlb.demo.product.constant.ApiConstants; 4 | import de.michlb.demo.product.model.Product; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * This is an example controller with a feign client. 15 | */ 16 | @RestController 17 | public class ProductDetailController { 18 | 19 | /** 20 | * Used to provide tools a HTTP 200 OK when service is running. 21 | */ 22 | @RequestMapping(path = ApiConstants.API_PRODUCT_DETAIL) 23 | public ResponseEntity> getList() { 24 | List productList = new ArrayList<>(); 25 | 26 | Product p1 = new Product(); 27 | p1.setName("MacBook Pro"); 28 | p1.setDescription( 29 | "MacBook Pro mit Retina Display. Jetzt mit dem Force Touch Trackpad, längerer Batterielaufzeit und schnellerem Flash-Speicher. 13 zoll ab 1.449 €. 15 zoll ab 2.249 €."); 30 | p1.setPictureUrl("http://images.apple.com/euro/macbook-pro/h/generic/images/overview_hero.jpg"); 31 | productList.add(p1); 32 | 33 | return new ResponseEntity<>(productList, HttpStatus.OK); 34 | } 35 | } -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/feign/FeignConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.feign; 2 | 3 | import de.michlb.demo.product.feign.interceptor.TraceRequestInterceptor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | *
 9 |  *     Configuration for Feign Clients.
10 |  * 
11 | */ 12 | @Configuration 13 | public class FeignConfiguration { 14 | 15 | @Bean 16 | public TraceRequestInterceptor traceRequestInterceptor() { 17 | return new TraceRequestInterceptor(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/feign/client/AccountClient.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.feign.client; 2 | 3 | 4 | import de.michlb.demo.product.feign.FeignConfiguration; 5 | import org.springframework.cloud.netflix.feign.FeignClient; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestMethod; 10 | 11 | @FeignClient(name = "${account.service.name}", configuration = FeignConfiguration.class) 12 | public interface AccountClient { 13 | 14 | @RequestMapping(value = "/api/accounts/{id}/balance", method = RequestMethod.GET) 15 | ResponseEntity getBalance(@PathVariable("id") long id); 16 | } 17 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/feign/interceptor/TraceRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.feign.interceptor; 2 | 3 | import com.shedhack.trace.request.api.constant.HttpHeaderKeysEnum; 4 | import com.shedhack.trace.request.api.threadlocal.RequestThreadLocalHelper; 5 | import feign.RequestInterceptor; 6 | import feign.RequestTemplate; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | *
11 |  *     Before any requests are made Feign Clients we set some header properties.
12 |  *     These properties have been set by the Trace Request Filter.
13 |  * 
14 | */ 15 | @Component 16 | public class TraceRequestInterceptor implements RequestInterceptor { 17 | 18 | @Override 19 | public void apply(RequestTemplate requestTemplate) { 20 | 21 | if( RequestThreadLocalHelper.get() !=null) { 22 | 23 | requestTemplate.header(HttpHeaderKeysEnum.CALLER_ID.key(), RequestThreadLocalHelper.get().getRequestId()); 24 | requestTemplate.header(HttpHeaderKeysEnum.GROUP_ID.key(), RequestThreadLocalHelper.get().getGroupId()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /product/src/main/java/de/michlb/demo/product/model/Product.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product.model; 2 | 3 | public class Product { 4 | 5 | private String name; 6 | private String description; 7 | private String pictureUrl; 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | public void setName(String name) { 14 | this.name = name; 15 | } 16 | 17 | public String getDescription() { 18 | return description; 19 | } 20 | 21 | public void setDescription(String description) { 22 | this.description = description; 23 | } 24 | 25 | public String getPictureUrl() { 26 | return pictureUrl; 27 | } 28 | 29 | public void setPictureUrl(String pictureUrl) { 30 | this.pictureUrl = pictureUrl; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /product/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # When you're ready to move to spring-cloud-config then this file should be served by that service 2 | # Eureka server has been added and is available as a sibling to the rest module. 3 | 4 | spring: 5 | application.version: @pom.version@ 6 | 7 | server: 8 | port: 8080 9 | 10 | # When Eureka is available set accordingly 11 | eureka: 12 | client: 13 | serviceUrl: 14 | defaultZone: http://localhost:8071/eureka/ 15 | instance: 16 | status-page-url: ${management.contextPath}/info 17 | health-check-url-path: ${management.contextPath}/health 18 | 19 | management.contextPath: /admin 20 | #management.port: 8081 21 | #management.security.enabled: true 22 | #security.basic.enabled: true 23 | #security.user.name: admin 24 | #security.user.password: admin 25 | 26 | # Custom actuators 27 | exception.interceptor.queue.size: 50 28 | exception.interceptor.endpoint: exceptions 29 | exception.interceptor.stacktrace: true 30 | trace.interceptor.queue.size: 50 31 | trace.interceptor.endpoint: requests 32 | 33 | 34 | # Trace Request JPA requires these settings 35 | # Please modify for your desired DB. Currently uses an inmemory instance of HSQL. 36 | trace-request: 37 | hibernate: 38 | hbm2ddl.auto: create-drop 39 | dialect: org.hibernate.dialect.HSQLDialect 40 | jdbc: 41 | driverClassName: org.hsqldb.jdbc.JDBCDriver 42 | url: jdbc:hsqldb:file:request 43 | user: SA 44 | pass: 45 | 46 | # Properties used by /admin/info and /admin/build endpoints. 47 | info.app.name: ${spring.application.name} 48 | info.app.profile: ${spring.profiles.active} 49 | info.app.maven.version: @pom.version@ 50 | info.app.maven.artifactId: @pom.artifactId@ 51 | info.app.maven.groupId: @pom.groupId@ 52 | 53 | # If you wish to disable hystrix uncomment 54 | #feign.hystrix.enabled: false 55 | 56 | # Default feign requests run in different threads 57 | hystrix.command.default.execution.isolation.strategy: SEMAPHORE 58 | 59 | # Please remove this setting - added for demo, see AccountClient 60 | account.service.name: ${spring.application.name} 61 | 62 | --- 63 | spring: 64 | profiles: development 65 | 66 | 67 | --- 68 | spring: 69 | profiles: test 70 | 71 | 72 | --- 73 | spring: 74 | profiles: production 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /product/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | . ____ _ __ _ _ 2 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ 3 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 4 | \\/ ___)| |_)| | | | | || (_| | ) ) ) ) 5 | ' |____| .__|_| |_|_| |_\__, | / / / / 6 | =========|_|==============|___/=/_/_/_/ 7 | :: Spring Boot :: 8 | :: ${spring.application.name} :: v${spring.application.version} 9 | :: profile - ${spring.profiles.active} 10 | 11 | ${spring.application.name} has successfully started on port ${server.port}........... 12 | -------------------------------------------------------------------------------- /product/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: 4 | active: development 5 | application: 6 | name: product 7 | 8 | # If you wish to use Spring Cloud Config then set the path accordingly 9 | #spring.cloud.config.uri: http://localhost:8070 10 | -------------------------------------------------------------------------------- /product/src/main/resources/git-build.properties: -------------------------------------------------------------------------------- 1 | git.commit.id.abbrev= 2 | git.commit.user.email= 3 | git.commit.message.full= 4 | git.commit.id= 5 | git.commit.message.short= 6 | git.commit.user.name= 7 | git.build.user.name= 8 | git.commit.id.describe= 9 | git.build.user.email= 10 | git.branch= 11 | git.commit.time= 12 | git.build.time= 13 | git.remote.origin.url= -------------------------------------------------------------------------------- /product/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /product/src/test/java/de/michlb/demo/product/PingControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product; 2 | 3 | import de.michlb.demo.product.constant.ApiConstants; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.test.IntegrationTest; 9 | import org.springframework.boot.test.SpringApplicationConfiguration; 10 | import org.springframework.boot.test.TestRestTemplate; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | import org.springframework.test.context.web.WebAppConfiguration; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | import java.net.URL; 17 | 18 | import static org.hamcrest.Matchers.equalTo; 19 | import static org.junit.Assert.assertThat; 20 | 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | @SpringApplicationConfiguration(classes = Application.class) 23 | @WebAppConfiguration 24 | @IntegrationTest({"server.port=0"}) 25 | public class PingControllerIntegrationTest { 26 | 27 | @Value("${local.server.port}") 28 | private int port; 29 | 30 | private URL base; 31 | private RestTemplate template; 32 | 33 | @Before 34 | public void setUp() throws Exception { 35 | this.base = new URL("http://localhost:" + port + "/" + ApiConstants.API_PING); 36 | template = new TestRestTemplate(); 37 | } 38 | 39 | @Test 40 | public void checkPing() throws Exception { 41 | ResponseEntity response = template.getForEntity(base.toString(), String.class); 42 | assertThat(response.getBody(), equalTo("Application is running.....")); 43 | } 44 | } -------------------------------------------------------------------------------- /product/src/test/java/de/michlb/demo/product/PingControllerTest.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.product; 2 | 3 | import de.michlb.demo.product.constant.ApiConstants; 4 | import de.michlb.demo.product.controller.PingController; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.boot.test.SpringApplicationConfiguration; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.mock.web.MockServletContext; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | import org.springframework.test.context.web.WebAppConfiguration; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 16 | 17 | import static org.hamcrest.Matchers.equalTo; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | @SpringApplicationConfiguration(classes = MockServletContext.class) 23 | @WebAppConfiguration 24 | public class PingControllerTest { 25 | 26 | private MockMvc mvc; 27 | 28 | @Before 29 | public void setUp() throws Exception { 30 | mvc = MockMvcBuilders.standaloneSetup(new PingController()).build(); 31 | } 32 | 33 | @Test 34 | public void checkPing() throws Exception { 35 | mvc.perform(MockMvcRequestBuilders.get(ApiConstants.API_PING).accept(MediaType.APPLICATION_JSON)) 36 | .andExpect(status().isOk()) 37 | .andExpect(content().string(equalTo("Application is running....."))); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /product/src/test/jmeter/Microservice test plan.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | host 18 | ${__P(host, localhost)} 19 | = 20 | 21 | 22 | port 23 | ${__P(port, 8080)} 24 | = 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Content-Type 33 | application/json 34 | 35 | 36 | 37 | 38 | 39 | continue 40 | 41 | false 42 | 1 43 | 44 | 10 45 | 1 46 | 1460289476000 47 | 1460289476000 48 | false 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ${host} 58 | ${port} 59 | 60 | 61 | 62 | 63 | /api/ping 64 | GET 65 | true 66 | false 67 | true 68 | false 69 | false 70 | 71 | 72 | 73 | 74 | 75 | 200 76 | 77 | 78 | Assertion.response_code 79 | false 80 | 2 81 | 82 | 83 | 84 | 85 | false 86 | 87 | saveConfig 88 | 89 | 90 | true 91 | true 92 | true 93 | 94 | true 95 | true 96 | true 97 | true 98 | false 99 | true 100 | true 101 | false 102 | false 103 | false 104 | false 105 | false 106 | false 107 | false 108 | false 109 | 0 110 | true 111 | true 112 | 113 | 114 | 115 | 116 | 117 | 118 | false 119 | 120 | saveConfig 121 | 122 | 123 | true 124 | true 125 | true 126 | 127 | true 128 | true 129 | true 130 | true 131 | false 132 | true 133 | true 134 | false 135 | false 136 | false 137 | false 138 | false 139 | false 140 | false 141 | false 142 | 0 143 | true 144 | true 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /zuul/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | org.springframework.cloud 10 | spring-cloud-starter-parent 11 | Brixton.RELEASE 12 | 13 | 14 | 15 | 16 | de.michlb.demo 17 | zuul 18 | 1.0.0-SNAPSHOT 19 | Cloud Zuul Server with integrated backends-for-frontends 20 | 21 | 22 | UTF-8 23 | de.michlb.demo.zuul.Application 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.cloud 30 | spring-cloud-starter-zuul 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-starter-eureka 41 | 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-starter-ribbon 46 | 47 | 48 | 49 | org.springframework.cloud 50 | spring-cloud-starter-feign 51 | compile 52 | 53 | 54 | 55 | org.springframework.cloud 56 | spring-cloud-starter-hystrix 57 | 58 | 59 | 60 | org.springframework.cloud 61 | spring-cloud-starter-hystrix-dashboard 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-test 67 | test 68 | 69 | 70 | 71 | 72 | zuul 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-maven-plugin 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /zuul/run.cmd: -------------------------------------------------------------------------------- 1 | title run:zuul:8081 2 | mvn spring-boot:run -Drun.jvmArguments="-Dserver.port=8081 -Xms128m -Xmx256m" 3 | -------------------------------------------------------------------------------- /zuul/src/main/java/de/michlb/demo/zuul/Application.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.zuul; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.cloud.netflix.feign.EnableFeignClients; 7 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 8 | 9 | @EnableZuulProxy 10 | @SpringBootApplication 11 | @EnableDiscoveryClient 12 | @EnableFeignClients 13 | public class Application { 14 | public static void main(String[] args) { 15 | SpringApplication.run(Application.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /zuul/src/main/java/de/michlb/demo/zuul/bff/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.zuul.bff.controller; 2 | 3 | import de.michlb.demo.zuul.feign.CustomerClient; 4 | import de.michlb.demo.zuul.feign.ProductClient; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestMethod; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | @RestController 15 | @RequestMapping("/bff/product") 16 | public class ProductController { 17 | 18 | @Autowired 19 | private ProductClient productClient; 20 | 21 | @Autowired 22 | private CustomerClient customerClient; 23 | 24 | /** 25 | * This call needs to be split into at least two calls and the result needs to be aggreagted 26 | * 27 | * @return Map of String of the service as key and the JSON result ass the value 28 | */ 29 | @RequestMapping(value = "/detail", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 30 | public Map productDetail() { 31 | Map aggregatedResult = new HashMap<>(); 32 | 33 | aggregatedResult.put("product", productClient.detail()); 34 | aggregatedResult.put("customer", customerClient.info()); 35 | 36 | return aggregatedResult; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /zuul/src/main/java/de/michlb/demo/zuul/feign/CustomerClient.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.zuul.feign; 2 | 3 | import org.springframework.cloud.netflix.feign.FeignClient; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | 7 | @FeignClient("customer") 8 | public interface CustomerClient { 9 | 10 | @RequestMapping(value = "api/customer/info", method = RequestMethod.GET) 11 | String info(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /zuul/src/main/java/de/michlb/demo/zuul/feign/ProductClient.java: -------------------------------------------------------------------------------- 1 | package de.michlb.demo.zuul.feign; 2 | 3 | import org.springframework.cloud.netflix.feign.FeignClient; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | 7 | @FeignClient("product") 8 | public interface ProductClient { 9 | 10 | @RequestMapping(value = "api/product/detail", method = RequestMethod.GET) 11 | String detail(); 12 | } 13 | -------------------------------------------------------------------------------- /zuul/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | . ____ _ __ _ _ 2 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ 3 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 4 | \\/ ___)| |_)| | | | | || (_| | ) ) ) ) 5 | ' |____| .__|_| |_|_| |_\__, | / / / / 6 | =========|_|==============|___/=/_/_/_/ 7 | :: Spring Boot :: 8 | :: ${spring.application.name} :: v${spring.application.version} 9 | :: profile - ${spring.profiles.active} 10 | 11 | ${spring.application.name} has successfully started on port ${server.port}........... 12 | 13 | ============================================== 14 | 15 | ZUUL SERVER with Backends-for-Frontends 16 | 17 | ============================================== 18 | -------------------------------------------------------------------------------- /zuul/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application: 4 | name: zuul 5 | 6 | # If you want to store these settings in the config server prefixed with [spring, e.g. spring.cloud.config] 7 | # cloud: 8 | # config: 9 | # uri: http://localhost:8070 10 | 11 | eureka: 12 | instance: 13 | leaseRenewalIntervalInSeconds: 10 14 | status-page-url: ${management.contextPath}/info 15 | health-check-url-path: ${management.contextPath}/health 16 | client: 17 | serviceUrl: 18 | defaultZone: http://localhost:8071/eureka/ 19 | 20 | # When running outside of eureka set this to false and remove the eureka settings above 21 | #ribbon.eureka.enabled: false 22 | 23 | server: 24 | port: 8081 25 | 26 | # Add the paths for the Gateway 27 | # XXX is the name of your app, e.g. X-rest and YYY is the endpoint you'd access 28 | zuul: 29 | ignoredPatterns: /**/admin/** 30 | # routes.product-be-rest: 31 | # path: /product-be-rest/** 32 | # url: http://phoenix-be-product.cfapps.io/ 33 | --------------------------------------------------------------------------------