diff --git a/docs/developer/decision-records/2024-11-06-configuration-injection/README.md b/docs/developer/decision-records/2024-11-06-configuration-injection/README.md new file mode 100644 index 0000000000..7552720f27 --- /dev/null +++ b/docs/developer/decision-records/2024-11-06-configuration-injection/README.md @@ -0,0 +1,134 @@ +# Configuration injection + +## Decision + +In addition to service injection, the EDC project will support _configuration injection_ ("CI") in future releases. + +## Rationale + +In an effort to improve the ease-of-use and to lower the barrier of entry for developers we will provide a feature to +configure extensions with an annotation mechanism. Resolving configuration can result in convoluted and difficult to +read code. + +## Approach + +### Requirements + +- default values: it should be possible to provide a default value for configuration fields +- optionality: configuration fields that are not required should be `null` in case there is no config value for them +- type flexibility: at least `String`, `Integer`, `Long`, `Double` and `Boolean` must be supported +- annotation-based: the configuration injection is triggered by annotations + +Configuration values are resolved during runtime startup, more specifically during the dependency injection phase. +Technically they are another type of `InjectionPoint`. + +### Resolving and injecting plain configuration values + +Extension classes can declare fields of type `String`, `Integer`, `Long`, `Double` or `Boolean` and annotate them with +the `@Setting` annotation: + +```java +public class SomeExtension implements ServiceExtension { + + @Setting(key = "edc.iam.publickey.alias") + private String publicKeyAlias; +} +``` + +This would check if a config value `edc.iam.publickey.alias` is present on the `Config` object, and if so, assign its +value to the field. An injection error would be raised if no config value is found for `edc.iam.publickey.alias`. This +can be avoided by declaring a default value: + +```java +public class SomeExtension implements ServiceExtension { + + @Setting(key = "edc.iam.publickey.alias", defaultValue = "foobar") + private String publicKeyAlias; + + @Setting(key = "edc.some.timeout", defaultValue = "60") + private Integer someTimeout; // default value gets converted to int + + @Setting(key = "edc.some.fraction", defaultValue = "barbaz") + private Double someFraction; // runtime exception if the default value is used: "barbaz" cannot be converted to double +} +``` + +Note that default values are supplied as Strings, and an attempt is made to convert them to the appropriate type. An +injection error is raised raised if the value cannot be converted to the desired type. + +Alternatively, config values can be marked as optional: + +```java +public class SomeExtension implements ServiceExtension { + + @Setting(key = "edc.iam.publickey.alias", required = false) + private String publicKeyAlias; +} +``` + +If no config value is found for `edc.iam.publickey.alias`, then the field is `null`. + +Note that if a `defaultValue` is supplied, the `required` attribute becomes meaningless. + +### Resolving and injecting configuration objects + +In addition to supplying simple config values it should be possible to have the injection mechanism construct a _config +object_: + +```java +public class SomeExtension implements ServiceExtension { + + @Configuration + private KeyConfig keyConfig; +} + +@Settings +public record KeyConfig(@Setting(key = "edc.iam.publickey.alias") String alias, + @Setting(key = "edc.iam.key.algorithm") String algorithm) { +} + +// alternatively: +@Settings +public class KeyConfig { + + @Setting(key = "edc.iam.publickey.alias") + private String alias; + + @Setting(key = "edc.iam.key.algorithm") + private String algorithm; + + // MUST have a public default constructor! +} +``` + +These are Java POJOs that are annotated with the `@Settings` annotation and contain the actual config values. The same +principle applies as before, but the `@Setting`-annotated fields are compounded in a class or a `record`. However, +some limitations apply: + +- the field must be annotated with `@Configuration` and the class must be annotated with `@Settings` +- they can only be declared inside an extension class +- config record classes can only contain constructors where every parameter is annotated with `@Setting` +- `@Configuration`-annotated fields are optional if and only if **all** `@Setting`-annotated fields within them have the + `required = false` attribute. This optionality is _implicit_ and cannot be configured. +- `@Configuration`-annotated fields **cannot** have default values because those would have to be compile-time constant. + However, all config values _within_ can have default values. +- all `@Setting`-annotated fields of a `@Configuration` object must be optional or resolvable, either from config or via + a default value, otherwise the CI mechanism fails with an error +- if config objects are normal classes, they **must** have a public default constructor. All other constructors are + ignored by the CI mechanism +- `@Setting`-annotated fields must not be `final` +- nested config objects are not supported + +### Changes to the `autodoc` processor + +We already have a `@Setting` annotation in the `runtime-metamodel` component, which we should re-use for this. A new +attribute named `key` is added to the `@Setting` annotation. If present, it triggers the config injection mechanism. + +Currently, the description is the default `value()` attribute of the `@Setting` annotation. This should eventually be +changed so that the `key` becomes the default value, and `description` is another named attribute. + +### Error reporting + +By piggy-backing on the dependency injection mechanism, configuration injection errors are automatically reported in the +same way as service injection errors. +Reporting dependency errors will be improved in future development iterations. diff --git a/docs/developer/decision-records/README.md b/docs/developer/decision-records/README.md index 7530462558..3338f418e9 100644 --- a/docs/developer/decision-records/README.md +++ b/docs/developer/decision-records/README.md @@ -66,4 +66,5 @@ - [2024-10-06 Typed Policy Scopes through Contexts](./2024-10-05-typed-policy-context) - [2024-10-10 DAPS module deprecation](./2024-10-10-daps-deprecation) - [2024-10-24 bidirectional-data-transfers](./2024-10-24-bidirectional-data-transfers) +- [2024-11-06 configuration-injection](./2024-11-06-configuration-injection)