This documentation is for developers of Helidon, or for developers of additional features for Helidon Declarative
A declarative programming model for Helidon SE.
Declarative: a programming model where we declare intention by annotating elements, to achieve functionality that would otherwise require significant programming effort
Rules for Helidon Declarative:
- Required APIs will be part of the existing SE module (Annotations, support for generated code, new APIs)
- Annotations will use the "nested" approach we have started with builders, i.e.
@Http.Path; the class that annotations are nested in should not be used in any other way (i.e. it should not have methods); find an alternative name if an existing class would be the best fit, or deprecate existing methods and move them elsewhere (SeeFaultTolerancevs.Ft) - The code generation for all declarative features that are part of Helidon will be done in
declarative/codegen, this module can have packages for each feature; as this module does not depend on any other feature module, and only triggers based on feature annotations, it is safe to collect the code generation classes together - It is forbidden to use reflection in any declarative feature; if reflection seems to be needed, replace it with code-generated
type (example: fault tolerance fallback needs to invoke a fallback method, as this would require reflection, there is a
generated type such as
GreetEndpoint_failingFallback__Fallbackthat is named with the unique identification of the method it is generated for, and has the required code generated, the interceptor for fallback then looks it up at runtime to correctly handle invocation) - If there is a good reason the user may want to use a custom named service implementation, provide a way to inject it (see Retry
generated code for named retries, such as
GreetEndpoint_retriable__Retry.java) - All features must be configured through service registry.
A few codegen features that are available:
io.helidon.codegen.spi.TypeMapperProviderto map aTypeInfocreated by codegen to a newTypeInfo- this is useful to add annotations, remove annotations etc. before code generation startsio.helidon.codegen.spi.ElementMapperProviderto map aTypedElementInfo- similar as above, but on the level of methods and fieldsio.helidon.codegen.spi.AnnotationMapperProviderto map annotations (i.e. we could map@Autowiredto@Service.Inject)io.helidon.service.codegen.spi.RegistryCodegenExtensionProvider(or the "hard-core"io.helidon.codegen.spi.CodegenExtensionProvider) to create extensions to build custom generated types - example can be found inio.helidon.declarative.codegen.scheduling.SchedulingExtensionProvider
For each Helidon feature, we need a namespace class to contain the annotations and APIs
| Feature | Class | Notes |
|---|---|---|
| HTTP | Http, RestServer, RestClient |
WebServer cannot be freed, HttpClient cannot be freed |
| Config | Configuration |
Config cannot be freed |
| Metrics | Metric |
New class, as Metrics is already used |
| Fault Tolerance | Ft |
FaultTolerance could theoretically be freed |
| GRPC | RpcServer, RpcClient |
GrpcClient cannot be freed |
| WebSocket | WebSocketClient, WebSocketServer |
WsClient cannot be freed |
| Security | Secured |
Security cannot be freed (big API), existing annots. |
| Messaging | Messages |
Messaging cannot be freed |
| Scheduling | Scheduling |
Deprecate methods and current types for removal |
| Health | Health |
OK |
| OpenAPI | OpenApi |
OK |
| Builders | Prototype, Option, RuntimeType |
OK (maybe just use Builder?) |
| Tracing | Tracing |
OK |
| CORS | Cors |
OK |
| MCP protocol | McpServer |
OK |
| DbClient | N/A | DbClient cannot be freed, maybe combine with Data? |
| GraphQL | GraphQlServer, GraphQlClient |
OK |
| Data | Data |
OK |
| Logging | N/A | Not sure we need, Logging is free |
| LRA | LRA |
Lra cannot be freed |
| Transactions | Tx |
Transaction is the interface |
| Service Registry | Service, Interception |
OK |
| Validation | Validation, Check |
OK |
| Feature | Class | Notes |
|---|---|---|
| Langchain4j | Ai |
OK |
The following Helidon features can be used to create a new declarative feature
- Interceptors - metrics, tracing, logging etc.
- Injection (service factory) - for any feature where we expect the user to inject a specific service that the feature provides ( AI, declarative rest client etc.)
- Code generation - for any feature that needs additional code to minimize runtime lookups and handling; ideally we should have
injection points that can be bound at build time (as opposed to runtime registry lookups) - see
Interception.ElementInterceptorfor generating code specific to a single method
All feature codegens belong to this module.
There are a few types in the top level package:
RunLevels- all run levels used by our features, to have a single place where we can see it, any@RunLevelannotation generated MUST use a value from this typeDeclarativeTypes-TypeNameconstants for common types that are not defined inio.helidon.service.codegen.ServiceCodegenTypes
See feature codegen details in the Codegen Readme.
Each method that is invoked for an external trigger is considered an entry point (HTTP method, grpc method etc.). There is a set of tools to create entry point interceptors (one of the main reasons is to support security, running within a context etc.).
Definition of Helidon declarative features.
Defines a Server HTTP Service.
Each method is a route with its own path (see declaration below).
The method has a choice of using the "core" Helidon SE approach - getting ServerRequest and ServerResponse as parameters and
doing everything manually, or by using qualified parameters to obtain the desired information from the request, and returning an
object that will be sent as the response entity.
To a certain degree, these approaches can be combined (i.e. we can get ServerRequest and return an entity, or get annotated
parameters and ServerResponse).
Annotations on type:
@RestServer.Endpoint- required annotation on a type representing the endpoint@RestServer.Listener- optional annotation that allows selecting a specific listener (socket) to use@RestServer.Header- a header to be sent with every response from the server, defined either on the endpoint class, or on a method (repeatable)@RestServer.ComputedHeader- a header to be sent with every response computed from a service@Http.Path- the path this endpoint will be available on
Annotations on method(s), may be defined on the endpoint type, or on an interface the endpoint type implements:
@RestServer.Status- define HTTP status to return from a method when the default is not good@Http.GET,@Http.POSTetc., or@Http.Method("LIST")- mutually exclusive, to define the HTTP method that the endpoint method will be available on@Http.Produces- the media type produced by this method (returned in theContent-Typeheader), also used when matching theAcceptheader of the request@Http.Consumes- the media type expected by this method, when the request has an entity, matched againstContent-Typeof the request@Http.Path- the path this method will be available on, nested within the endpoint path, may contain path parameters (same as we can do when setting up routing)
Parameters defined by type:
ServerRequest- SE webserver requestServerResponse- SE webserver responseio.helidon.common.context.Context- server request context- other parameters as supported by code generators for other features (i.e.
SecurityContextwill be supported as soon as security feature is implemented) - seeio.helidon.declarative.codegen.http.webserver.spi.HttpParameterCodegenProvider
Parameters defined by qualifiers (may be an Optional, supports Mappers):
@Http.HeaderParam- a named header from the request@Http.QueryParam- a named query parameter@Http.PathParam- a named parameter from the definition of@Http.Path@Http.Entity- the HTTP request entity
Currently we only support use of configuration in String annotation properties, where a template can be used that will
be read from configuration, such as @RestServer.Listener("${my-endpoint.listener:admin}"), which would look for
my-endpoint.listener property in configuration, and if not found, would use the default value of admin instead.
Capability to override values specified through annotations and additional configurability of declarative features will be designed later.
A __HttpFeature class is code generated for each @RestServer.Endpoint.
This type creates entry point interceptors for each method.
The feature is a "usual" HttpFeature picked up by WebServer starter service.
In case a Http.Produces or Http.Consumes is defined on a method, the route tests the Content-Type/Accept headers respectively,
and only invokes the method if both match.
For each qualified parameter, the parameter is obtained from the request using generated code that uses constants wherever possible (for header names, header values, media types etc.).
Defines a Client HTTP API.
Each method is a representation of a server side route.
The declarative client shares annotations from the io.helidon.http.Http with server endpoint declaration, so the same interface
can be used to define both server-side and client-side API.
Declaration must be done on an interface.
Annotations on type:
@RestClient.Endpoint- required annotation to generate a typed rest client@RestClient.Header- a header to be sent with every request (repeatable)@RestClient.ComputedHeader- a header to be sent with every request computed from a service@Http.Path- base path of every request from this client
Annotations on the interface method(s):
@Http.Path- path of this method (sub-path of the path defined on the type)@RestClient.Header- a header to be sent with every request (repeatable)@RestClient.ComputedHeader- a header to be sent with every request computed from a@Http.GET,@Http.POSTetc., or@Http.Method("LIST")- mutually exclusive, to define the HTTP method that the client will invoke@Http.Produces- the media type produced by the server (client response)@Http.Consumes- the media type expected by the server (client request)
Parameters defined by qualifiers (may be an Optional, supports Mappers):
@Http.HeaderParam- a named header for the request@Http.QueryParam- a named query parameter@Http.PathParam- a named parameter for the definition of@Http.Path@Http.Entity- the HTTP request entity
To use a declarative rest client, simply inject the annotated interface it into your code, using @RestClient.Client qualifier
for the injection point:
@Service.Inject
MyClass(@RestClient.Client MyRestClient restClient) {
}To create an error handler, create a service that implements io.helidon.webclient.api.RestClient.ErrorHandler
In case @RestClient.Endpoint.clientName() is defined and exists in the registry, all WebClient configuration will be ignored (
except for the URI).
The @RestClient.Endpoint may define a value() with the URI of the endpoint. If it is empty, the value MUST be provided by
configuration, otherwise it can be overridden from configuration.
The base of configuration for a declarative client is the fully qualified name of the annotated interface. This key can be
modified using configKey property of the @RestClient.Endpoint annotation.
There are two keys that can be defined under this key:
uri- the URI of the remote service (excluding the path as defined by@Http.Path)client- configuration options of Helidon WebClient
A class named AnnotatedInterfaceName__DeclarativeClient will be generated for types annotated with @RestClient.Endpoint.
This class will implement all of the interface methods, and it will use a configured instance of Helidon WebClient to invoke all
requests.
The implementation uses constants wherever possible (header names, header values, media types etc.).
Annotated method(s) of a service will be invoked with the schedule defined by the annotation. When the registry is shutdown, all the scheduled tasks will be closed.
Annotations (mutually exclusive):
@Schedule.Cron- on a method@Schedule.FixedRate- on a method
Parameters:
io.helidon.scheduling.CronInvocationfor@Schedule.Cron, not requiredio.helidon.scheduling.FixedRateInvocationfor@Schedule.FixedRate, not required
Scopes:
- The service with annotated method(s) can be a
SingletonorPerLookupscope. - The generated
ScheduledStarteris aSingleton
The schedule can be overridden by configuration, default configuration key is:
<fully-qualified-class-name.method-name.schedule>, i.e. my.app.MyType.updateValues.schedule, with the possibility to use a
custom configuration key (through annotation property)
For each class with at least one annotated method, a __ScheduledStarter class is generated with
@RunLevel(io.helidon.declarative.codegen.RunLevels.SCHEDULING).
If a @Weight is defined on the service, the generated starter will have the same weight (this allows ordering of triggering of
scheduled tasks)
The class will have a @PostConstruct method that creates the tasks, and a
@PreDestroy that closes the tasks.
Support for fault-tolerance features. In most cases, fault tolerance is an interceptor, that makes sure the method is invoked as expected.
Annotations:
Ft.Fallback- defines a method to fallback to in case an exception is thrownFt.Async- runs the method asynchronously in an executor serviceFt.Retry- retries the method in case an exception is thrownFt.Timeout- a timeout exception is thrown in case the method takes longer than definedFt.Bulkhead- limits parallel execution of the methodFt.CircuitBreaker- "breaks" the circuit in case exceptions are thrown, automatically throwing an exception, until the method starts returning without exception again
Parameters: Fault tolerance annotations ignore method parameters
For the cases where named instances are supported, you can create a custom service instance named according to the name from annotation. Configuration override for annotation values is currently not supported.
An element interceptor class ClassName_methodName__Fallback is generated for each @Ft.Fallback annotated method.
The generated type implements the fallback functionality without the use of reflection. The fallback method decides whether to
re-throw the exception (if it should be skipped), or calls the fallback method (if it should be applied).
An element interceptor class ClassName_methodName__Async is generated for each @Ft.Async annotated method.
The Async instance can be named - in this case the generated code tries to get a named instance from the registry and use it. If
not available, a new async is produced.
The ExecutorService used with produced async can be named - in this case the generated code tries to get a named executor from
the service registry and use it. If not available, no executor is explicitly configured.
An element interceptor class ClassName_methodName__Retry is generated for each @Ft.Retry annotated method.
The Retry instance can be named - in this case the generated code tries to get a named instance from the registry and use it. If
not available, a new retry instance is produced from annotation properties.
An element interceptor class ClassName_methodName__Timeout is generated for each @Ft.Timeout annotated method.
The Timeout instance can be named - in this case the generated code tries to get a named instance from the registry and use it.
If not available, a new timeout instance is produced from annotation properties.
An element interceptor class ClassName_methodName__Bulkhead is generated for each @Ft.Bulkhead annotated method.
The Bulkhead instance can be named - in this case the generated code tries to get a named instance from the registry and use it.
If not available, a new bulkhead instance is produced from annotation properties.
An element interceptor class ClassName_methodName__CircuitBreaker is generated for each @Ft.CircuitBreaker annotated method.
The CircuitBreaker instance can be named - in this case the generated code tries to get a named instance from the registry and
use it. If not available, a new circuit breaker instance is produced from annotation properties.
Validation provides capabilities to validate types and method parameters/return values. Both features require code-generation.
Type validation code-generation of type validators is triggered by the presence of the @Validation.Validated annotation on a type.
Method validation code-generation of interceptors is triggered by the presence of an annotation "meta-annotated"
with @Validation.Constraint on a method, its parameter, or type arguments of its parameters or return type.
In addition the Check.Valid annotation also triggers code-generation, and can be used to validate against a type validator
mentioned above.
Annotations:
- All annotations from
io.helidon.validation.Checkclass to trigger validation using an interceptor @Validation.Validated- on a type to generate type validator for a type
There is currently no configuration for validation.
Each constraint has a dedicated validator provider service, with (default-weight - 30) weight. The providers are annotated with a named annotation that matches the constraint annotation.
This allows our users to override the implementation using their custom services.
The validation process works as follows:
- an interceptor is generated for any method that has constraint annotations or valid annotation
- the interceptor obtains all constraint validation instances and type validators needed to validate a call
- the interceptor validates the call and throws a
ValidationExceptionif validation fails
The type validation works as follows:
- a type validator is generated for any type that has a
@Validation.Validatedannotation - the type validator obtains all constraint validation instances and type validators needed to validate an instance
- the type validator validates the type and returns response with possible constraint violations
Important types:
Check- a container class for all constraint annotationsValidation- a container class for validation annotations that are not constraintsValidationException- throws when validation fails in an interceptorValidator- programmatic API to validate instances and their properties (only for validated types), can be obtained from service registryConstraintValidatorProvider- service registry service that validates a single constraint annotation typeConstraintValidator- created for each annotated element using the type of the element and the constraint annotationvalidatorspackage contains built-in constraint validator providers
Supported concepts:
- any service method annotated with a constraint annotation will be intercepted and validated
- any service method that has parameters with at least one constraint annotation will be intercepted and validated
- for identification of "what to validate",
@Check.Validis a constraint annotation - annotations on type arguments of method parameters and method return type are supported, as long as they are on
Optional,Map,Collection- i.e.List<@Check.Valid String>will trigger an interceptor and will be validated - types annotated with
@Validation.Validatedwill be validated when used in a method or constructor that uses@Check.Validon the typed parameter - same rules as for generation of interceptors apply for type validation: constraints are honored, including getters, fields, and type arguments
- a user may create a "compound annotation" - an annotation that has one or more constraint annotations, these will be honored as if the element was directly annotated with the constraints
- a user may create a custom constraint annotation (annotation meta-annotated with
@Validation.Constraint), such annotations may also be meta-annotated with additional constraints; a custom constraint annotation requires a custom validator provider