Irked
Controller framework for Vert.x-web with automatic configuration and dynamic discovery.
Install / Use
/learn @GreenfieldTech/IrkedREADME
Irked Vert.x Web Framework 5.x
Irked is a very opinionated framework for configuring Vert.x-web routing and call dispatch.
It allows you to write your REST API code without writing routing boiler plate by leveraging annotations, auto-discovery through reflection and optionally (if you're into that as well) dependency injection.
This version supports Vert.x 5. To use with earlier Vert.x versions, try Irked 4 for Vert.x 4 support, Irked 2 for Vert.x 3.9 support or Irked 1 for earlier versions (Vert.x 3.9 has changed its API in a non-backward compatible way - method return types were changed - which required that 1 + 2 split).
Other than different backward compatibility, Irked versions are essentially the same with bug fixes ported to all releases.
Installation
Irked is available from the Maven Central Repository.
If using Maven, add Irked as a dependency in your pom.xml file:
<dependency>
<groupId>tech.greenfield</groupId>
<artifactId>irked-vertx</artifactId>
<version>5.0.6</version>
</dependency>
For other build tools, see the Maven Central website for the syntax, but it generally
boils down to just using tech.greenfield:irked-vertx:5.0.6 as the dependency string.
Quick Start
You may want to take a look at the example application at src/example/java/tech/greenfield/vertx/irked/example/App.java which shows how to create a new Vert.x Verticle using an Irked Router and a few very simple APIs. Then you may want to read the rest of this document for explanations, rationale and more complex API examples.
To run the example application, after compiling (for example, using mvn compile) run it with your full Vert.x 5.0.6 installation:
vertx run -cp target/classes/ tech.greenfield.vertx.irked.example.App
Or, alternatively, using the Vert.x JAR dependencies in the Irked maven project:
mvn exec:exec -Dexec.executable=java -Dexec.args="-cp %classpath io.vertx.core.Launcher run tech.greenfield.vertx.irked.example.App"
Usage
Under Irked we use the concept of a "Controller" - a class whose fields and methods are used as handlers for routes and that will handle incoming HTTP requests from Vert.x-web.
A "master controller" is created to define the root of the URI hierarchy - all configured routes on that controller will be parsed relative to the root of the host.
Setup and Simple Routing
To publish routes to the server's "Request Handler", create your controller class by extending the
irked Controller class, define fields or methods to handle HTTP requests and annotate them with
the route matching logic that you want Irked to configure for each handler.
- Most often you'd want to just match on HTTP method and URI, for which Irked offers
annotations of the form
@Method("/path"), such as@Get("/foo"). - You can also use the annotation
@Endpoint("/path")to match on all methods (this very is useful to mount sub-controllers, as detailed below). - The path argument for the both method-specific and
@Endpointannotations is optional and omitting it will match requests for all URIs.
An Example Controller
package com.example.api;
import tech.greenfield.vertx.irked.*;
import tech.greenfield.vertx.irked.status.*;
import tech.greenfield.vertx.irked.annotations.*;
class Root extends Controller {
@Get("/")
Handler<RoutingContext> index = r -> {
// classic Vert.x RoutingContext usage
r.response().setStatusCode(200).end("Hello World!");
};
@Post("/")
void create(Request r) {
// the irked Request object offers some useful helper methods over the
// standard Vert.x RoutingContext
r.send(new BadRequest("Creating resources is not yet implemented"));
}
@Put("/update")
String update(Request r) {
return "Updated completed";
}
}
Initializing
After creating your set of Controller implementations, deploy them to Vert.x by setting up
a Verticle like you would do for a Vert.x-web Router,
but use Irked to create a router from your root controller - and set that as the request handler.
A Vert.x Web HTTP Server Example
Router router = Irked.router(vertx).with(new com.example.api.Root());
vertx.createHttpServer().requestHandler(router).listen(8080);
Configuration Errors
Sometimes humans make mistakes, it happens. When Irked is asked to setup your controllers
(when calling with() or one of the other setup methods), it scans your configuration
(the controller classes annotations) and if it detects a configuration that cannot be
executed - for example, handlers with too few or too many parameters - it will throw
an InvalidRouteConfiguration exception with as much details as appropriate. This
happens during the Vert.x setup stage and before the HTTP server request handler is set
up. We make every attempt to not cause exceptions to be thrown during actual request
processing, and any such generated by the Irked implementation will be considered bugs and
will be fixed.
Sub Controllers
Complex routing topologies can be implemented by "mounting" sub-controllers under
the main controller - by setting fields to additional Controller implementations and annotating
them with the @Endpoint annotation with the URI set to the endpoint you want your sub-controller
to be accessible under.
Main and Sub Controllers Example
package com.example.api;
import tech.greenfield.vertx.irked.*;
import tech.greenfield.vertx.irked.annotations.*;
class Root extends Controller {
@Endpoint("/blocks") // the block API handles all requests that start with "/blocks/"
BlockApi blocks = new BlockApi();
}
package com.example.api;
import tech.greenfield.vertx.irked.*;
import tech.greenfield.vertx.irked.annotations.*;
class BlockApi extends Controller {
@Get("/:id") // handle requests like "GET /blocks/identifier"
Handler<Request> retrieve = r -> {
// irked supports Vert.x-web path parameters
r.send(loadBlock(r.pathParam("id")));
};
}
Request Context Re-programming
As hinted above, irked supports path parameters using Vert.x-web, but unlike Vert.x-web's sub-router, irked controllers support path parameters everywhere, including as base paths for mounting sub-controllers.
As a result, a sub-controller might be interested in reading data from a path parameter defined in a parent controller, such that the sub-controller has no control over the definition. To promote object oriented programming with good encapsulation, irked allows parent controllers to provide access to parameter (and other) data by "re-programming" the routing context that is passed to sub-controllers.
A parent controller can define path parameters and then extract the data and hand it down to local
handlers and sub-controllers through a well defined API, by overriding the
Controller.getRequestContext() method.
This functionality is also just useful to DRY repeatable data access pattern by encapsulating them in a request context type.
Request Context Re-programming Example
package com.example.api;
import tech.greenfield.vertx.irked.*;
import tech.greenfield.vertx.irked.annotations.*;
class MyRequest extends Request {
String id;
public MyRequest(Request req) {
super(req);
id = req.pathParam("id");
}
public String getId() {
return id;
}
}
package com.example.api;
import tech.greenfield.vertx.irked.*;
import tech.greenfield.vertx.irked.status.*;
import tech.greenfield.vertx.irked.annotations.*;
class Root extends Controller {
@Get("/:id")
void report(MyRequest r) {
r.response(new OK()).end(createReport(r.getId()));
}
@Endpoint("/:id/blocks") // ":id" is read by the MyRequest class
BlockApi blocks = new BlockApi();
@Override
protected MyRequest getRequestContext(Request req) {
return new MyRequest(req);
}
}
package com.example.api;
import tech.greenfield.vertx.irked.*;
import tech.greenfield.vertx.irked.annotations.*;
class BlockApi extends Controller {
@Get("/")
Handler<MyRequest> retrieve = r -> {
r.send(getAllBlocksFor(r.getId())); // read the identifier field defined by Root and MyRequest
};
}
Handler-specific Request Context Re-Programming
By having a controller implement getRequestContext() it can create a specific Request sub-type to be used
by all handlers in that controller or controllers mounted under it. Handlers can also require additional specialization
of the request context, on a per-handler basis. That allows different handlers to use different
specializations - if it makes sense. Irked will automatically construct such specializations using one
of two methods:
- If the specialization can be trivially constructed from the current request context - either
Requestor a controller-level specialization created by a customgetRequestContext()- i.e. the specialized Request sub-type class has a constructor that takes a single such parameter. - If the current controller class provides a creator method that accepts a
Requestinstance (not a sub-type) and returns the needed type.
Both methods are demonstrated in the following examples:
Trivially Constructed Request Context Specialization Example
package com.example.api;
import tech.greenfield.vertx.irked.*;
import tech.greenfield.vertx.irked.status.*;
import tech.greenfield.vertx.irked.annotations.*;
import com.example.api.Hasher;
class Api extends Controller {
publi
