You will learn how to define generic controllers using Java 8 functional interfaces. Sample code on GitHub .
This is the class that links your application together. When you open this class you should immediately understand how everything works:
public class EchoApplication { private static final Logger LOG = Logger.getLogger(EchoApplication.class); // Declare dependencies public static EchoService echoService; static { echoService = new EchoService(); } public static void main(String[] args) { port(4567); start(); } public static void start() { JsonUtils.registerModules(); LOG.info("Initializing routes"); establishRoutes(); } private static void establishRoutes() { path("/api", () -> path("/v1", () -> { get(Path.ECHO, EchoController.echo); post(Path.PONG, EchoController.pong); }) ); } }
Static dependencies are not just what you are used to seeing in Java
, but static may be better than embedded dependencies when it comes to controllers in web applications. Moreover, in our microservice, which performs only one function, there will be few services. Of the advantages, it should be noted that the abandonment of DI accelerates the launch of the application, and you can write unit tests for one or two services without DI.
In the Path.java
class Path.java
I hold the entry points to the REST API in the form of constants. In this application, there are only two request handlers that are placed on one EchoController
controller:
public class EchoController { public static Route echo = (req, res) -> ((QueryParamsHandlerFunc) echoQuery -> echoService .echo(echoQuery.value("echo")) .map(Answer::ok) .orElse(Answer.error(HTTP_BAD_REQUEST, ErrorMessages.UNABLE_TO_ECHO + req.body())) ).handleRequest(req, res); public static Route pong = (req, res) -> ((ModelHandlerFunc<Ping>) pongRequest -> echoService .pong(pongRequest) .map(Answer::ok) .orElse(Answer.error(HTTP_BAD_REQUEST, ErrorMessages.UNABLE_TO_PONG + req.body())) ).handleRequest(req, res, Ping.class); }
The first handler is a lambda function, the body of which is the QueryParamsHandlerFunc
functional interface. The input parameter of this interface is the GET request data - a dictionary with a part of the requests from the link. (For example, /echo?echo=message
). In the body of this interface, we call our service handler. Those. the service receives the already assembled object and does not depend on the controller in any way, which facilitates testing. The service returns an Optional
, which maps to the Answer
class, which creates the answer. In the event of an error, a Answer
class is returned with an error code and an error message. This interface has the QueryParamsHandlerFunc::handleRequest
, to which the request and the response are sent from the Func::handlerRequest(req, res)
handler controller.
The second handler returns a result that forms approximately the same interface as above, only the class that inherits the Payload
interface is specified as the template parameter. The processing of this request is no different from that described above. The only difference is that the ModelHandlerFunc::handleRequest
this interface receives the class of our Payload
as a parameter ModelHandlerFunc<Ping>
.
This is a functional interface that inherits the basic interface, into which actions common to the GET request handler are made. This interface defines a default method, QueryParamsHandlerFunc :: handleRequest, which accepts request and response objects as input parameters. Performs some checks, such as headers, by invoking the BaseHandlerFunc :: commonCheck (request) base interface method. Next, it takes the query dictionary from the query (/ echo? Echo = message), sends them to the method defined in the QueryParamsHandlerFunc :: process interface, after processing the request, specifies the response code and serializes this response to Json.
@FunctionalInterface public interface QueryParamsHandlerFunc extends BaseHandlerFunc { default String handleRequest(Request request, Response response) { String check = commonCheck(request); if (StringUtils.isNotBlank(check)) { return check; } QueryParamsMap queryParamsMap = request.queryMap(); Answer processed = process(queryParamsMap); response.status(processed.getCode()); return dataToJson(processed); } Answer process(QueryParamsMap data); }
This interface works the same way as described above, with the only difference that it handles POST requests. The converted class, as well as when processing request parameters, is passed to the ModelHandlerFunc::process
interface method.
@FunctionalInterface public interface ModelHandlerFunc<T extends Payload> extends BaseHandlerFunc { default String handleRequest(Request request, Response response, Class<T> clazz) { String check = commonCheck(request); if (StringUtils.isNotBlank(check)) { return check; } String json = request.body(); T data = jsonToData(json, clazz); Answer processed = process(data); response.status(processed.getCode()); return dataToJson(processed); } Answer process(T data); }
This is an interface that aggregates common methods in itself.
public interface BaseHandlerFunc { default String commonCheck(Request request) { // do your smart check here return null; } }
For serialization in JSON of the Answer class and its load, polymorphism with the @ @JsonTypeInfo
annotation was @JsonTypeInfo
. More on the links.
To test the controller, we will use the Spark Test library. Sample code testing.
public class EchoControllerTest { private static String echoUrl = "/api/v1"; private static Integer randomPort = 1000 + new Random().nextInt(60000); public static class BoardBoxControllerTestSparkApplication implements SparkApplication { @Override public void init() { EchoApplication.start(); } } @ClassRule public static SparkServer<BoardBoxControllerTestSparkApplication> testServer = new SparkServer<>(BoardBoxControllerTestSparkApplication.class, randomPort); @Test public void should_echo() throws HttpClientException { String echoMsg = "echo"; Echo echo = (Echo) get("/echo?echo" + "=" + echoMsg).getBody(); assertEquals(echoMsg, echo.getEcho()); } @Test public void should_pong() throws HttpClientException { Pong pong = (Pong) post("/ping", new Ping("PING")).getBody(); assertEquals("PING PONG", pong.getPong()); } private Answer post(String path, Object payload) throws HttpClientException { PostMethod resp = testServer.post(echoUrl + path, dataToJson(payload), false); HttpResponse execute = testServer.execute(resp); return jsonToData(new String(execute.body()), Answer.class); } private Answer get(String params) throws HttpClientException { GetMethod resp = testServer.get(echoUrl + "/" + params, false); HttpResponse execute = testServer.execute(resp); return jsonToData(new String(execute.body()), Answer.class); } }
Source: https://habr.com/ru/post/352732/
All Articles