Spring Boot Single REST DTO Service Endpoint
https://www.std.ch/getb?&atom
Fri, 29 Mar 2024 00:59:17 +0000
stack.ch
https://stack.ch/
918daec5-ed67-11ee-8c11-005056bb85fb
Simtech AG - Blog - Spring Boot Blogs - Spring Boot Single REST DTO Service Endpoint
https://www.std.ch/getb
918dafc9-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +0000
Single REST DTO Service Endpoint
https://www.std.ch/getb
918db101-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +0000
https://www.std.ch/getb
918db215-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +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.std.ch/getb
918db4f9-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +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.std.ch/getb
918dc86f-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +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.std.ch/getb
918dcf8a-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +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.std.ch/getb
918dd4f3-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +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.std.ch/getb
918ddb87-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +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.std.ch/getb
918de31e-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +0000
Das gesamte Beispiel finden Sie unter dem Link genericdtocontroller.zip.
https://www.std.ch/getb
918de741-ed67-11ee-8c11-005056bb85fb
Fri, 29 Mar 2024 00:59:17 +0000
War dieser Blog für Sie wertvoll. Wir danken für jede Anregung und Feedback
-
Über uns
https://www.std.ch/about
Fri, 29 Mar 2024 00:59:17 +0000
918de9ae-ed67-11ee-8c11-005056bb85fb
-
Aktuell
https://www.std.ch/
Fri, 29 Mar 2024 00:59:17 +0000
918dea66-ed67-11ee-8c11-005056bb85fb
-
AGB
https://www.std.ch/agb
Fri, 29 Mar 2024 00:59:17 +0000
918deb0f-ed67-11ee-8c11-005056bb85fb
-
Bildungswege
https://www.std.ch/bildungswege
Fri, 29 Mar 2024 00:59:17 +0000
918debc3-ed67-11ee-8c11-005056bb85fb
-
Blog
https://www.std.ch/blog
Fri, 29 Mar 2024 00:59:17 +0000
918dec6c-ed67-11ee-8c11-005056bb85fb
-
Rufen Sie mich an
https://www.std.ch/callus
Fri, 29 Mar 2024 00:59:17 +0000
918ded0e-ed67-11ee-8c11-005056bb85fb
-
Charts
https://www.std.ch/charts
Fri, 29 Mar 2024 00:59:17 +0000
918deda6-ed67-11ee-8c11-005056bb85fb
-
Consulting
https://www.std.ch/consulting
Fri, 29 Mar 2024 00:59:17 +0000
918dee5f-ed67-11ee-8c11-005056bb85fb
-
Kontakt
https://www.std.ch/contact
Fri, 29 Mar 2024 00:59:17 +0000
918def00-ed67-11ee-8c11-005056bb85fb
-
Ausbildung/Kurse
https://www.std.ch/education
Fri, 29 Mar 2024 00:59:17 +0000
918def9f-ed67-11ee-8c11-005056bb85fb
-
Software Engineering
https://www.std.ch/engineering
Fri, 29 Mar 2024 00:59:17 +0000
918df03a-ed67-11ee-8c11-005056bb85fb
-
Freelancer
https://www.std.ch/freelancer
Fri, 29 Mar 2024 00:59:17 +0000
918df0d6-ed67-11ee-8c11-005056bb85fb
-
Impressum
https://www.std.ch/impressum
Fri, 29 Mar 2024 00:59:17 +0000
918df170-ed67-11ee-8c11-005056bb85fb
-
Kursleiter
https://www.std.ch/kursleiter
Fri, 29 Mar 2024 00:59:17 +0000
918df20f-ed67-11ee-8c11-005056bb85fb
-
Netzwerk
https://www.std.ch/network
Fri, 29 Mar 2024 00:59:17 +0000
918df2aa-ed67-11ee-8c11-005056bb85fb
-
Referenzen
https://www.std.ch/references
Fri, 29 Mar 2024 00:59:17 +0000
918df4e4-ed67-11ee-8c11-005056bb85fb
-
Sitemap
https://www.std.ch/sitemap
Fri, 29 Mar 2024 00:59:17 +0000
918df5b7-ed67-11ee-8c11-005056bb85fb
-
Tools
https://www.std.ch/tools
Fri, 29 Mar 2024 00:59:17 +0000
918df65a-ed67-11ee-8c11-005056bb85fb
-
Vision
https://www.std.ch/vision
Fri, 29 Mar 2024 00:59:17 +0000
918df6f5-ed67-11ee-8c11-005056bb85fb