From bc67c451afdb08e096c82f426298d8b26f5fcb99 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Wed, 6 Nov 2024 17:19:55 +0100 Subject: [PATCH 1/4] docs: decision record about configuration injection --- .../README.md | 139 ++++++++++++++++++ docs/developer/decision-records/README.md | 1 + 2 files changed, 140 insertions(+) create mode 100644 docs/developer/decision-records/2024-11-06-configuration-injection/README.md 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 00000000000..8e5d4406a38 --- /dev/null +++ b/docs/developer/decision-records/2024-11-06-configuration-injection/README.md @@ -0,0 +1,139 @@ +# 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; +} + +public record KeyConfig(@Setting(key = "edc.iam.publickey.alias") String alias, + @Setting(key = "edc.iam.key.algorithm") String algorithm) { +} + +// alternatively: +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 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: + +- 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, errors are automatically reported in a collated way. So multiple +unresolved config values would be reported as one: + +``` +Caused by: org.eclipse.edc.boot.system.injection.EdcInjectionException: The following injected fields were not provided or could not be resolved: +Configuration value "fooBarBaz" of type class java.lang.Long (config key 'foo.bar.baz') +Configuration object 'config' of type 'class org.eclipse.edc.some.FooBarExtension$FooBarConfig' in class class org.eclipse.edc.some.FooBarExtension + at org.eclipse.edc.boot.system.DependencyGraph.of(DependencyGraph.java:136) + at org.eclipse.edc.boot.system.ExtensionLoader.loadServiceExtensions(ExtensionLoader.java:85) + at org.eclipse.edc.boot.system.runtime.BaseRuntime.createExtensions(BaseRuntime.java:164) + at org.eclipse.edc.boot.system.runtime.BaseRuntime.boot(BaseRuntime.java:92) + ... 1 more +``` diff --git a/docs/developer/decision-records/README.md b/docs/developer/decision-records/README.md index 75304625583..3338f418e94 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) From 923c3215c2c922fb3ebcece440bdffb3d72838d5 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:01:39 +0100 Subject: [PATCH 2/4] Update docs/developer/decision-records/2024-11-06-configuration-injection/README.md Co-authored-by: andrea bertagnolli --- .../2024-11-06-configuration-injection/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 8e5d4406a38..8b1db4c84b1 100644 --- a/docs/developer/decision-records/2024-11-06-configuration-injection/README.md +++ b/docs/developer/decision-records/2024-11-06-configuration-injection/README.md @@ -130,7 +130,7 @@ unresolved config values would be reported as one: ``` Caused by: org.eclipse.edc.boot.system.injection.EdcInjectionException: The following injected fields were not provided or could not be resolved: Configuration value "fooBarBaz" of type class java.lang.Long (config key 'foo.bar.baz') -Configuration object 'config' of type 'class org.eclipse.edc.some.FooBarExtension$FooBarConfig' in class class org.eclipse.edc.some.FooBarExtension +Configuration object 'config' of type 'class org.eclipse.edc.some.FooBarExtension$FooBarConfig' in class org.eclipse.edc.some.FooBarExtension at org.eclipse.edc.boot.system.DependencyGraph.of(DependencyGraph.java:136) at org.eclipse.edc.boot.system.ExtensionLoader.loadServiceExtensions(ExtensionLoader.java:85) at org.eclipse.edc.boot.system.runtime.BaseRuntime.createExtensions(BaseRuntime.java:164) From 269f8adc38e781e5c0b3315ca758764393645e8a Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Thu, 7 Nov 2024 11:57:20 +0100 Subject: [PATCH 3/4] update DR --- .../README.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) 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 index 8b1db4c84b1..1abf154f700 100644 --- a/docs/developer/decision-records/2024-11-06-configuration-injection/README.md +++ b/docs/developer/decision-records/2024-11-06-configuration-injection/README.md @@ -87,7 +87,7 @@ public record KeyConfig(@Setting(key = "edc.iam.publickey.alias") String alias, } // alternatively: -public class KeyConfig{ +public class KeyConfig { @Setting(key = "edc.iam.publickey.alias") private String alias; @@ -124,16 +124,6 @@ changed so that the `key` becomes the default value, and `description` is anothe ### Error reporting -By piggy-backing on the dependency injection mechanism, errors are automatically reported in a collated way. So multiple -unresolved config values would be reported as one: - -``` -Caused by: org.eclipse.edc.boot.system.injection.EdcInjectionException: The following injected fields were not provided or could not be resolved: -Configuration value "fooBarBaz" of type class java.lang.Long (config key 'foo.bar.baz') -Configuration object 'config' of type 'class org.eclipse.edc.some.FooBarExtension$FooBarConfig' in class org.eclipse.edc.some.FooBarExtension - at org.eclipse.edc.boot.system.DependencyGraph.of(DependencyGraph.java:136) - at org.eclipse.edc.boot.system.ExtensionLoader.loadServiceExtensions(ExtensionLoader.java:85) - at org.eclipse.edc.boot.system.runtime.BaseRuntime.createExtensions(BaseRuntime.java:164) - at org.eclipse.edc.boot.system.runtime.BaseRuntime.boot(BaseRuntime.java:92) - ... 1 more -``` +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. From 45e750a25e9525c24b2cb3afe9de62da0f4fe2f9 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Date: Fri, 8 Nov 2024 09:32:06 +0100 Subject: [PATCH 4/4] Update README.md --- .../2024-11-06-configuration-injection/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 index 1abf154f700..7552720f27d 100644 --- a/docs/developer/decision-records/2024-11-06-configuration-injection/README.md +++ b/docs/developer/decision-records/2024-11-06-configuration-injection/README.md @@ -82,11 +82,13 @@ public class SomeExtension implements ServiceExtension { 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") @@ -99,9 +101,12 @@ public class KeyConfig { } ``` -These are Java POJOs that 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: +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.