Added template structure
This commit is contained in:
@ -0,0 +1,68 @@
|
||||
package de.twomartens.templateservice.actuator;
|
||||
|
||||
import de.twomartens.templateservice.interceptors.RequestTypeInterceptor;
|
||||
import de.twomartens.templateservice.interceptors.TraceIdInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractHealthCheck implements HealthIndicator {
|
||||
|
||||
private static final String DEFAULT_HOST = "localhost";
|
||||
private static final String HEALTH_KEY_ENDPOINT = "endpoint";
|
||||
|
||||
private final String endpoint;
|
||||
|
||||
private final TraceIdInterceptor traceIdInterceptor;
|
||||
|
||||
private final RequestTypeInterceptor requestTypeInterceptor;
|
||||
|
||||
private final int port;
|
||||
|
||||
public AbstractHealthCheck(int port,
|
||||
TraceIdInterceptor traceIdInterceptor,
|
||||
RequestTypeInterceptor requestTypeInterceptor) {
|
||||
this.traceIdInterceptor = traceIdInterceptor;
|
||||
this.requestTypeInterceptor = requestTypeInterceptor;
|
||||
this.port = port;
|
||||
this.endpoint = mkEndpoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
requestTypeInterceptor.markAsHealthCheck();
|
||||
traceIdInterceptor.createNewTraceId();
|
||||
Health result;
|
||||
try {
|
||||
if (isEndpointAvailable()) {
|
||||
result = Health.up().withDetail(HEALTH_KEY_ENDPOINT, getEndpoint()).build();
|
||||
} else {
|
||||
result = Health.down().withDetail(HEALTH_KEY_ENDPOINT, getEndpoint()).build();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result = Health.down().withDetail(HEALTH_KEY_ENDPOINT, getEndpoint()).withException(e).build();
|
||||
}
|
||||
log.info("health check invoked for '{}' with status '{}'", getMethodName(), result.getStatus());
|
||||
return result;
|
||||
}
|
||||
|
||||
String getHost() {
|
||||
return DEFAULT_HOST;
|
||||
}
|
||||
|
||||
int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
String getEndpoint() {
|
||||
return this.endpoint;
|
||||
}
|
||||
|
||||
abstract boolean isEndpointAvailable();
|
||||
|
||||
abstract String getMethodName();
|
||||
|
||||
abstract String mkEndpoint();
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package de.twomartens.templateservice.actuator;
|
||||
|
||||
import de.twomartens.templateservice.entity.Greeting;
|
||||
import de.twomartens.templateservice.interceptors.RequestTypeInterceptor;
|
||||
import de.twomartens.templateservice.interceptors.TraceIdInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A Healtcheck which checks if the rest services are working.
|
||||
*
|
||||
* If you have a complex service behind grpc, you should think about an easy greeting or echo service, which only tests
|
||||
* the network/service stack and not the full application.
|
||||
*
|
||||
* The healthcheck will be called by kubernetes to check if the container/pod shuold be in loadbalancing. It is possible
|
||||
* to have as much healthchecks as you loke.
|
||||
*
|
||||
* There should be a healtcheck which is ok not before all data is loaded.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RestHealthCheck extends AbstractHealthCheck implements HealthIndicator {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public RestHealthCheck(ServerProperties serverProperties,
|
||||
TraceIdInterceptor traceIdInterceptor,
|
||||
RequestTypeInterceptor requestTypeInterceptor,
|
||||
RestTemplate restTemplate) {
|
||||
super(Optional.ofNullable(serverProperties.getPort()).orElse(8080),
|
||||
traceIdInterceptor, requestTypeInterceptor);
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isEndpointAvailable() {
|
||||
try {
|
||||
ResponseEntity<Greeting> result = restTemplate.getForEntity(getEndpoint(), Greeting.class);
|
||||
Greeting body = result.getBody();
|
||||
if (body == null) {
|
||||
return false;
|
||||
}
|
||||
return !body.getMessage().isEmpty();
|
||||
} catch (RestClientException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String getMethodName() {
|
||||
return "infodb-rest";
|
||||
}
|
||||
|
||||
String mkEndpoint() {
|
||||
return String.format("http://%s:%d/greeting?name=RestHealthCheck", getHost(), getPort());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package de.twomartens.templateservice.configs;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "de.twomartens.templateservice")
|
||||
public class TemplateServiceProperties {
|
||||
|
||||
private final Template template = new Template();
|
||||
|
||||
@Data
|
||||
public static class Template {
|
||||
|
||||
private String greeting;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package de.twomartens.templateservice.configs;
|
||||
|
||||
import de.twomartens.templateservice.interceptors.RequestTypeInterceptor;
|
||||
import de.twomartens.templateservice.interceptors.TraceIdInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@EnableWebMvc
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new TraceIdInterceptor());
|
||||
registry.addInterceptor(new RequestTypeInterceptor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
List<ClientHttpRequestInterceptor> interceptors
|
||||
= restTemplate.getInterceptors();
|
||||
if (CollectionUtils.isEmpty(interceptors)) {
|
||||
interceptors = new ArrayList<>();
|
||||
}
|
||||
interceptors.add(new TraceIdInterceptor());
|
||||
interceptors.add(new RequestTypeInterceptor());
|
||||
restTemplate.setInterceptors(interceptors);
|
||||
return restTemplate;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package de.twomartens.templateservice.control;
|
||||
|
||||
import de.twomartens.templateservice.entity.Greeting;
|
||||
import de.twomartens.templateservice.service.GreetingService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class GreetingController {
|
||||
|
||||
private final GreetingService service;
|
||||
|
||||
GreetingController(GreetingService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@GetMapping("/greeting")
|
||||
@ResponseBody
|
||||
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
|
||||
return Greeting.builder().message(service.createGreeting(name)).build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package de.twomartens.templateservice.entity;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
||||
public class Greeting {
|
||||
|
||||
@NonNull
|
||||
private final String message;
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package de.twomartens.templateservice.interceptors;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* marks requests of certain types like health check or integration test for better logging
|
||||
*/
|
||||
@Component
|
||||
@NoArgsConstructor
|
||||
public class RequestTypeInterceptor extends HandlerInterceptorAdapter
|
||||
implements ClientHttpRequestInterceptor {
|
||||
|
||||
private static final String LOGGER_ID = "REQTYPE";
|
||||
private static final String HEADER_FIELD_ID = "x-type";
|
||||
|
||||
public void markAsHealthCheck() {
|
||||
MDC.put(LOGGER_ID, "HEALTH_CHECK");
|
||||
}
|
||||
|
||||
public void markAsIntegrationTest() {
|
||||
MDC.put(LOGGER_ID, "INTEGRATION_TEST");
|
||||
}
|
||||
|
||||
// web client interceptor
|
||||
@Override
|
||||
public ClientHttpResponse intercept(
|
||||
HttpRequest request,
|
||||
byte[] body,
|
||||
ClientHttpRequestExecution execution) throws IOException {
|
||||
request.getHeaders().add(HEADER_FIELD_ID, MDC.get(LOGGER_ID));
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
|
||||
// web server interceptor
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
MDC.put(LOGGER_ID, request.getHeader(HEADER_FIELD_ID));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
package de.twomartens.templateservice.interceptors;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
@NoArgsConstructor
|
||||
public class TraceIdInterceptor extends HandlerInterceptorAdapter
|
||||
implements ClientHttpRequestInterceptor {
|
||||
|
||||
private static final String LOGGER_TRACE_ID = "TraceID";
|
||||
private static final String HEADER_FIELD_TRACE_ID = "X-TraceId";
|
||||
|
||||
/**
|
||||
* Gets the TraceId from the MDC or create a new one and put it to the MDC
|
||||
*/
|
||||
public String getTraceId() {
|
||||
String traceId = MDC.get(LOGGER_TRACE_ID);
|
||||
if (traceId == null || traceId.trim().isEmpty()) {
|
||||
traceId = UUID.randomUUID().toString();
|
||||
MDC.put(LOGGER_TRACE_ID, traceId);
|
||||
}
|
||||
return traceId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A failsafe method to set a TraceId to the MDC.
|
||||
*
|
||||
* @param traceId should be a traceId, if it is null or empty a new one will be created.
|
||||
* @return the traceId param, or a new one, if it was empty
|
||||
*/
|
||||
public String setOrCreateTraceId(String traceId) {
|
||||
if (traceId == null || traceId.trim().isEmpty()) {
|
||||
traceId = UUID.randomUUID().toString();
|
||||
}
|
||||
MDC.put(LOGGER_TRACE_ID, traceId);
|
||||
return traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the traceId from the MDC.
|
||||
*/
|
||||
public void resetTraceId() {
|
||||
MDC.remove(LOGGER_TRACE_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new traceId for this thread.
|
||||
*
|
||||
* An old one will be overwritten!
|
||||
*/
|
||||
public void createNewTraceId() {
|
||||
resetTraceId();
|
||||
getTraceId();
|
||||
}
|
||||
|
||||
// client interceptor web
|
||||
@Override
|
||||
public ClientHttpResponse intercept(
|
||||
HttpRequest request,
|
||||
byte[] body,
|
||||
ClientHttpRequestExecution execution) throws IOException {
|
||||
request.getHeaders().add(HEADER_FIELD_TRACE_ID, getTraceId());
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
|
||||
// server interceptor web
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
String traceId = request.getHeader(HEADER_FIELD_TRACE_ID);
|
||||
setOrCreateTraceId(traceId);
|
||||
response.addHeader(HEADER_FIELD_TRACE_ID, getTraceId());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package de.twomartens.templateservice.service;
|
||||
|
||||
import de.twomartens.templateservice.configs.TemplateServiceProperties;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class GreetingService {
|
||||
|
||||
private final MeterRegistry meterRegistry;
|
||||
private final TemplateServiceProperties properties;
|
||||
private final Counter counter;
|
||||
|
||||
public GreetingService(MeterRegistry meterRegistry, TemplateServiceProperties properties) {
|
||||
this.meterRegistry = meterRegistry;
|
||||
this.properties = properties;
|
||||
counter = meterRegistry.counter("infodb.callCounter");
|
||||
}
|
||||
|
||||
public String createGreeting(String name) {
|
||||
log.info("Create greeting for '{}'", name);
|
||||
counter.increment();
|
||||
meterRegistry.gauge("infodb.nameLength", name.length());
|
||||
String greeting = properties.getTemplate().getGreeting();
|
||||
return String.format(greeting, name);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user