Improvements under the hood – InfoProviders injectable as dependencies and asynchronous Get methods
Welcome to the next episode of our short article series presenting Kentico Xperience 13 technical features, which might not be visible at first sight.
It is an honor to introduce a brand new trait of our InfoProvider data manipulation pattern – InfoProvider interfaces.
The API of InfoProviders is no longer formed by a set of static methods. Instead, we overhauled the approach and the result is that each provider now has a corresponding interface that is injectable as a dependency.
We believe this will lead to a cleaner code design and improved testability.
Moreover, asynchronous API was incorporated into the providers.
The evolution of InfoProviders
Let me illustrate the change on the SKUInfoProvider. The core provider interface consisted of the following static methods:
- ObjectQuery<SKUInfo> GetSKUs()
- SKUInfo GetSKUInfo(int skuId)
- SKUInfo GetSKUInfo(Guid skuGuid)
- void SetSKUInfo(SKUInfo skuObj)
- void DeleteSKUInfo(SKUInfo skuObj)
The methods provided for typical CRUD operations one performs with SKUInfo. If we step back, we realize the methods represent what all the providers do, more or less. Some feature extra code name based retrieval, others the site name parameter, but basically, they are all very similar.
With that in mind, it is easy to imagine what the new provider interface should look like. We will use the ISKUInfoProvider interface as an example:
- ObjectQuery<SKUInfo> Get()
- SKUInfo Get(int id)
- SKUInfo Get(Guid guid)
- void Set(SKUInfo info)
- void Delete(SKUInfo info)
We put away the wordy method naming, which repeated what was already included in the class name. So instead of calling SKUInfoProvider.GetSKUInfo(123), you can call simply ISKUInfoProvider.Get(123). It is a tiny change, which, however, significantly affects the overall feel of the API.
In addition, there are new members related to the asynchronous querying API we developed for Xperience 13
- Task<SKUInfo> GetAsync(int id, CancellationToken? cancellationToken = null)
- Task<SKUInfo> GetAsync(Guid guid, CancellationToken? cancellationToken = null)
Together with the new native async support in ObjectQuery, all the get operations can now be executed asynchronously.
Consuming the providers
The new API represents a change in how the provider methods are consumed. Instead of directly depending on static provider members in your code, dependency injection can be leveraged.
Providers now have their corresponding interfaces and the interfaces are registered in the IoC container. Therefore, providers can be injected into classes using constructor injection
public class ShoppingController
public ShoppingController(ISKUInfoProvider skuInfoProvider)
However, we realize there is a ton of existing or legacy code which is not IoC enabled and a rewrite of the code will either not happen immediately or not at all. To accommodate such code, we kept a way to simply adjust the existing code. Info objects offer a new Provider property, which points to the corresponding provider interface implementation.
To display the usage, here is an example of a replacement of the SKUInfoProvider.GetSKUInfo(123) method call
SKUInfo.Provider.Get(123) // Accesses the ISKUInfoProvider directly, without dependency injection
To avoid breaking changes in existing code, all the original static methods were kept in place with the Obsolete attribute. The obsolete message gives a hint to developers about the proper replacement using dependency injection or using the Provider property shortcut.
While we encourage the use of dependency injection and IoC container, we do not force developers to do so.
Additional provider members
We are aware the existing providers contain many other methods than just those supporting CRUD operations. There are various utility, helper or business logic members that rather belong to utility classes, business layer services or extension methods.
We intend to address those in the future as well. However, we were dealing with dozens of providers in the process of introducing the interfaces and it would not be possible for us to solve everything in the scope of a single version.
There are also some providers with a very custom API, which does not fit into the general interface pattern. We intend to improve them as one of the next steps.
InfoProviders have a corresponding interface to support dependency injection and enhance unit testing.
For existing code, the Provider property of Info object classes can be used as an easy substitution. Dependency injection is the recommended approach, though.
Asynchronous get operations are now available as well.