-
Notifications
You must be signed in to change notification settings - Fork 567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Helidon Inject documentation #9742
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
== Overview | ||
Injection is the basic building stone for inversion of control. Dependency injection provides a mechanism to get |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend we start with the basics of Injection and IoC. SE developers, prior to 4.2 would be focused only on procedural coding thus a very brief introduction to what is Injection, what IoC is, why use Injection; why consider adopting IoC will help users. Include why we didn't just adopt CDI (light-weight, smaller, faster, easier to understand, ...) I also think we should describe the goals achieved with Helidon Inject (How/Why is Helidon Inject different from other Injection frameworks that a reader might have used/heard about. Some potential differentiators: -- compile-time generation (immutable images, more secure)-- source-code, not byte-code (easier debugging/stack traces) ... (add more beneficial facets as you think is appropriate).
Give the reader a bit of a primer on Inject, IoC, and set up reason to read further.
Signed-off-by: David Kral <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review of examples (injection.adoc
will come later).
|
||
// tag::snippet_6[] | ||
@Service.Singleton | ||
record MyIdProducer(@Service.Named("id") Event.Emitter<String> emitter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to use custom qualifiers instead of a named qualifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I want this to make sense, I would need to paste custom qualifier creation there as well. What I am trying to do, is to examplain the emitter with qualifier. Since we do provider named one out of the box, it does not need to be made and this is te simplest example from my point of view.
docs/src/main/java/io/helidon/docs/se/inject/FactoryExample.java
Outdated
Show resolved
Hide resolved
docs/src/main/java/io/helidon/docs/se/inject/FactoryExample.java
Outdated
Show resolved
Hide resolved
class InjectionPointFactory implements Service.InjectionPointFactory<MyService> { | ||
|
||
@Override | ||
public Optional<Service.QualifiedInstance<MyService>> first(Lookup lookup) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example is just showing that you can implement a method of an interface, not how to query the lookup, which is the reason this factory exists.
|
||
// tag::snippet_6[] | ||
@Service.Singleton | ||
@Service.Named("name") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, using a name qualifier does not work nicely in this example.
docs/src/main/java/io/helidon/docs/se/inject/InterceptorExample.java
Outdated
Show resolved
Hide resolved
docs/src/main/java/io/helidon/docs/se/inject/ServiceRegistryExample.java
Show resolved
Hide resolved
Signed-off-by: David Kral <[email protected]>
Signed-off-by: David Kral <[email protected]>
---- | ||
== Usage | ||
To start using Helidon Inject, you need to create both: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not really called Helidon Inject
anywhere right now. Not sure if that is what we want to use
@Service.Singleton | ||
class MyGreetingService implements GreetingContract { | ||
public String greet(String name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing @Override
1. Through a constructor annotated with `@Service.Inject` - each parameter is considered an injection point; this is the recommended way of injecting dependencies (as it can be unit tested easily, and fields can be declared private final) | ||
2. Through field(s) annotated with `@Service.Inject` - each field is considered an injection point; this is not recommended, as the fields must be accessible (at least package local), and can’t be declared as final | ||
Injected services are picked by the highest weight and implementing the requested contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message seems to be missing something. Weight is suddenly introduced without a link to explanation (or inlined explanation).
Also picked
is not very deterministic here.
This is the definition:
Injection points are satisfied by services that match the required contract and qualifiers.
If more than one service satisfies an injection point, the service with the highest weight is chosen (see `@Weight`, `Weighted`).
If two services have the same weight, the order is undefined.
If the injection point accepts a `java.util.List` of contracts, all available services are used to satisfy the injection point ordered by weight.
Weight, Weighted - this should have proper links
Dependencies can be injected in different formats, depending on the required behavior: | ||
- `Contract` - Retrieves an instance of another service. | ||
- `Optional<Contract>` - Retrieves an instance, but if the service is unavailable, an empty optional is provided. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"but if the service is unavailable" - if there is no service providing the contract
The injection point asks for a contract, not for a service.
- `Contract` - Retrieves an instance of another service. | ||
- `Optional<Contract>` - Retrieves an instance, but if the service is unavailable, an empty optional is provided. | ||
- `List<Contract>` - Retrieves all available instances of a given service. | ||
- `Supplier<Contract>`, `Supplier<Optional<Contract>>`, `Supplier<List<Contract>>` - Similar to the above, but the value is only resolved when get() is called, allowing lazy evaluation for more control. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not "for more control", but to resolve:
- cyclic dependencies
- a "storm" of initializations when a service is looked up from the registry
When suppliers are not used, all instances MUST be created when the service instance is created (during construction).
If you want to use programmatic lookup, there are two main ways to access a `ServiceRegistry` instance: | ||
- Create a new ServiceRegistry instance and search for the desired service. | ||
- Inject the existing ServiceRegistry instance currently used by Helidon and retrieve services from it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should only be used for advanced use cases, as it bypasses a lot of optimizations of the service registry (i.e. build time binding of services to injection points when using the Maven plugin)
If a service performs any actions during construction or post-construction, | ||
you must first retrieve an instance from the registry to trigger its initialization. | ||
Special registry operations: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are advanced methods, which we should not even mention in documentation. Just document the methods that operate on contracts.
- `TypeName` - the same, but using Helidon abstraction of type names (may have type arguments) | ||
- `Lookup` - a full search criteria for a registry lookup | ||
== Startup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be a bit unfinished, as we need explanation of RunLevel
and how it is used in generated main class, and in io.helidon.Main
.
All options to start a Helidon application that uses service registry: | ||
- A custom Main method using `ServiceRegistryManager.start(...)` methods, or `ServiceRegistryManager.create(...)` methods | ||
- A generated `ApplicationMain` - optional feature of the Maven plugin, requires property `generateMain` to be set to `true` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also mention it uses RunLevel
actively, but via code generated class.
This is the only approach that is fully reflection free and skips lookups for injection points.
== Startup | ||
Helidon provides a Maven plugin (`io.helidon.service:helidon-service-maven-plugin`, goal `create-application`) to generate | ||
build time bindings, that can be used to start the service registry without any classpath discovery and reflection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ApplicationBinding
provides build time bindings of service providers to injection points - this is to avoid lookup at runtime.
It does NOT elimiminate classpath discovery and reflection. To avoid that you need to use the generated ApplicationMain
class.
Also classpath discovery does not mean classpath scanning (it sounds a bit bad in here) - we lookup all service.registry
and service.loader
resources from the classpath - we are not scanning for them (i.e. using only Java features that do not depend on how the classpath is created, so it works in native image as well)
Not sure how much of this should be in the docs, but it seems a bit misleading right now.
Description
Fixes #9741
Documentation
Adds user documentation for Helidon Inject