-
Enhancement
-
Resolution: Done
-
Major
-
None
-
None
A common pattern for subsystem resources is use a SimpleAttributeDefinition or StringListAttributeDefinition in conjunction with a capability reference.
For these attributes, typical runtime workflow is:
- Resolving the string value(s) from the resource model
- Potentially extract other segments comprising the capability reference from the path address and/or the resource model
- Constructing a service name using the capability name and dynamic segments for use with a ServiceBuilder.
e.g.
// Given some required capability: static final String FOO = "foo"; // ... and a resource capability requiring the above capability RuntimeCapability<Void> CAPABILITY = RuntimeCapability.Builder.of("bar").build(); // ... and an attribute referencing the "foo" capability AttributeDefinition ATTRIBUTE = new SimpleAttributeDefinitionBuilder("foo-ref", ModelType.STRING) .setRequired(false) .setAllowExpression(false) .setCapabilityReference(CAPABILITY, FOO) .build(); // Runtime operation handling typically involves: String value = ATTRIBUTE.resolveModelAttribute(context, model).asStringOrNull(); CapabilityServiceBuilder<?> builder = target.addService(); Supplier<Foo> dependency = (value != null) ? builder.requires(context.getCapabilityServiceName(FOO, value)) : null;
WFCORE-6347 added a new wildfly-subsystem module (derived from org.wildfly:wildfly-clustering-common) that introduced typed CapabilityReferenceRecorder, SeviceDescriptor, and ServiceDependency interfaces. ServiceDescriptor encapsulates both the name and type of a capability dependency, but also the number of dynamic segments required to construct a resolved name or service name. The typed CapabilityReferenceRecorder use a builder that forces name resolution compliance with the required ServiceDescriptor.
e.g.
// Given the following dependency description: UnaryServiceDescriptor<Foo> FOO = UnaryServiceDescriptor.of("foo", Foo.class); // And a resource capability requiring Foo RuntimeCapability<Void> CAPABILITY = RuntimeCapability.Builder.of("bar").build(); // ... and an attribute referencing the "foo" capability AttributeDefinition ATTRIBUTE = new SimpleAttributeDefinitionBuilder("foo-ref", ModelType.STRING) .setRequired(false) .setAllowExpression(false) .setCapabilityReference(CapabilityReferenceRecorder.builder(CAPABILITY, FOO).build()) .build(); // Runtime operation handling became more structured while being less verbose: String value = ATTRIBUTE.resolveModelAttribute(context, model).asStringOrNull(); CapabilityServiceBuilder<?> builder = target.addService(); Supplier<Foo> dependency = (value != null) ? builder.requires(FOO, value) : null; // Or dependency could be self-contained, and applied to any service builder ServiceDependency<Foo> foo = ServiceDependency.of(FOO, value); foo.accept(builder);
However, this still means that we go from a typed capability reference to a de-typed attribute definition, used to read a string reference name, from which the typed dependency can be constructed. There is an opportunity to simplify this further.
During capability reference recording, we already use the CapabilityReferenceRecorder associated with an attribute to resolve requirement names. Why not use the same mechanism during runtime operation handling?
We can use our typed capability reference to create a typed AttributeDefinition. Rather than resolve a simple attribute value, we can resolve the service dependency directly.
e.g.
// Given the following dependency description: UnaryServiceDescriptor<Foo> FOO = UnaryServiceDescriptor.of("foo", Foo.class); // And a resource providing some capability that requires Foo: RuntimeCapability<Void> CAPABILITY = RuntimeCapability.Builder.of("bar").build(); // And a resource attribute that references Foo: CapabilityReferenceAttributeDefinition<Foo> ATTRIBUTE = CapabilityReferenceAttributeDefinition.builder("foo-ref").requires(CAPABILITY, FOO).build(); // We can simplify runtime handling such that we eliminate the intermediate de-typed steps from the previous examples: CapabilityServiceBuilder<?> builder = target.addService(); Supplier<Foo> foo = builder.requires(ATTRIBUTE.resolve(context, model)); // or using a self-contained dependency: ServiceDependency<Foo> dependency = ATTRIBUTE.resolve(context, model);
The best part is that the runtime handling is exactly the same for any capability reference, no matter now many dynamic segments a given capability has, or even from where its ancestor segments originate (e.g. other attributes, or in the path) since this complexity is already encapsulated by the CapabilityReferenceRecorder. We also avoid boilerplate code for dealing with undefined attribute values since CapabilityReferenceAttributeDefinition.resolve(...) will simply return a Supplier that evaluates to null if the attribute is optional. Additionally, a specific AttributeDefinition for capability references ensures that we avoid common pitfalls, like allowing expressions or using "magic" default values.