Das Ziel dieses Projekts ist es anhand einer simplen Essens-Rezept Applikation zu zeigen, wie man die Open-Tracing Software Jaeger in eine verteilte Spring-Service Architektur integriert.
Die beiden Services wurden mit Spring+Java realisiert und für die Datenbank wurde MySQL gewählt. Die restful Requests, sowie die Datenbankzugriffe, des gesammten Systems wird mit Opentracing und Jaeger gelogged. Das Deployment aller Komponenten des Systems wird über Docker gehandhabt.
Jaeger Web UI: http://localhost:16686
MySQL: jdbc:mysql://localhost:3306
MySQL Credentials sind im docker-compose File zu finden.
Das Interaction service ist für die Kommunikation mit den Clients(Benutzer) zuständig und liefert, unter Verwendung von Thymeleaf, ein HTML an diesen. Das Service bzw. die HTML Representation der Rezepte-Übersichts Seite und aller andere Funktionen kann über http://localhost:8080/api/recipe/html erreicht werden.
Das Persistence Service ist für die Verwaltung der Daten in der MySQL Datenbank zuständig und wird vom Interaction Service mittels restful Requests genutzt. Das Service kann unter http://localhost:8888/api/... erreicht werden.
Persistence Service Requests:
GET Recipes: http://persistence-service:8888/api/recipe?type=ALL
Delete Recipe: http://persistence-service:8888/api/recipe/{id}
POST Recipe: http://persistence-service:8888/api/recipe/
Form-Data: title={Title}&description={Description}&imageUrl={example.com}
Client greift über einen GET-Request auf den interaction-service zu.
@GetMapping("/html/{type}")
public String getRecipesHTML(@PathVariable(value="type", required = false) String type, Model model){
RecipeType recipeType;
try {
recipeType = RecipeType.valueOf(type);
}catch (IllegalArgumentException e){
recipeType = RecipeType.ALL;
}
model.addAttribute("recipeForm", new RecipeCreationDto());
model.addAttribute("recipes",restTemplate.getRecipes(recipeType));
return HtmlTemplates.INDEX;
}
Es wird die Template-Engine Thymleaf verwendet um HTML für den User zu generieren. Hier ein Auszug aus der Index.html zu Generierung der einzelnen Rezept-Karten:
<div class="card-body">
<h4 class="card-title" th:text="${recipe.title}"></h4>
<div th:switch="${recipe.type}">
<span th:case="'VEGETARIAN'" class="badge badge-success" th:text="${recipe.type}"></span>
<span th:case="'MEAT'" class="badge badge-danger" th:text="${recipe.type}"></span>
</div>
<a th:href="@{~/api/recipe/html/details/{id}(id=${recipe.id})}"> Information</a>
<p class="card-text"><small class="text-muted" th:text="${recipe.date}"></small></p>
</div>
Der interaction-service greift über ein RestTemplate auf den persistence-service zu.
public RecipeDto getRecipe(int id){
String urlTemplate = UriComponentsBuilder.fromHttpUrl(baseUrl+"/api/recipe/"+id)
.encode()
.toUriString();
try {
return rest.getForObject(urlTemplate,RecipeDto.class);
}catch (RestClientException ex){
logger.error("getRecipes()",ex);
return null;
}
}
Der persistence-service ist über einen REST-Controller erreichbar.
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE, path = "/{id}")
public ResponseEntity<RecipeEntity> getRecipe(@PathVariable(value="id") int id){
if(recipeService.getRecipe(id).isPresent()){
return new ResponseEntity<>(recipeService.getRecipe(id).get(), HttpStatus.OK);
}else{
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
Über Spring-Data greift der persistence-service auf die von Hibernate gemanagte Datenbank zu.
@Repository
public interface RecipeRepository extends JpaRepository<RecipeEntity,Integer>{
@Query
public List<RecipeEntity> findAllByType(String type);
}
Jaeger Service im docker-compose
jaeger:
image: jaegertracing/all-in-one:latest
container_name: jaeger
ports:
- "6831:6831/udp"
- "16686:16686"
- "4317:4317"
networks:
- jaeger_net
Properties für Jaeger Setup im persistence-service
spring.application.name=persistence-service
opentracing.jaeger.udp-sender.host=jaeger
opentracing.jaeger.udp-sender.port=6831
opentracing.jaeger.log-spans=true
Properties für Jaeger Setup im interaction-service
spring.application.name=interaction-service
opentracing.jaeger.udp-sender.host=jaeger
opentracing.jaeger.udp-sender.port=6831
opentracing.jaeger.log-spans=true
Damit die zusammenhängend Spans über REST-Kommunikation richtig bei Jaeger getraced werden, ist es wichtig, das RestTemplate als Bean bekannt zu machen.
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
Auf der Startseite der Beispiel-Applikation findet man eine Übersicht über alle Rezepte. Man kann nach Rezepten filtern, einzelne Rezepte löschen oder über ein Formular neue Rezepte hinzufügen.
Mit einem Klick auf "Information" gelangt man zur Detail-Seite.
Es ist nun möglich die Requests des Users über die verteilten Services hinweg zu tracen. Diese Traces verlaufen vom interaction-service bis zur Datenbank.
Da die Datenbank und das gesamte Entity Management mit Hibernate umgesetzt wurde, ist auch die Datenbank in dem Jaeger-Trace integriert und es ist u.a. möglich, Informationen über die generierten Querys abzufragen.
Wenn im Zuge der Kommunikation der Services bzw. innerhalb eines Services eine Exception geworfen wird, ist diese sofort bereits in der Übersicht erkennbar.
Über den jeweiligen Error, kann man alle Details abfragen. Debugging in verteilten Systemen wird dadurch sehr stark vereinfacht.
Die Integrierung von Jaeger in eine verteilte Spring-Service-Architektur, gestaltete sich problemlos. Es sind dafür nur wenige Konfigurationen und Abhängigkeiten zu setzen. Die besondere Stärke von Jaeger sehen wir in der Verfolgung von Requests in einer verteilten Architektur um eventuelle Bottlenecks zu identifizieren und dadurch Performanceverbesserungen umzusetzen. Außerdem wird wie bereits erwähnt das Debuggen sehr viel einfacher.
Zusätzlich haben wir versucht die Monitoring-Funktionalität in Kombination mit Prometheus zu aktivieren. Diese Funktionalität ist auf der Seite von Jaeger noch im Status "experimental" und durch mangelnde Dokumentation, konnten wir diese nicht lauffähig umsetzen.
Das start.ps1 Skript ausführen, dieses baut die Jars der Web Services und führt anschließend das docker-compose aus.
Jaeger: http://localhost:16686/search
Interaction-Service: http://localhost:8080/api/recipe/html