Samstag, 23. Dezember 2017

PantherDI - Part 6: Metadata, Factories

Metadata


Apart from the metadata that PantherDI will hold internally, in this blogpost I will implement functionality which allows custom metadata to be registered for a type.
This metadata can then be used to further determine which resolved item will be instantiated for example when creating a dynamic plugin system.

Internal storage

Custom metadata are stored in a key-value store for each type where the key is a string and the value is an object supplied by the registration. It will not be possible to supply different metadata for each registered factory, the metadata will be attached to the registered type (=the registration) itself. Also it will be possible to request metadata which is not registered for a given type or to have metadata registered which is not be retrieved.
In other words: Registering and retrieving metadata is completely optional.

Registration by reflection

Metadata can be either registered manually (by adding it to the IRegistration) or - if reflection is used - registered with the means of attributes. Since the manual registration is rather trivial, I will mainly go over the registration via attributes as there are three ways to add metadata to a type: Attributing the type, marking a static property or field as metadata and metadata inheritance.
Both ways can be mixed, however a key must be unique for a type. PantherDI will throw an exception when a key is present more than once for the metadata of a single type. The only case where the presence of a metadata attribute with an already known key is when the key is added via metadata inheritance.

Attributing the type

The attribute Metadata can be used on a registered type to add an entry to the metadata of its registration. This attribute expects the key as well as the value of the entry. You can add as many of these attributes to a type. It is also possible to create your own attribute that inherits from the MetadataAttribute so the key, which is a magic string does not need to be supplied manually.

Attributing a static property or field

If the MetadataAttribute is added to a static property or field, the value of this property or field is read at the time of resolving the metadata (more on that later). While this allows the metadata to be changed during the lifetime of the container, it is advised to set the metadata statically and never change it afterwards.

When supplying metadata through a field or property, the key of the MetadataAttribute can be ommitted. This will cause PantherDI to use the name of the property or field as key for the entry. However if a key is supplied in the attribute, it is used instead of the property or field name.

Metadata supplied as fields or properties will always override metadata supplied on the type itself.

Inheriting metadata

Metadata on a type will also be set on types it inherits from. These inherited entries can be overriden on the derived type.
Thus the normal inheritance mechanism can't be used: PantherDI needs to know which entry comes from which type, so overriding is done correctly.

Retrieval

To retrieve the metadata of a type, Lazy<T, TMetadata> needs to be resolved. PantherDI will create a new instance of TMetadata and fill all its public, non readoly properties with the metadata.
The rules for doing so are as follows:

  • If the property is decorated with a MetadataAttribute, the key from it will be used to retrieve the entry. This is why the MetadataAttribute can be instantiated without value. However if a value is supplied, it will be ignored.
  • If the attribute is missing, the name of the property is used as a key
  • If the metadata of the type contains an entry which is not reflected in a property of TMetadata, it is simply ignored.
  • If a property of TMetadata can't be matched to an entry in the metadata, its setter will never be called.

This allows the consumer of metadata to easily specify which entries are of interest.

Creating your own MetadataAttribute types

If you create a type that inherits from MetadataAttribute, PantherDI will also use it to gather metadata or mark properties to be filled with metadata. That way you can create your own attribute and use that instead of the key.

Missing registration of instances via properties and fields

During implementation of registration of instances I forgot to implement registration via static properties and fields. Now that metadata can be registered in a similar way, I will fix that bug, using the same mechanisms to detect these an assembly is scanned. 
The field or property needs to be decorated with the ContractAttribute and just like decorating a type, the set of ContractAttributes will determine the fulfilled contracts of the registered instance.
If a contract attribute is present without a contract, the name of the property and its type will be used.
Instances registered in that manner can not have any metadata (for PantherDI would be unable to distinguish whether you want the property or field to be the value of a metadata entry or whether the metadata should be annotating the property or field).

To enable registration of instances with their own contracts, factories now can have a set of fulfilled contracts too. These contracts are only fulfilled when the respective factory is used to create the instance.

FactoryAttribute for registration of static methods as factories

Instead of just using the constructors, PantherDI supports registration of static methods as factories. If a static method is annotated with the FactoryAttribute, it will be considered a factory for the return type. ContractAttributes can be used to further annotate the method and add contracts that will only be considered as fulfilled when the factory is used.



Code

The code after implementing this blogpost can be found on GitHub under the Tag v1.2.0: Link

Glossary

This chapter contains a cumulative list of terms that are used within this project.

Container
The central element of PantherDI. 
It offers a way to retrieve instances of objects with all their transitive dependencies resolved.

Registration
Information about a single type, enabling a container to create it, resolve its dependencies and use it as dependency.

Contract
Any object that serves as the promise that a registration can be used as dependency. A registration can be retrieved only by contract, so the consumer does not need to know its actual type.

Factory
Basically the object representation of a function which will create an instance of a registered type.

Dependency (of a Factory or Provider, see below)
A single dependency of a factory is the object representation of one a function parameter. It includes meta-information, for example the contracts the instance passed needs to fulfil.

Provider
Created from a factory by resolving all the dependencies that can be resolved using the given registrations.

Knowledge Base (KB)
The knowledge base contains all providers that can be used by the container.

Resolver
An algorithm that utilizes the knowledge base to return all providers for a given dependency.
It may create new providers from the ones given in the knowledge base, depending on its purpose.

Processing
Often means converting a Factory into a Provider by resolving all its dependencies that can be resolved.

Keine Kommentare:

Kommentar posten