├── .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 |
--------------------------------------------------------------------------------