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.
Update [2018-05-05@22:11]: Added the section Error handling
on server-side error handling mechanisms.
“…
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 … “ (seeBare-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 andweb container
support in aMicroservices
andIoT
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 doRESTful
services. I don’t want to buyclasspath
scanning orbytecode
introspection only for doingREST
. I want to doBare-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 kinda programming where you decide on how your application is constructed, where you control thelifecycle
and where you create a damn simplelistener
for processing a resource’sREST
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)
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 ofHeavy-Weight frameworks
because our services’ complexity is continuously shrinking? Just to mention theMicroservices
hype and tinyIoT
devices being everywhere. Therefore I implemented aBare-Metal
API
for doing client sideREST
requests and for implementing tinyRESTful
servers.
Quick start archetype
Use the refcodes-rest-ext-archetype
to create a bare metal REST
driven Java
application within just one source code file:
Please adjust
my.corp
with your actual Group-ID andmyapp
with your actual Artifact-ID!
mvn archetype:generate \
-DarchetypeGroupId=org.refcodes \
-DarchetypeArtifactId=refcodes-rest-ext-archetype \
-DarchetypeVersion=2.1.0 \
-DgroupId=my.corp \
-DartifactId=myapp \
-Dversion=1.0-SNAPSHOT
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>2.1.0</version>
</dependency>
...
</dependencies>
For tweaking your configuration such as binding to SLF4J
, see the refcodes-rest
blog post.
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 HttpRestServerImpl();
// 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 HttpRestServerImpl().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 newHTTP-Response
with the accordingHTTP-Status-Code
and the error description fields.HttpExceptionHandling.UPDATE
: Keep the so far processedHTTP-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 processedHTTP-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 theHTTP-Response
yourself.HttpExceptionHandling.KEEP
: Do not modify the so far processedHTTP-Response
except update the actualHTTP-Status-Code
. Useful when you manually prepared theHTTP-Response
yourself.HttpExceptionHandling.EMPTY
: Create a newHTTP-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 HttpRestServerImpl().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 or DELETE) - 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 HttpRestClientImpl();
// 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
: YourRestCaller
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
RestCaller
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
RestCaller
- A HTTP-Response is produces by the server and passed back to the
HttpRestClient
- The
HttpRestClient
invokes thelambda
of theRestCaller
with the response - The
lambda
of theRestCaller
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 code
at bitbucket.org/metacodez/refcodes-rest
.