Overview
Helidon declarative programming model allows inversion of control style programming with all the performance benefits of Helidon SE.
Our declarative approach has the following advantages:
Uses Helidon SE imperative code to implement features (i.e. performance is same as "pure" imperative application)
Generates all the necessary code at build-time, to avoid reflection and bytecode manipulation at runtime
It is based on Helidon Injection
Declarative features are in the same modules as Helidon SE features (i.e. does not require additional dependencies)
Note
Helidon Declarative is an incubating feature. The APIs shown here are subject to change. These APIs will be finalized in a future release of Helidon.
Usage
To create a declarative application, use the annotations provided in our Helidon SE modules (details under Features), and the maven plugin described in Injection: Startup to generate the binding.
In addition, the following section must be added to the build of the Maven pom.xml to enable annotation processors that generate the necessary code:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-apt</artifactId>
<version>${helidon.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>Features
The following features are currently implemented:
A Helidon Declarative application should be started using the generated application binding, to ensure no lookup and no reflection. The call to ServiceRegistryManager.start ensures that all services with a defined RunLevel are started, including Helidon WebServer, Scheduled services etc.
@Service.GenerateBinding // generated binding to bypass discovery and runtime binding
public static class Main {
public static void main(String[] args) {
// configure logging
LogConfig.configureRuntime();
// start the "container"
ServiceRegistryManager.start(ApplicationBinding.create());
}
}Configuration
Configuration can be injected as a whole into any service, or a specific configuration option can be injected using @Configuration.Value. Default values can be defined using annotations in @Default
Services available for injection:
Annotations:
io.helidon.config.Configuration.Value- define the configuration key to inject, on constructor parameterAnnotations defined in
io.helidon.common.Default- define a default typed value, on the same constructor parameter
Example of usage can be seen below in HTTP Server Endpoint example.
HTTP Server Endpoint
To create an HTTP endpoint, simply annotate a class with @RestServer.Endpoint, and add at least one method annotated with one of the HTTP method annotations, such as @Http.GET.
Services available for injection:
N/A
Supported method parameters (no annotation required):
io.helidon.common.security.SecurityContextio.helidon.security.SecurityContext- in casehelidon-securitymodule is on the classpath
Annotations on endpoint type:
io.helidon.webserver.http.RestServer.Endpoint- required annotationio.helidon.webserver.http.RestServer.Listener- to define the named listener this should be served on (named port/socket)io.helidon.webserver.http.RestServer.Header- header to return with each response from this endpointio.helidon.webserver.http.RestServer.ComputedHeader- computed header to return with each response from this endpointio.helidon.http.Http.Path- path (context) this endpoint will be available on
Annotations on endpoint methods:
io.helidon.webserver.http.RestServer.Header- header to return with each response from this methodio.helidon.webserver.http.RestServer.ComputedHeader- computed header to return with each response from this methodio.helidon.webserver.http.RestServer.Status- status to return (if a custom one is required)io.helidon.http.Http.Path- path (context) this method will be available on (subpath of the endpoint path)io.helidon.http.Http.GET(and other methods) - definition of HTTP method this method will serveio.helidon.http.Http.HttpMethod- for custom HTTP method names (mutually exclusive with above)io.helidon.http.Http.Produces- what media type this method produces (return entity content type)io.helidon.http.Http.Consumes- what media type this method accepts (request entity content type)
Annotations on method parameters:
io.helidon.http.Http.Entity- Request entity, a typed parameter is expected, will use HTTP media type modules to coerce into the correct typeio.helidon.http.Http.HeaderParam- Typed HTTP request header valueio.helidon.http.Http.QueryParam- Typed HTTP query valueio.helidon.http.Http.PathParam- Typed parameter from path template
@RestServer.Endpoint // identifies this class as a server endpoint
@Http.Path("/greet") // serve this endpoint on /greet context root (path)
@Service.Singleton // a singleton service (single instance within a service registry)
static class GreetEndpoint {
private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Map.of());
private final String greeting;
// inject app.greeting configuration value, use "Hello" if not configured
GreetEndpoint(@Configuration.Value("app.greeting") @Default.Value("Hello") String greeting) {
this.greeting = greeting;
}
@Http.GET // HTTP GET endpoint
@Http.Produces(MediaTypes.APPLICATION_JSON_VALUE) // produces entity of application/json media type
public JsonObject getDefaultMessageHandler() {
// build the JSON object (requires `helidon-http-media-jsonp` on classpath)
return JSON.createObjectBuilder()
.add("message", greeting + " World!")
.build();
}
}Typed HTTP Client
To create a typed HTTP client, create an interface annotated with RestClient.Endpoint, and at least one method annotated with one fo the HTTP method annotations, such as @Http.GET. Methods can only have parameters annotated with one of the Http qualifiers.
Annotations on endpoint type:
io.helidon.webclient.api.RestClient.Endpoint- required annotationio.helidon.http.Http.Path- path (context) the server listens onio.helidon.webclient.api.RestClient.Header- header to include in every request to the serverio.helidon.webclient.api.RestClient.ComputedHeader- header to compute and include in every request to the server
Annotations on endpoint methods:
io.helidon.webclient.api.RestClient.Header- header to include in every request to the serverio.helidon.webclient.api.RestClient.ComputedHeader- header to compute and include in every request to the serverio.helidon.http.Http.Path- path (context) the server serves this endpoint method onio.helidon.http.Http.GET(and other methods) - definition of HTTP method this method will invokeio.helidon.http.Http.HttpMethod- for custom HTTP method names (mutually exclusive with above)io.helidon.http.Http.Produces- what media type this method produces (content type of entity from the server)io.helidon.http.Http.Consumes- what media type this method accepts (request entity content type)
Annotations on method parameters:
io.helidon.http.Http.Entity- Request entity, a typed parameter is expected, will use HTTP media type modules to write to the requestio.helidon.http.Http.HeaderParam- Typed HTTP header value to sendio.helidon.http.Http.QueryParam- Typed HTTP query value to sendio.helidon.http.Http.PathParam- Typed parameter from path template to construct the request URI
@RestClient.Endpoint("${greet-service.client.uri:http://localhost:8080}")
@RestClient.Header(name = HeaderNames.USER_AGENT_NAME, value = "my-client")
interface GreetClient {
@Http.GET
@Http.Produces(MediaTypes.APPLICATION_JSON_VALUE)
JsonObject getDefaultMessageHandler();
}Fault Tolerance
Fault tolerance annotation allow adding features to methods on services. The annotations can be added to any method that supports interception (i.e. methods that are not private).
Method Annotations:
io.helidon.faulttolerance.Ft.Retry- allow retriesio.helidon.faulttolerance.Ft.Fallback- fallback to another method that providesio.helidon.faulttolerance.Ft.Async- invoke method asynchronouslyio.helidon.faulttolerance.Ft.Timeout- invoke method with a timeoutio.helidon.faulttolerance.Ft.Bulkhead- use bulkheadio.helidon.faulttolerance.Ft.CircuitBreaker- use circuit breaker
@Service.Singleton
static class AlgorithmService {
@Ft.Fallback(value = "fallbackAlgorithm", applyOn = IOException.class)
String algorithm() throws IOException {
// may throw an exception
return "some-algorithm";
}
// method that would be called if #algorithm fails with an IOException
String fallbackAlgorithm() {
return "default";
}
}Scheduling
Scheduling allows service methods to be invoked periodically.
Method annotations:
io.helidon.scheduling.Scheduling.Cron- execute with schedule defined by a CRON expressionio.helidon.scheduling.Scheduling.FixedRate- execute with a fixed interval
@Service.Singleton
static class CacheService {
@Scheduling.FixedRate("PT5S")
void checkCache() {
// do something every 5 seconds
}
}