Skip to content

Commit 1f6667c

Browse files
committed
Initial documentation for Helidon Declarative
1 parent e5799a1 commit 1f6667c

File tree

2 files changed

+308
-0
lines changed

2 files changed

+308
-0
lines changed

docs/src/main/asciidoc/se/injection.adoc

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ include::{rootdir}/includes/se.adoc[]
4444
- <<Events, Events>>
4545
- <<Programmatic Lookup, Programmatic Lookup>>
4646
- <<Startup, Startup>>
47+
- <<Declarative, Declarative>>
4748
4849
== Overview
4950
@@ -905,3 +906,189 @@ level2 created
905906
level2 destroyed
906907
level1 destroyed
907908
----
909+
910+
== Declarative
911+
912+
Helidon declarative programming model allows inversion of control style programming with all the performance benefits of Helidon SE.
913+
914+
Our declarative approach has the following advantages:
915+
916+
- Uses Helidon SE imperative code to implement features (i.e. performance is same as "pure" imperative application)
917+
- Generates all the necessary code at build-time, to avoid reflection and bytecode manipulation at runtime
918+
919+
Helidon declarative is currently a preview feature, though we welcome feedback!
920+
921+
To create a declarative application, use the annotations provided in our Helidon SE modules (details below), and the maven plugin
922+
described above to generate the binding.
923+
In addition, the following section must be added to the `build` of the Maven `pom.xml` to enable annotation processors that
924+
generate the necessary code:
925+
926+
[source,xml]
927+
----
928+
<plugins>
929+
<plugin>
930+
<groupId>org.apache.maven.plugins</groupId>
931+
<artifactId>maven-compiler-plugin</artifactId>
932+
<configuration>
933+
<annotationProcessorPaths>
934+
<path>
935+
<groupId>io.helidon.bundles</groupId>
936+
<artifactId>helidon-bundles-apt</artifactId>
937+
<version>${helidon.version}</version>
938+
</path>
939+
</annotationProcessorPaths>
940+
</configuration>
941+
</plugin>
942+
</plugins>
943+
----
944+
945+
The following features are currently implemented:
946+
947+
- <<Dec-Config, Configuration>>
948+
- <<Dec-HTTP-Server, HTTP Server Endpoint>>
949+
- <<Dec-HTTP-Client, Typed HTTP Client>>
950+
- <<Dec-FT, Fault Tolerance>>
951+
- <<Dec-Scheduling, Scheduling>>
952+
953+
954+
[source,java]
955+
.Example of a declarative main class
956+
----
957+
include::{sourcedir}/se/inject/DeclarativeExample.java[tag=snippet_1, indent=0]
958+
----
959+
960+
=== Configuration [[Dec-Config]]
961+
962+
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`
963+
964+
Services available for injection:
965+
966+
- `io.helidon.config.Config`
967+
968+
Annotations:
969+
970+
- `io.helidon.config.Configuration.Value` - define the configuration key to inject, on constructor parameter
971+
- `io.helidon.common.Default.*` - define a default typed value, on the same constructor parameter
972+
973+
Example of usage can be seen below in HTTP Server Endpoint example.
974+
975+
=== HTTP Server Endpoint [[Dec-HTTP-Server]]
976+
977+
To create an HTTP endpoint, simply annotate a class with `@RestServer.Endpoint`, and
978+
add at least one method annotated with one of the HTTP method annotations, such as `@Http.GET`.
979+
980+
Services available for injection:
981+
982+
N/A
983+
984+
Supported method parameters (no annotation required):
985+
986+
- `io.helidon.webserver.http.ServerRequest`
987+
- `io.helidon.webserver.http.ServerResponse`
988+
- `io.helidon.common.context.Context`
989+
- `io.helidon.common.security.SecurityContext`
990+
- `io.helidon.security.SecurityContext` - in case `helidon-security` module is on the classpath
991+
992+
Annotations on endpoint type:
993+
994+
- `io.helidon.webserver.http.RestServer.Endpoint` - required annotation
995+
- `io.helidon.webserver.http.RestServer.Listener` - to define the named listener this should be served on (named port/socket)
996+
- `io.helidon.webserver.http.RestServer.Header` - header to return with each response from this endpoint
997+
- `io.helidon.webserver.http.RestServer.ComputedHeader` - computed header to return with each response from this endpoint
998+
- `io.helidon.http.Http.Path` - path (context) this endpoint will be available on
999+
1000+
Annotations on endpoint methods:
1001+
1002+
- `io.helidon.webserver.http.RestServer.Header` - header to return with each response from this method
1003+
- `io.helidon.webserver.http.RestServer.ComputedHeader` - computed header to return with each response from this method
1004+
- `io.helidon.webserver.http.RestServer.Status` - status to return (if a custom one is required)
1005+
- `io.helidon.http.Http.Path` - path (context) this method will be available on (subpath of the endpoint path)
1006+
- `io.helidon.http.Http.GET` (and other methods) - definition of HTTP method this method will serve
1007+
- `io.helidon.http.Http.HttpMethod` - for custom HTTP method names (mutually exclusive with above)
1008+
- `io.helidon.http.Http.Produces` - what media type this method produces (return entity content type)
1009+
- `io.helidon.http.Http.Consumes` - what media type this method accepts (request entity content type)
1010+
1011+
Annotations on method parameters:
1012+
1013+
- `io.helidon.http.Http.Entity` - Request entity, a typed parameter is expected, will use HTTP media type modules to coerce into the correct type
1014+
- `io.helidon.http.Http.HeaderParam` - Typed HTTP request header value
1015+
- `io.helidon.http.Http.QueryParam` - Typed HTTP query value
1016+
- `io.helidon.http.Http.PathParam` - Typed parameter from path template
1017+
1018+
[source,java]
1019+
.Example of an HTTP Server Endpoint
1020+
----
1021+
include::{sourcedir}/se/inject/DeclarativeExample.java[tag=snippet_2, indent=0]
1022+
----
1023+
1024+
=== Typed HTTP Client [[Dec-HTTP-Client]]
1025+
1026+
To create a typed HTTP client, create an interface annotated with `RestClient.Endpoint`, and at least one method annotated
1027+
with one fo the HTTP method annotations, such as `@Http.GET`. Methods can only have parameters annotated with one of the
1028+
`Http` qualifiers.
1029+
1030+
Annotations on endpoint type:
1031+
1032+
- `io.helidon.webclient.api.RestClient.Endpoint` - required annotation
1033+
- `io.helidon.http.Http.Path` - path (context) the server listens on
1034+
- `io.helidon.webclient.api.RestClient.Header` - header to include in every request to the server
1035+
- `io.helidon.webclient.api.RestClient.ComputedHeader` - header to compute and include in every request to the server
1036+
1037+
Annotations on endpoint methods:
1038+
1039+
- `io.helidon.webclient.api.RestClient.Header` - header to include in every request to the server
1040+
- `io.helidon.webclient.api.RestClient.ComputedHeader` - header to compute and include in every request to the server
1041+
- `io.helidon.http.Http.Path` - path (context) the server serves this endpoint method on
1042+
- `io.helidon.http.Http.GET` (and other methods) - definition of HTTP method this method will invoke
1043+
- `io.helidon.http.Http.HttpMethod` - for custom HTTP method names (mutually exclusive with above)
1044+
- `io.helidon.http.Http.Produces` - what media type this method produces (content type of entity from the server)
1045+
- `io.helidon.http.Http.Consumes` - what media type this method accepts (request entity content type)
1046+
1047+
Annotations on method parameters:
1048+
1049+
- `io.helidon.http.Http.Entity` - Request entity, a typed parameter is expected, will use HTTP media type modules to write to the request
1050+
- `io.helidon.http.Http.HeaderParam` - Typed HTTP header value to send
1051+
- `io.helidon.http.Http.QueryParam` - Typed HTTP query value to send
1052+
- `io.helidon.http.Http.PathParam` - Typed parameter from path template to construct the request URI
1053+
1054+
[source,java]
1055+
.Example of a Typed HTTP Client
1056+
----
1057+
include::{sourcedir}/se/inject/DeclarativeExample.java[tag=snippet_3, indent=0]
1058+
----
1059+
1060+
1061+
=== Fault Tolerance [[Dec-FT]]
1062+
1063+
Fault tolerance annotation allow adding features to methods on services.
1064+
The annotations can be added to any method that supports interception (i.e. methods that are not private).
1065+
1066+
Method Annotations:
1067+
1068+
- `io.helidon.faulttolerance.Ft.Retry` - allow retries
1069+
- `io.helidon.faulttolerance.Ft.Fallback` - fallback to another method that provides
1070+
- `io.helidon.faulttolerance.Ft.Async` - invoke method asynchronously
1071+
- `io.helidon.faulttolerance.Ft.Timeout` - invoke method with a timeout
1072+
- `io.helidon.faulttolerance.Ft.Bulkhead` - use bulkhead
1073+
- `io.helidon.faulttolerance.Ft.CircuitBreaker` - use circuit breaker
1074+
1075+
[source,java]
1076+
.Example of Fault Tolerance Fallback
1077+
----
1078+
include::{sourcedir}/se/inject/DeclarativeExample.java[tag=snippet_4, indent=0]
1079+
----
1080+
1081+
=== Scheduling [[Dec-Scheduling]]
1082+
1083+
Scheduling allows service methods to be invoked periodically.
1084+
1085+
Method annotations:
1086+
1087+
- `io.helidon.scheduling.Scheduling.Cron` - execute with schedule defined by a CRON expression
1088+
- `io.helidon.scheduling.Scheduling.FixedRate` - execute with a fixed interval
1089+
1090+
[source,java]
1091+
.Example of a fixed rate scheduled method
1092+
----
1093+
include::{sourcedir}/se/inject/DeclarativeExample.java[tag=snippet_5, indent=0]
1094+
----
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.docs.se.inject;
18+
19+
import java.io.IOException;
20+
import java.util.Map;
21+
22+
import io.helidon.common.Default;
23+
import io.helidon.common.media.type.MediaTypes;
24+
import io.helidon.config.Configuration;
25+
import io.helidon.faulttolerance.Ft;
26+
import io.helidon.http.HeaderNames;
27+
import io.helidon.http.Http;
28+
import io.helidon.logging.common.LogConfig;
29+
import io.helidon.scheduling.Scheduling;
30+
import io.helidon.service.registry.Binding;
31+
import io.helidon.service.registry.Service;
32+
import io.helidon.service.registry.ServiceRegistryManager;
33+
import io.helidon.webclient.api.RestClient;
34+
import io.helidon.webserver.http.RestServer;
35+
36+
import jakarta.json.Json;
37+
import jakarta.json.JsonBuilderFactory;
38+
import jakarta.json.JsonObject;
39+
40+
@SuppressWarnings("deprecation")
41+
public class DeclarativeExample {
42+
private DeclarativeExample() {
43+
}
44+
45+
// tag::snippet_3[]
46+
@RestClient.Endpoint("${greet-service.client.uri:http://localhost:8080}")
47+
@RestClient.Header(name = HeaderNames.USER_AGENT_NAME, value = "my-client")
48+
interface GreetClient {
49+
@Http.GET
50+
@Http.Produces(MediaTypes.APPLICATION_JSON_VALUE)
51+
JsonObject getDefaultMessageHandler();
52+
}
53+
// end::snippet_3[]
54+
55+
// tag::snippet_1[]
56+
@Service.GenerateBinding
57+
public static class Main {
58+
public static void main(String[] args) {
59+
// configure logging
60+
LogConfig.configureRuntime();
61+
62+
// start the "container"
63+
ServiceRegistryManager.start(ApplicationBinding.create());
64+
}
65+
}
66+
// end::snippet_1[]
67+
68+
// tag::snippet_2[]
69+
@RestServer.Endpoint
70+
@Http.Path("/greet")
71+
@Service.Singleton
72+
static class GreetEndpoint {
73+
private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Map.of());
74+
private final String greeting;
75+
76+
GreetEndpoint(@Configuration.Value("app.greeting") @Default.Value("Hello") String greeting) {
77+
this.greeting = greeting;
78+
}
79+
80+
@Http.GET
81+
@Http.Produces(MediaTypes.APPLICATION_JSON_VALUE)
82+
public JsonObject getDefaultMessageHandler() {
83+
return JSON.createObjectBuilder()
84+
.add("message", greeting + " World!")
85+
.build();
86+
}
87+
}
88+
// end::snippet_2[]
89+
90+
// tag::snippet_4[]
91+
@Service.Singleton
92+
static class AlgorithmService {
93+
@Ft.Fallback(value = "fallbackAlgorithm", applyOn = IOException.class)
94+
String algorithm() throws IOException {
95+
// may throw an exception
96+
return "some-algorithm";
97+
}
98+
99+
String fallbackAlgorithm() {
100+
return "default";
101+
}
102+
}
103+
// end::snippet_4[]
104+
105+
// tag::snippet_5[]
106+
@Service.Singleton
107+
static class CacheService {
108+
@Scheduling.FixedRate("PT5S")
109+
void checkCache() {
110+
// do something every 5 seconds
111+
}
112+
}
113+
// end::snippet_5[]
114+
115+
116+
private static class ApplicationBinding {
117+
static Binding create() {
118+
return null;
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)