Closed hoangdt84 closed 2 years ago
Could we do this - yes, the question is should we do this.
So for example:
@Singleton
public class EngineImpl implements Engine {
@Value("${my.engine.cylinders:6}")
protected int cylinders;
...
}
Could be done today as:
import io.avaje.config.Config;
@Singleton
public class EngineImpl implements Engine {
protected int cylinders = Config.getInt("my.engine.cylinders", 6);
... and in fact we can make that a final field:
protected final int cylinders = Config.getInt("my.engine.cylinders", 6);
There is some background design thinking around this so we need to outline that here.
Both Spring and Micronaut have a @Value
and by implication they have chosen to combine "external configuration" in with "dependency injection". With avaje-inject we could also do that but we (mostly I) have desired to keep these 2 things separate for what I believe are fairly good reasons. So lets look at those reasons.
We could for implement this via source code generation with avaje-inject as:
public class EngineImpl$Proxy extends EngineImpl {
public EngineImpl$Proxy() { // match super constructor,
super();
this.cylinders = Config.getInt("my.engine.cylinders", 6);
}
}
We can see that cylinders
is only set after the super()
. Any code that tries to use cylinders
before that would get a 0 (or null with Integer etc). This is relatively obvious to experienced devs but it is a source of bugs for less experienced devs. That is, if we don't use @Value
and instead use Config.getInt()
the field is initialised just like any normal field. That is, @Value
fields have delayed initialisation and this can trip people up / be a source of bugs.
If we go from needing the configuration read and set ONCE AT STARTUP to being read each time and potentially changing (aka dynamic configuration). Then we either need to change away from using @Value
OR use the "Refreshable scope" concept.
I'd argue that when using Config.getInt()
we can just use it anywhere - field, final field, static final field, in a method (dynamic configuration). There isn't a big shift between static configuration and dynamic configuration.
As a consequence of "dynamic configuration", both Spring and Micronaut have the concept of "refreshable beans" / "refreshable context". With avaje-inject we explicitly do NOT have that concept as we'd get no value from "re-wiring the graph". Spring and Micronaut somewhat need it to re-read the @Value
configuration - I'd argue that is because they have combined "external configuration" with "dependency injection wiring" ... so any dynamic configuration need with @Value
means the bean needs to be refreshed.
That is, avaje-inject doesn't need "refreshable beans" because we expect "external dynamic configuration" to be done independently (for example, by using avaje-config). Instead, avaje-inject creates effectively immutable BeanScope.
I really like avaje-config. It's simple, extendable, and pretty mature. It was originally part of ebean orm and I extracted it into it's own project. If we supported @Value
in avaje-inject then it would have to pick a "configuration implementation" and that would be avaje-config. However, currently avaje-inject users are free to do whatever they like here with external configuration and I know that some of them choose other configuration libraries.
Q: So could we support @Value
?
A: Yes we could, it means that we need a $Proxy
(just like we do to support AOP) and the generated code would pretty much be:
public class EngineImpl$Proxy extends EngineImpl {
public EngineImpl$Proxy() { // match constructor of EngineImpl
super();
this.cylinders = Config.getInt("my.engine.cylinders", 6);
}
}
Q: So should we do this ?
Well, we could ... and then ideally document it in such a way that people think about their choices.
One of the interesting things about the code generation approach is that adding something like this can often result in ZERO added complexity to the runtime part of the library .. and instead just extra complexity is added to the code generator. I suspect that is the case here.
@rbygrave thanks a lot for your explanation. It makes perfect sense to me.
Hi,
Could you please add something like micronaut's
@Value
/@Property
(see micronaut's document)?