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