Spring Boot Single REST DTO Service Endpoint
https://www.golabs.ch/requestdto?&atom
Thu, 28 Mar 2024 16:26:13 +0000
stack.ch
https://stack.ch/
e4bcc007-ed1f-11ee-8c11-005056bb85fb
Simtech AG - Blog - Spring Boot Blogs - Spring Boot Single REST DTO Service Endpoint
https://www.golabs.ch/requestdto
e4bcc11a-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +0000
Single REST DTO Service Endpoint
https://www.golabs.ch/requestdto
e4bcc23d-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +0000
https://www.golabs.ch/requestdto
e4bcc32e-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +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/requestdto
e4bcc59b-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +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/requestdto
e4bcd4b7-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +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/requestdto
e4bcdb87-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +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/requestdto
e4bce0fd-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +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/requestdto
e4bce6ea-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +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/requestdto
e4bceecc-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +0000
Das gesamte Beispiel finden Sie unter dem Link genericdtocontroller.zip.
https://www.golabs.ch/requestdto
e4bcf3b7-ed1f-11ee-8c11-005056bb85fb
Thu, 28 Mar 2024 16:26:13 +0000
War dieser Blog für Sie wertvoll. Wir danken für jede Anregung und Feedback
-
Über uns
https://www.golabs.ch/about
Thu, 28 Mar 2024 16:26:13 +0000
e4bcf5ea-ed1f-11ee-8c11-005056bb85fb
-
Aktuell
https://www.golabs.ch/
Thu, 28 Mar 2024 16:26:13 +0000
e4bcf707-ed1f-11ee-8c11-005056bb85fb
-
AGB
https://www.golabs.ch/agb
Thu, 28 Mar 2024 16:26:13 +0000
e4bcf7b9-ed1f-11ee-8c11-005056bb85fb
-
Bildungswege
https://www.golabs.ch/bildungswege
Thu, 28 Mar 2024 16:26:13 +0000
e4bcf85d-ed1f-11ee-8c11-005056bb85fb
-
Blog
https://www.golabs.ch/blog
Thu, 28 Mar 2024 16:26:13 +0000
e4bcf8f9-ed1f-11ee-8c11-005056bb85fb
-
Rufen Sie mich an
https://www.golabs.ch/callus
Thu, 28 Mar 2024 16:26:13 +0000
e4bcf99b-ed1f-11ee-8c11-005056bb85fb
-
Charts
https://www.golabs.ch/charts
Thu, 28 Mar 2024 16:26:13 +0000
e4bcfa49-ed1f-11ee-8c11-005056bb85fb
-
Consulting
https://www.golabs.ch/consulting
Thu, 28 Mar 2024 16:26:13 +0000
e4bcfae5-ed1f-11ee-8c11-005056bb85fb
-
Kontakt
https://www.golabs.ch/contact
Thu, 28 Mar 2024 16:26:13 +0000
e4bcfb7a-ed1f-11ee-8c11-005056bb85fb
-
Ausbildung/Kurse
https://www.golabs.ch/education
Thu, 28 Mar 2024 16:26:13 +0000
e4bcfc18-ed1f-11ee-8c11-005056bb85fb
-
Software Engineering
https://www.golabs.ch/engineering
Thu, 28 Mar 2024 16:26:13 +0000
e4bcfcc7-ed1f-11ee-8c11-005056bb85fb
-
Freelancer
https://www.golabs.ch/freelancer
Thu, 28 Mar 2024 16:26:13 +0000
e4bcfd60-ed1f-11ee-8c11-005056bb85fb
-
Impressum
https://www.golabs.ch/impressum
Thu, 28 Mar 2024 16:26:13 +0000
e4bcfdfb-ed1f-11ee-8c11-005056bb85fb
-
Kursleiter
https://www.golabs.ch/kursleiter
Thu, 28 Mar 2024 16:26:13 +0000
e4bcfe96-ed1f-11ee-8c11-005056bb85fb
-
Netzwerk
https://www.golabs.ch/network
Thu, 28 Mar 2024 16:26:13 +0000
e4bcff2f-ed1f-11ee-8c11-005056bb85fb
-
Referenzen
https://www.golabs.ch/references
Thu, 28 Mar 2024 16:26:13 +0000
e4bcffdb-ed1f-11ee-8c11-005056bb85fb
-
Sitemap
https://www.golabs.ch/sitemap
Thu, 28 Mar 2024 16:26:13 +0000
e4bd0089-ed1f-11ee-8c11-005056bb85fb
-
Tools
https://www.golabs.ch/tools
Thu, 28 Mar 2024 16:26:13 +0000
e4bd012e-ed1f-11ee-8c11-005056bb85fb
-
Vision
https://www.golabs.ch/vision
Thu, 28 Mar 2024 16:26:13 +0000
e4bd01cb-ed1f-11ee-8c11-005056bb85fb