├── .gitignore ├── README.md ├── pom.xml └── src └── main └── java └── uk └── co └── taidev └── experiments └── correlationidsync ├── Application.java ├── controllers └── NewsController.java ├── reporting └── RequestCorrelation.java ├── services ├── ExternalNewsService.java ├── ExternalNewsServiceRest.java ├── NewsService.java └── NewsServiceSimple.java └── web ├── client ├── CorrelatingRestClient.java └── RestClient.java └── filters └── CorrelationHeaderFilter.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | target/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | correlation-id-sync 2 | =================== 3 | 4 | This is a simple Spring boot-based application that demonstrates how request correlationIds could be generated and managed in order to correlate an initial request through a distributed (SOA/microservices) application. 5 | 6 | Please note that this version relies on ThreadLocal, and so will only work if you are handling requests synchronously in the initiating thread. As soon as you use a DeferredResult, Future or Callable, this reliance on ThreadLocal will not propogate the correlationIds unless they are manually passed as method parameters (or some similiar approach) 7 | 8 | Disclaimer: This is just an experiment and toy-example! I'm not suggesting this code is production-ready! :-) 9 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | uk.co.taidev.experiments 7 | correlation-id-sync 8 | 0.0.1-SNAPSHOT 9 | 10 | correlation-id-sync 11 | A simple example of implementing request correlation ids within a synchronous context 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 1.0.1.RELEASE 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | 27 | uk.co.taidev.experiments.correlationidsync.Application 28 | 1.8 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-maven-plugin 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/Application.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.context.embedded.FilterRegistrationBean; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.context.annotation.Configuration; 9 | import uk.co.taidev.experiments.correlationidsync.web.filters.CorrelationHeaderFilter; 10 | 11 | import java.util.Arrays; 12 | 13 | @Configuration 14 | @EnableAutoConfiguration 15 | @ComponentScan(basePackages = "uk.co.taidev.experiments.correlationidsync") 16 | public class Application { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(Application.class, args); 20 | } 21 | 22 | 23 | @Bean 24 | public FilterRegistrationBean correlationHeaderFilter() { 25 | FilterRegistrationBean filterRegBean = new FilterRegistrationBean(); 26 | filterRegBean.setFilter(new CorrelationHeaderFilter()); 27 | filterRegBean.setUrlPatterns(Arrays.asList("/*")); 28 | 29 | return filterRegBean; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/controllers/NewsController.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | import uk.co.taidev.experiments.correlationidsync.services.ExternalNewsService; 7 | import uk.co.taidev.experiments.correlationidsync.services.NewsService; 8 | 9 | /** 10 | * NewsController 11 | */ 12 | @RestController 13 | public class NewsController { 14 | 15 | @Autowired 16 | private ExternalNewsService externalNewsService; 17 | 18 | @Autowired 19 | private NewsService newsService; 20 | 21 | 22 | @RequestMapping("externalNews") 23 | public String externalNews() { 24 | return externalNewsService.getNews(); 25 | } 26 | 27 | 28 | @RequestMapping("news") 29 | public String news() { 30 | return newsService.getNews(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/reporting/RequestCorrelation.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.reporting; 2 | 3 | /** 4 | * Utility class which stores ThreadLocal (Request) correlation Id. 5 | */ 6 | public class RequestCorrelation { 7 | 8 | public static final String CORRELATION_ID_HEADER = "correlationId"; 9 | 10 | 11 | private static final ThreadLocal id = new ThreadLocal(); 12 | 13 | 14 | public static void setId(String correlationId) { 15 | id.set(correlationId); 16 | } 17 | 18 | public static String getId() { 19 | return id.get(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/services/ExternalNewsService.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.services; 2 | 3 | /** 4 | * ExternalNewsService 5 | */ 6 | public interface ExternalNewsService { 7 | 8 | String getNews(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/services/ExternalNewsServiceRest.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.services; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import uk.co.taidev.experiments.correlationidsync.web.client.RestClient; 6 | 7 | /** 8 | * ExternalNewsServiceRest 9 | */ 10 | @Service 11 | public class ExternalNewsServiceRest implements ExternalNewsService { 12 | 13 | private static final String NEWS_LOCATION = "http://localhost:8080/news"; 14 | 15 | 16 | @Autowired 17 | private RestClient restClient; 18 | 19 | 20 | @Override 21 | public String getNews() { 22 | //TODO: error-handling and fault-tolerance would be required here for production code 23 | return restClient.getForString(NEWS_LOCATION); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/services/NewsService.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.services; 2 | 3 | /** 4 | * NewsService 5 | */ 6 | public interface NewsService { 7 | 8 | String getNews(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/services/NewsServiceSimple.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.services; 2 | 3 | import org.springframework.stereotype.Service; 4 | import uk.co.taidev.experiments.correlationidsync.reporting.RequestCorrelation; 5 | 6 | /** 7 | * NewsServiceSimple 8 | */ 9 | @Service 10 | public class NewsServiceSimple implements NewsService { 11 | 12 | @Override 13 | public String getNews() { 14 | //you probably wouldn't pollute service code with correlation Ids (only external calls) 15 | //but it is included here as an example 16 | return String.format("No news is good news (with correlation Id '%s')", RequestCorrelation.getId()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/web/client/CorrelatingRestClient.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.web.client; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpEntity; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.HttpMethod; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.client.RestTemplate; 11 | import uk.co.taidev.experiments.correlationidsync.reporting.RequestCorrelation; 12 | 13 | /** 14 | * CorrelatingRestClient 15 | */ 16 | @Component 17 | public class CorrelatingRestClient implements RestClient { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(CorrelatingRestClient.class); 20 | 21 | private RestTemplate restTemplate = new RestTemplate(); 22 | 23 | 24 | @Override 25 | public String getForString(String uri) { 26 | String correlationId = RequestCorrelation.getId(); 27 | HttpHeaders httpHeaders = new HttpHeaders(); 28 | httpHeaders.set(RequestCorrelation.CORRELATION_ID_HEADER, correlationId); 29 | 30 | LOGGER.info("start REST request to {} with correlationId {}", uri, correlationId); 31 | 32 | //TODO: error-handling and fault-tolerance required in production code 33 | ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, 34 | new HttpEntity(httpHeaders), String.class); 35 | 36 | LOGGER.info("completed REST request to {} with correlationId {}", uri, correlationId); 37 | 38 | return response.getBody(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/web/client/RestClient.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.web.client; 2 | 3 | /** 4 | * RestClient 5 | */ 6 | public interface RestClient { 7 | 8 | String getForString(String uri); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/uk/co/taidev/experiments/correlationidsync/web/filters/CorrelationHeaderFilter.java: -------------------------------------------------------------------------------- 1 | package uk.co.taidev.experiments.correlationidsync.web.filters; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import uk.co.taidev.experiments.correlationidsync.reporting.RequestCorrelation; 6 | 7 | import javax.servlet.*; 8 | import javax.servlet.http.HttpServletRequest; 9 | import java.io.IOException; 10 | import java.util.UUID; 11 | 12 | /** 13 | * CorrelationHeaderFilter 14 | */ 15 | public class CorrelationHeaderFilter implements Filter { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(CorrelationHeaderFilter.class); 18 | 19 | 20 | public void init(FilterConfig filterConfig) throws ServletException { 21 | } 22 | 23 | 24 | @Override 25 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 26 | throws IOException, ServletException { 27 | 28 | final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; 29 | String currentCorrId = httpServletRequest.getHeader(RequestCorrelation.CORRELATION_ID_HEADER); 30 | 31 | if (currentCorrId == null) { 32 | currentCorrId = UUID.randomUUID().toString(); 33 | LOGGER.info("No correlationId found in Header. Generated : " + currentCorrId); 34 | } else { 35 | LOGGER.info("Found correlationId in Header : " + currentCorrId); 36 | } 37 | 38 | RequestCorrelation.setId(currentCorrId); 39 | 40 | filterChain.doFilter(httpServletRequest, servletResponse); 41 | } 42 | 43 | 44 | @Override 45 | public void destroy() { 46 | } 47 | 48 | } 49 | --------------------------------------------------------------------------------