Spring Boot Single REST DTO Service Endpoint
https://www.golabs.ch/setA?&atom
Fri, 29 Mar 2024 05:30:01 +0000
stack.ch
https://stack.ch/
63a2f16d-ed8d-11ee-8c11-005056bb85fb
Simtech AG - Blog - Spring Boot Blogs - Spring Boot Single REST DTO Service Endpoint
https://www.golabs.ch/setA
63a2f3b3-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
Single REST DTO Service Endpoint
https://www.golabs.ch/setA
63a2f6e0-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
https://www.golabs.ch/setA
63a2f815-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
Die Programmierung von REST Services mit Spring Boot ist gemäss Lehrbuch eine relativ einfache Sache.Jede Klasse kann als Rest Service funktionieren und Daten im Format JSON verarbeiten. In der Regel erfolgt dies über Data Transfer Objekte (DTO's).Mit dem Lauf der Entwicklung nimmt die Anzahl REST Service Endpoints zu und damit auch die Komplexität und Redundanz. Projekte mit über 100 REST Endpoints sind schnell möglich und damit befinden wir uns in einem stetigen Update Prozess, da die REST Endpoints mit der zunehmenden Anzahl vermehrt angepasst werden. Es fehlt ein zentrales Error Handling oder ein Überwachungspunkt (Single REST Endpoint). Jede Anpassung löst sofort an vielen anderen Stellen Korrekturen aus.Ein Single REST Endpoint arbeitet wie ein Portier, alle Zu- und Abgänge werden über einen Punkt abgewickelt. Er zwingt uns ein Protokoll für die Kommunikation zu definieren und damit die REST Endpoints zu standardisieren. Genau hier hilft das Konzept des Single REST DTO Service Endpoints.
https://www.golabs.ch/setA
63a2fc32-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
Zuerst definieren wir das REST Protokoll mit generischen DTO Klassen und setzen auf das HEAD-BODY-Pattern. Das UML Modell:Die generische Klasse Base definiert die HEAD-BODY Struktur:package ch.std.genericdto.dto;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonIgnore;
import ch.std.genericdto.tools.StringUtil;
public class BaseDTO<T> {
private static final Logger logger = LoggerFactory.getLogger(BaseDTO.class);
private Map<String, Object> header;
private T body;
public BaseDTO() {
this.header = new HashMap<String, Object>();
}
public Map<String, Object> getHeader() {
return header;
}
public void setHeader( Map<String, Object> header) {
this.header = header;
}
public T getBody() {
return body;
}
public void setBody(T body) {
this.body = body;
}
@JsonIgnore
public Object getHeadValue(String key) {
return this.header.get(key);
}
@JsonIgnore
public void setHeadValue(String key, Object value) {
this.header.put(key, value);
}
@JsonIgnore
public String getStatus() {
try {
return this.header.get("status").toString();
} catch (Exception e) {
return null;
}
}
@JsonIgnore
public String getStatusMessage() {
try {
return this.header.get("statusMessage").toString();
} catch (Exception e) {
return null;
}
}
@JsonIgnore
public boolean isSuccess() {
return "success".equals(this.getStatus());
}
@JsonIgnore
public void setSuccess(String message) {
try {
this.header.put("status", "success");
this.header.put("statusMessage", message);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@JsonIgnore
public void setStatusSuccessIfNotSet(String message) {
if (this.header.get("status") != null) {
return;
}
this.setSuccess(message);
}
@JsonIgnore
public boolean isFailure() {
return "failure".equals(this.getStatus());
}
@JsonIgnore
public void setFailure(String message) {
try {
this.header.put("status", "failure");
this.header.put("statusMessage", message);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@JsonIgnore
public void setFailure(Throwable t) {
try {
this.header.put("status", "failure");
String message = t.getMessage();
if (StringUtil.isNullOrEmpty(message)) {
message = t.toString();
}
this.header.put("statusMessage", message);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}Die generische Klasse RequestDTO repräsentiert den REST Request:
package ch.std.genericdto.dto;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
public class RequestDTO<T> extends BaseDTO<T> {
private Map<String, Object> parameterMap;
public RequestDTO() {
}
public RequestDTO(HttpServletRequest httpServletRequest) {
this.parameterMap = new HashMap<String, Object>();
httpServletRequest.getParameterMap().forEach((key, value) -> {
if (value.length > 0) {
this.parameterMap.put(key, value[0]);
}
});
}
public T getDTOFromParameterMap(Class<T> c) {
final ObjectMapper mapper = new ObjectMapper(); // jackson's objectmapper
return mapper.convertValue(parameterMap, c);
}
}Die generische Klasse ResponseDTO repräsentiert die REST Response:package ch.std.genericdto.dto;
public class ResponseDTO<T> extends BaseDTO<T> {
}Damit ist das HEAD-BODY Protokoll abgedeckt. Im Header sind beliebige Key/Value Parameter definierbar. Der Body ist frei für die durch den generischen Type definierten DTO Instanzen.
https://www.golabs.ch/setA
63a316b1-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
Der Single Rest Endpoint Controller bildet den zentralen Butler, über den alle REST Calls laufen. Das Beispiel zeigt das zentrale Exception und Status Handling:
package ch.std.genericdto.rest;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ch.std.genericdto.dto.RequestDTO;
import ch.std.genericdto.dto.ResponseDTO;
@RestController // default scope is singleton
@RequestMapping(value = "/rest/generic", produces = MediaType.APPLICATION_JSON_VALUE)
public class GenericDTOController<RES, REQ> {
public GenericDTOController() {
super();
}
private static final Logger logger = LoggerFactory.getLogger(GenericDTOController.class);
@GetMapping
public ResponseDTO<RES> get(HttpServletRequest httpServletRequest) {
logger.info("generic get call");
try {
long start = System.currentTimeMillis();
RequestDTO<REQ> requestDTO = new RequestDTO<REQ>(httpServletRequest);
ResponseDTO<RES> responseDTO = executeGet(requestDTO);
long end = System.currentTimeMillis();
responseDTO.setStatusSuccessIfNotSet("successful");
responseDTO.setHeadValue("durationmillis", (end - start));
return responseDTO;
} catch (Exception e) {
logger.error(e.getMessage(), e);
ResponseDTO<RES> responseDTO = new ResponseDTO<RES>();
responseDTO.setFailure(e);
return responseDTO;
}
}
@PostMapping
public ResponseDTO<RES> post(@RequestBody RequestDTO<REQ> requestDTO, HttpServletRequest httpServletRequest) {
logger.info("generic post call");
try {
long start = System.currentTimeMillis();
ResponseDTO<RES> responseDTO = executePost(requestDTO);
long end = System.currentTimeMillis();
responseDTO.setStatusSuccessIfNotSet("successful");
responseDTO.setHeadValue("durationmillis", (end - start));
return responseDTO;
} catch (Exception e) {
logger.error(e.getMessage(), e);
ResponseDTO<RES> responseDTO = new ResponseDTO<RES>();
responseDTO.setFailure(e);
return responseDTO;
}
}
public ResponseDTO<RES> executeGet(RequestDTO<REQ> requestDTO) throws Exception {
throw new UnsupportedOperationException();
}
public ResponseDTO<RES> executePost(RequestDTO<REQ> requestDTO) throws Exception {
throw new UnsupportedOperationException();
}
}
https://www.golabs.ch/setA
63a32213-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
Das folgende Listing zeigt die Implementation eines Echo Controllers basierend auf dem Single REST Endpoint:package ch.std.genericdto.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ch.std.genericdto.dto.RequestDTO;
import ch.std.genericdto.dto.ResponseDTO;
@RestController
@RequestMapping("/rest/echo")
public class EchoController extends GenericDTOController<EchoController.Echo, EchoController.Echo> {
Logger logger = LoggerFactory.getLogger(EchoController.class);
@PostMapping("/real")
public ResponseDTO<Echo> realPost(@RequestBody RequestDTO<Echo> requestDTO) {
logger.info("real echo call");
ResponseDTO<Echo> responseDTO = new ResponseDTO<Echo>();
responseDTO.setHeader(requestDTO.getHeader());
responseDTO.setBody(new Echo("James"));
return responseDTO;
}
@Override
public ResponseDTO<Echo> executeGet(RequestDTO<Echo> requestDTO) {
logger.info("echo executeGet, requestDTO = ", requestDTO);
ResponseDTO<Echo> responseDTO = new ResponseDTO<Echo>();
responseDTO.setBody((Echo)requestDTO.getBody());
return responseDTO;
}
@Override
public ResponseDTO<Echo> executePost(RequestDTO<Echo> requestDTO) {
logger.info("echo executePost, requestDTO = ", requestDTO);
ResponseDTO<Echo> responseDTO = new ResponseDTO<Echo>();
responseDTO.setHeader(requestDTO.getHeader());
responseDTO.setBody(requestDTO.getBody());
return responseDTO;
}
public static class Echo {
private String name;
public Echo() {
}
public Echo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
https://www.golabs.ch/setA
63a32e39-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
Das folgende Listing zeigt die Implementation eines Calc Controllers basierend auf dem Single REST Endpoint:
package ch.std.genericdto.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ch.std.genericdto.dto.RequestDTO;
import ch.std.genericdto.dto.ResponseDTO;
@RestController
@RequestMapping("/rest/calc")
public class CalcController extends GenericDTOController<Double, CalcController.Calc> {
Logger logger = LoggerFactory.getLogger(CalcController.class);
@Override
public ResponseDTO<Double> executeGet(RequestDTO<Calc> requestDTO) {
logger.info("calc executeGet, requestDTO = ", requestDTO);
Calc calc = requestDTO.getDTOFromParameterMap(Calc.class);
ResponseDTO<Double> responseDTO = new ResponseDTO<Double>();
responseDTO.setBody(calc.calc());
return responseDTO;
}
@Override
public ResponseDTO<Double> executePost(RequestDTO<Calc> requestDTO) {
logger.info("calc executePost, requestDTO = ", requestDTO);
Calc calc = requestDTO.getBody();
ResponseDTO<Double> responseDTO = new ResponseDTO<Double>();
responseDTO.setBody(calc.calc());
return responseDTO;
}
public static class Calc {
protected String action;
protected double a;
protected double b;
public Calc() {
}
public Calc(String action, double a, double b) {
this.action = action;
this.a = a;
this.b = b;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public double getA() {
return a;
}
public void setA(double a) {
this.a = a;
}
public double getB() {
return b;
}
public void setB(double b) {
this.b = b;
}
public Double calc() {
switch (this.action) {
case "add":
return this.a + this.b;
default:
return null;
}
}
}
}
https://www.golabs.ch/setA
63a339f4-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
Das folgende Listing zeigt die Implementation des Calc Unit Integration Tests:package ch.std.genericdto.rest;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import javax.servlet.ServletContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import ch.std.genericdto.dto.RequestDTO;
import ch.std.genericdto.dto.ResponseDTO;
import ch.std.genericdto.rest.CalcController.Calc;
import ch.std.genericdto.rest.EchoController.Echo;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class CalcControllerTests {
Logger logger = LoggerFactory.getLogger(CalcControllerTests.class);
@LocalServerPort
private int port;
@Autowired
private ServletContext servletContext;
@Autowired
private TestRestTemplate restTemplate;
@BeforeEach
public void setup() {
logger.info("CalcControllerTests.setup");
}
@Test
public void testGetEcho() throws Exception {
String url = this.getUrl("/rest/calc?action=add&a=1.0&b=2.0");
ResponseEntity<ResponseDTO<Double>> responseDTO = this.restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<ResponseDTO<Double>>() {});
assertNotNull(responseDTO);
assertNotNull(responseDTO.getBody());
assertEquals(3.0, responseDTO.getBody().getBody());
}
@Test
public void testPostEcho() throws Exception {
String url = this.getUrl("/rest/calc");
RequestDTO<Calc> requestDTO = new RequestDTO<Calc>();
requestDTO.setBody(new Calc("add", 1.0,2.0));
HttpEntity<RequestDTO<Calc>> httpEntityRequest = new HttpEntity<>(requestDTO);
ResponseEntity<ResponseDTO<Double>> responseDTO = this.restTemplate.exchange(url, HttpMethod.POST, httpEntityRequest, new ParameterizedTypeReference<ResponseDTO<Double>>() {});
assertNotNull(responseDTO);
assertNotNull(responseDTO.getBody());
assertEquals(3.0, responseDTO.getBody().getBody());
}
@Test
public void testPostEcho2() throws Exception {
String url = this.getUrl("/rest/echo/real");
RequestDTO<Echo> requestDTO = new RequestDTO<Echo>();
requestDTO.setHeadValue("name", "James");
requestDTO.setBody(new Echo("James"));
HttpEntity<RequestDTO<Echo>> httpEntityRequest = new HttpEntity<>(requestDTO);
ResponseEntity<ResponseDTO<Echo>> responseDTO = this.restTemplate.exchange(url, HttpMethod.POST, httpEntityRequest, new ParameterizedTypeReference<ResponseDTO<Echo>>() {});
assertNotNull(responseDTO);
assertNotNull(responseDTO.getBody());
}
public String getUrl(String path) {
return "http://localhost:" + port + servletContext.getContextPath() + path;
}
}
https://www.golabs.ch/setA
63a347c3-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
Das gesamte Beispiel finden Sie unter dem Link genericdtocontroller.zip.
https://www.golabs.ch/setA
63a34f56-ed8d-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 05:30:01 +0000
War dieser Blog für Sie wertvoll. Wir danken für jede Anregung und Feedback
-
Über uns
https://www.golabs.ch/about
Fri, 29 Mar 2024 05:30:01 +0000
63a35323-ed8d-11ee-8c11-005056bb85fb
-
Aktuell
https://www.golabs.ch/
Fri, 29 Mar 2024 05:30:01 +0000
63a353bf-ed8d-11ee-8c11-005056bb85fb
-
AGB
https://www.golabs.ch/agb
Fri, 29 Mar 2024 05:30:01 +0000
63a355b7-ed8d-11ee-8c11-005056bb85fb
-
Bildungswege
https://www.golabs.ch/bildungswege
Fri, 29 Mar 2024 05:30:01 +0000
63a35673-ed8d-11ee-8c11-005056bb85fb
-
Blog
https://www.golabs.ch/blog
Fri, 29 Mar 2024 05:30:01 +0000
63a35708-ed8d-11ee-8c11-005056bb85fb
-
Rufen Sie mich an
https://www.golabs.ch/callus
Fri, 29 Mar 2024 05:30:01 +0000
63a358d7-ed8d-11ee-8c11-005056bb85fb
-
Charts
https://www.golabs.ch/charts
Fri, 29 Mar 2024 05:30:01 +0000
63a3599b-ed8d-11ee-8c11-005056bb85fb
-
Consulting
https://www.golabs.ch/consulting
Fri, 29 Mar 2024 05:30:01 +0000
63a35a35-ed8d-11ee-8c11-005056bb85fb
-
Kontakt
https://www.golabs.ch/contact
Fri, 29 Mar 2024 05:30:01 +0000
63a35ac6-ed8d-11ee-8c11-005056bb85fb
-
Ausbildung/Kurse
https://www.golabs.ch/education
Fri, 29 Mar 2024 05:30:01 +0000
63a35cb9-ed8d-11ee-8c11-005056bb85fb
-
Software Engineering
https://www.golabs.ch/engineering
Fri, 29 Mar 2024 05:30:01 +0000
63a35d76-ed8d-11ee-8c11-005056bb85fb
-
Freelancer
https://www.golabs.ch/freelancer
Fri, 29 Mar 2024 05:30:01 +0000
63a35e09-ed8d-11ee-8c11-005056bb85fb
-
Impressum
https://www.golabs.ch/impressum
Fri, 29 Mar 2024 05:30:01 +0000
63a35fee-ed8d-11ee-8c11-005056bb85fb
-
Kursleiter
https://www.golabs.ch/kursleiter
Fri, 29 Mar 2024 05:30:01 +0000
63a360a3-ed8d-11ee-8c11-005056bb85fb
-
Netzwerk
https://www.golabs.ch/network
Fri, 29 Mar 2024 05:30:01 +0000
63a3613a-ed8d-11ee-8c11-005056bb85fb
-
Referenzen
https://www.golabs.ch/references
Fri, 29 Mar 2024 05:30:01 +0000
63a361cc-ed8d-11ee-8c11-005056bb85fb
-
Sitemap
https://www.golabs.ch/sitemap
Fri, 29 Mar 2024 05:30:01 +0000
63a363c5-ed8d-11ee-8c11-005056bb85fb
-
Tools
https://www.golabs.ch/tools
Fri, 29 Mar 2024 05:30:01 +0000
63a36497-ed8d-11ee-8c11-005056bb85fb
-
Vision
https://www.golabs.ch/vision
Fri, 29 Mar 2024 05:30:01 +0000
63a366e7-ed8d-11ee-8c11-005056bb85fb