Find out about an alternative approach on how you can implement a serverless, tiny and slim RESTful service or a tiny and slim REST client with a little help from Java’s lambda expressions. Moreover, we skip using Heavy-Weight frameworks doing magic in the background. Instead we will follow the classic Bare-Metal approach where you stay in full control of your application’s lifecycle.
“… Bare-Metal programming is a term for programming that operates without various layers of abstraction or, as some experts describe it, without an operating system supporting it … “ (see Bare-Metal programming).
(in this context I use the term Bare-Metal for programming that operates without various layers of abstraction regarding the Java ecosystem)
A while ago I was thinking on what became of good old imperative object orientated Java programming:
There came generics
, keeping your code comprehensible and more reliable, which is a good thing. There came lambda
expressions and with it more functional programming
, still keeping your code comprehensible, still being a good thing. Meanwhile there came the Heavy-Weight frameworks
doing all kinds of magic in the background: Scanning your classpath
, introspecting your bytecode
, making decisions not easily traceable by you, the programmer, taking over your application’s lifecycle, placing you, the programmer, under disability.
Do we really need full blown lifecycle management and web container support in a Microservices and IoT world?
Motivation
What I actually want to do when programming is to keep control:
I want to instantiate some class with which I can do REST requests. I want to instantiate some class with which I can do RESTful services. I don’t want to buy
classpath
scanning orbytecode
introspection only for doing REST. I want to do Bare-Metal programming instead of programming in asteel-boned corset
of someapplication server
orHeavy-Weight framework
…
… and the refcodes-rest
artifact is just one piece in the puzzle of doing so. For example open up as many HTTP/HTTPS ports for as many different lambdas
in a single serverless
RESTful service as you wish.
(you may skip the below *Bare-Metal section in case you want to dive directly into the refcodes-rest
programming)*
Bare-Metal
In comparison to the Heavy-Weight frameworks, Bare-Metal (and therewith the refcodes-rest
artifact) follows an opposite approach: Give the programmer back the control.
Bare-Metal programming is the kind of programming where you decide on how your application is constructed, where you control the lifecycle and where you create a damn simple
listener
for processing a resource’s REST request instead of spreading tons of framework specificannotations
over your code,interpreted
magically by a framework atruntime
.
Just to get an impression on the overhead your applications are carrying around: Using Spring Boot
, just to get a RESTful service up-and-running, your archive including all required libraries gets about 13 MB of size. Moreover, Spring Boot
takes over full control of your application’s lifecycle. No Bare-Metal anymore.
In comparison, the Bare-Metal approach using the refcodes-rest
artifact would fit on three 3.5 inch floppy disks and still keep you in full control of your application’s lifecycle (even including a full blown Bare-Metal refcodes-cli
command line args parser).
(a +
represents 1 MB
in the table below)
Tech Stack | Size (MB) | Bare-Metal |
---|---|---|
Spring Boot |
+++++++++++++ | No, placing the programmer under disability |
refcodes-rest |
+++ | Yes, programmer keeps full control |
I admit, the above is a very one-sided, selective and exaggerated comparison of two very different things. The point I am investigating here is the question whether it would be more sufficient to use Bare-Metal straight forward imperative programming paradigms instead of
Heavy-Weight frameworks
because our services’ complexity is continuously shrinking? Just to mention the Microservices hype and tiny IoT devices being everywhere. Therefore I implemented a Bare-MetalAPI
for doing client side REST requests and for implementing tiny RESTful servers.
Quick start archetype
Use the refcodes-archetype-alt-rest
to create a bare metal REST driven Java application within just one source code file:
Please adjust
my.corp
with your actualGroup-ID
andmyapp
with your actualArtifact-ID
:
mvn archetype:generate \
-DarchetypeGroupId=org.refcodes \
-DarchetypeArtifactId=refcodes-archetype-alt-rest \
-DarchetypeVersion=3.3.9 \
-DgroupId=my.corp \
-DartifactId=myapp \
-Dversion=0.0.1
Using the defaults, this will generate a RESTful server and client application by harnessing the refcodes-rest
library. See also Bare-Metal REST with just a few lines of codes.
Getting started
To get up and running, include the following dependency (without the three dots “…”) in your pom.xml
:
1
2
3
4
5
6
7
8
9
<dependencies>
...
<dependency>
<artifactId>refcodes-rest</artifactId>
<groupId>org.refcodes</groupId>
<version>3.3.9</version>
</dependency>
...
</dependencies>
The RESTful server
The TinyRestfulServer
demo application uses syntactic sugar
for setting up a RESTful server including command line arguments parsing. Basically it requires these three steps to get your RESTful server up and running:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
public static void main( String[] args ) {
// STEP 1: We instantiate our HttpRestServer:
HttpRestServer theRestServer = new HttpRestServer();
// STEP 2: We register our lambda expression:
theRestServer.onGet( "/say/${name}=*", ( aRequest, aResponse ) -> {
String name = aRequest.getWildcardReplacement( "name" );
aResponse.getHeaderFields().withContentType( MediaType.APPLICATION_JSON ).withAddCookie( "greeting", "Hello " + name + "!" );
return "Hello " + name + "!" ;
} ).open();
// STEP 3: We open the HttpRestServer instance on port 8080:
theRestServer.open( 8080 );
}
...
The above example registers to the the imaginary resource “/say
” and provides you with the addressed element (“name
”). You may produce a response including cookies and required Header-Fields via the provided lambda
expression.
Error handling
In case an Exception
gets thrown while your lambda
is processing an HTTP-Response due to an incoming HTTP-Request, you may either handle it manually in your lambda
expression or you may use a predefined behavior or you may even register a custom global Exception
handler. The predefined behavior is configured as follows:
1
2
3
...
HttpRestServer theRestServer = new HttpRestServer().withHttpExceptionHandling( HttpExceptionHandling.REPLACE );
...
The behavior is specified by the enumeration HttpExceptionHandling
. You currently have the following options on how an exceptional situation is handled:
HttpExceptionHandling.REPLACE
: Create a new HTTP-Response with the accordingHTTP-Status-Code
and the error description fields.HttpExceptionHandling.UPDATE
: Keep the so far processed HTTP-Response as is except theHTTP-Status-Code
and also update all error description fields. Useful when you want to preserve already set fields.HttpExceptionHandling.MERGE
: Keep the so far processed HTTP-Response as is except update the actualHTTP-Status-Code
, add only non-existing fields if possible with the error description fields. Useful when you manually prepared the HTTP-Response yourself.HttpExceptionHandling.KEEP
: Do not modify the so far processed HTTP-Response except update the actualHTTP-Status-Code
. Useful when you manually prepared the HTTP-Response yourself.HttpExceptionHandling.EMPTY
: Create a new HTTP-Response with the accordingHTTP-Status-Code
and and an empty body.
In case the Media-Type
used is Application/JSON
, then the error description fields generated by the predefined exception handler looks as follows:
1
2
3
4
5
6
7
8
9
{
"status": {
"exception": "org.refcodes.net.NotFoundException",
"code": "404",
"alias": "NOT FOUND",
"message": "There is none endpoint for handling resource locator </repl/sessionsx> with HTTP-Method <GET>.",
"timestamp": "1525436619219"
}
}
This custom global exception handler is represented by an instance of the functional interface HttpExceptionHandler
being registered to your HttpRestServer
. You may register it using the lambda
notation:
1
2
3
4
5
6
7
...
HttpRestServer theRestServer = new HttpRestServer().withOnHttpException( ( request, response, exception, statusCode ) -> {
// ...
response.setResponse( new HttpBodyMapImpl().withPut( "message", exception.getMessage() ) );
// ...
} );
...
Under the hood
The diagram illustrates the interaction between a client A
, the server B
and the business part C
:
A
: The client such as a browser or theHttpRestClient
issuing a HTTP-RequestB
: TheHttpRestServer
receiving the HTTP-RequestC
: YourRestEndpoint
processing the HTTP-Request and producing a response
- The client issues a HTTP-Request with a given
HttpMethod
(GET
,POST
,PUT
orDELETE
) - The
HttpRestServer
analyzes the request and dispatches it to the accordingRestEndpoint
- Its
HttpMethod
must match and theLocator-Pattern
must match theLocator
- The
lambda
of the matchingRestEndpoint
is invoked and produces a response - The
HttpRestServer
marshals the response and sends it back to the client - The client receives the HTTP-Response as produced by your
lambda
The REST client
Basically it requires these three steps for firing a REST request from within your client:
- Instantiate the REST client
- Register your
lambda
expression for the response - Fire the client’s REST request
1
2
3
4
5
6
7
8
9
10
11
...
public static void main( String[] args ) {
// STEP 1: We instantiate our HttpRestClient:
HttpRestClient theRestClient = new HttpRestClient();
// STEP 2: We register our lambda expression:
theRestClient.doRequest( HttpMethod.POST, "http://mydomain:8080/say/nolan", ( aResponse ) -> {
String theResponse = aResponse.getResponse( String.class );
} ).withRequest( ... ).open();
// STEP 3: We opened the caller so it fires the request to port 8080 of domain "mydomain"
}
...
The above example issues a POST
request to some imaginary “/say
” resource, addressing the element “nolan
” and with some request body (any Object
which can be marshaled with the according Content-Type
) and processes the response asynchronously via the provided lambda
expression.
Under the hood
The diagram illustrates the interaction between the business part A
, the client B
and a server C
:
A
: YourRestResponseHandler
producing the HTTP-Request and processing the responseB
: TheHttpRestClient
sending the HTTP-RequestC
: The server such as a theHttpRestServer
receiving a HTTP-Request
- The HTTP-Request of your
RestResponseHandler
is processed by theHttpRestClient
- The
HttpRestClient
marshals the request and sends it to the server - The server receives the HTTP-Request as produced by your
RestResponseHandler
- A HTTP-Response is produces by the server and passed back to the
HttpRestClient
- The
HttpRestClient
invokes thelambda
of theRestResponseHandler
with the response - The
lambda
of theRestResponseHandler
is invoked and processes the response
Further reading
For an in depth description of the refcodes-rest
artifact on using syntactic sugar
or the syntax of the locator patterns ( e.g. "/say/${name}=*"
from the above example on RESTful services) please refer to the refcodes-rest
blog post. See also the example source code at bitbucket.org/refcodes/refcodes-rest
.