-
Notifications
You must be signed in to change notification settings - Fork 0
Design Rationale
Rapier’s design includes several decisions that might not make sense at a glance. The following unpacks the thinking behind some of these foundational decisions to help users better understand these design choices and build intuition about how Rapier works.
Dagger uses JSR-330: Dependency Injection for Java as the foundation of its design. In Dagger, dependencies (injection sites) and bindings (logical injection providers) are declared by the user. Dagger then generates code to resolve dependencies using the available bindings. This process is based on logical binding keys, which consist of th declared bound type and an optional JSR-330 qualifier. Each unique binding key can have zero or more dependencies, and exactly one binding. If no matching binding exists for a dependency, Dagger fails to generate the code.
Rapier extends Dagger’s model by introducing dependency injection for configuration data (e.g., @EnvironmentVariable("PORT")
). Users declare dependencies on configuration data, and Rapier generates bindings to satisfy those dependencies. To align with Dagger’s JSR-330-based model, Rapier treats its configuration source annotations (e.g., @EnvironmentVariable
) as JSR-330 qualifiers. This tight integration ensures predictable behavior and compatibility with Dagger.
Including the default value directly within configuration source annotations allows users to declare multiple dependencies on the same type for the same configuration source but with different default values. This is particularly useful in Dagger modules. For example:
@Module
public class ExampleModule {
@Provides
public Bar provideBarWithFoo123(
@EnvironmentVariable(value="FOO", defaultValue="123") Foo foo) {
return Bar.ofFoo(foo);
}
@Provides
public Bar provideQuuxWithFoo234(
@EnvironmentVariable(value="FOO", defaultValue="234") Foo foo) {
return Quux.ofFoo(foo);
}
}
Here, two distinct dependencies are defined:
@EnvironmentVariable(value="FOO", defaultValue="123") Foo
@EnvironmentVariable(value="FOO", defaultValue="234") Foo
Since the default value is included within the annotation, these bindings are considered different from each other by Dagger, enabling code generation to succeed. If the default values were placed elsewhere, for example in a separate annotation, then it would not be possible to generate multiple bindings with different default values for the same type because Dagger would fail due to duplicate bindings.
This design enables flexibility.
If users require a single default value across all dependencies, they can define it as a constant:
@Module
public class ExampleModule {
public static final String FOO_DEFAULT_VALUE = "123";
@Provides
public Bar provideBarWithFoo123(
@EnvironmentVariable(value="FOO", defaultValue=FOO_DEFAULT_VALUE) Foo foo) {
return Bar.ofFoo(foo);
}
@Provides
public Bar provideQuuxWithFoo234(
@EnvironmentVariable(value="FOO", defaultValue=FOO_DEFAULT_VALUE) Foo foo) {
return Quux.ofFoo(foo);
}
}
If users need different default values for the same type, embedding the default value in the annotation allows this use case, too.
By embedding the default value directly in the annotation, Rapier empowers users to handle a broad range of use cases, from simple configurations to complex scenarios involving multiple default values.