Apizr v4.0, Refit based resilient api client – Highlights

Apizr

The goal of Apizr v4.0 is still to get all ready to use for web api requesting, with the more resiliency we can, but without the boilerplate.

It’s based on Refit, so what we can do with Refit could be done with Apizr too.

But Apizr has more to offer, with at least:

  • Working offline with cache management
  • Handling errors with retry pattern and global catching
  • Handling request priority
  • Checking connectivity
  • Tracing http traffic
  • Handling authentication
  • Mapping model with DTO
  • Using Mediator pattern
  • Using Optional pattern

Anyway, this post is about changes only, but we published a series about Apizr already which you can find here.

If you want to know how to get started or how to configure it, please read the brand new documentation:

Read - Documentation

Anytime, feel free to browse code and samples too:

Browse - Source

LIBRAIRIES

Apizr features are still provided by several NuGet packages, depending on what you need, what you want to do, and how you want to achieve this.

Project NuGet
Apizr NuGet
Apizr.Extensions.Microsoft.DependencyInjection NuGet
Apizr.Extensions.Microsoft.Caching NuGet
Apizr.Integrations.Fusillade NuGet
Apizr.Integrations.Akavache NuGet
Apizr.Integrations.MonkeyCache NuGet
Apizr.Integrations.MediatR NuGet
Apizr.Integrations.Optional NuGet
Apizr.Integrations.AutoMapper NuGet

 

Note that Apizr.Integrations.Shiny has been discontinued as Shiny no longer provide built-in caching and logging feature anymore. Instead, Apizr now relies on MS Caching extensions, Akavache or MonkeyCache for caching feature and MS Logging extensions for logging feature. You’ll have to provide a connectivity handler if you want Apizr to check it.

LOGGER

Apizr now fully relies on Microsoft.Extensions.Logging to log it all. It means that you can now  register any compatible logging provider.

Static:

You can set logger configuration thanks to this option:

options => options.WithLoggerFactory(LoggerFactory.Create(loggingBuilder =>
{
    loggingBuilder.AddConsole();
    loggingBuilder.AddDebug();
}))

Where LoggerFactory.Create method is provided by Microsoft.Extensions.Logging.

Extended:

There’s nothing specific to do with Apizr about logger when using the extended approach. Just don’t forget to configure it like you usualy do:

loggingBuilder.AddConsole()

wherever in your app you get access to ILoggingBuilder

>> Doc article <<

logging

You can still configure logging either by attribute decoration or by fluent configuration, but the attribute name changed back to just Log

Attribute:

namespace Apizr.Sample
{
    [WebApi("https://reqres.in/"), Log(HttpTracerMode.Everything, HttpMessageParts.All, LogLevel.Information)]
    public interface IReqResService
    {
        [Get("/api/users")]
        Task<UserList> GetUsersAsync();
    }
}

Fluent:

options => options.WithLogging(HttpTracerMode.Everything, HttpMessageParts.All, LogLevel.Information)

In both cases (using attribute or fluent):

  • The first parameter called httpTracerMode introduces a tracing mode like tracing everything everytime or only when an exception occurs
  • The second parameter called  trafficVerbosity still adjust http traffic tracing verbosity
  • The last parameter called logLevels is now a parameter array. It lets you provide from 0 to 3 different MS log levels, as Apizr now needs to get corresponding log level to each internal severity:
    • Low: logs any internal and normal execution step
    • Medium: logs all missconfigured things, like asking for cache without providing any cache provider
    • High: logs errors and exceptions

Speaking of logLevels parameter array:

  • if you don’t provide any log level at all, default levels will be applied ([Low] Trace, [Medium]Information and [High]Critical)
  • if you provide only 1 log level like Information, it will be applied to all log entries ([Low] Information, [Medium] Informationand [High] Information). Up to you to catch exceptions and to log it at any level of your choice.
  • if you provide only 2 log levels like Debug and Error, the lowest will be applied to both Low and Medium ([Low] Debug, [Medium] Debugand [High] Error)
  • if you provide 3 log levels like Debug, Warning and Critical, it will be applied like you said ([Low] Debug, [Medium] Warning and [High] Critical)
  • if you provide more than 3 log levels, the lowest goes to Low, the highest to High and it will take the middle one for Medium
  • if you provide a None at some point, it will disable logging for corresponding severity

>> Doc article <<

exceptions

We can now handle request exceptions by providing  an optional onException catching action parameter:

var result = await _reqResManager.ExecuteAsync(api => api.GetUsersAsync(), clearCache: false, onException: OnGetUsersException);

...

private void OnGetUsersException(Exception ex)
{
    ...
}

>> Doc article <<

caching

First, you now get an optional boolean parameter while executing a request call. It’s called clearCache and as you can expect, it ask Apizr to clear cache right before sending the request, forcing the fetch and updating the cache. No matter of cache settings. Useful when the user try to refresh displayed data.

Then, Akavache fluent configuration options now let you adjust its main settings like application name and blob cache type. You also get acces to a new WithAkavacheCacheHandler() configuration option to get things shorter.

Finally, there’s a brand new caching intergration package with Microsoft.Extensions.Caching called Apizr.Extensions.Microsoft.Caching. It offers those using extended approach to relie on any compatible caching package. With the right package installed, you’ll find new configuration options like:

In-Memory:

options => options.WithInMemoryCacheHandler()

Distributed:

options => options.WithDistributedCacheHandler<TCacheType>()

where TCacheType could be either string or byte[], conforming to MS Extensions Distributed Cache definition.

Registering MS Extension Distributed Cache means that you have to install the distributed cache of your choice and register it too.

>> Doc article <<

Mapping

The main improvement here is that we can use data mapping into both static and extended worlds. It doesn’t matter anymore.

Also, there’s no autoMapper factory parameter anymore while executing a request.

Instead, after defining your mapping profiles and registering AutoMapper, just set your mapping types withing the generic request like:

var result = await reqResManager.ExecuteAsync<MinUser, User>((api, user) => api.CreateUser(user), minUser);

Here we give a MinUser typed object to Apizr, which will be mapped to User type just before sending it. Then Apizr will map the User typed result back to MinUser type just before returning it.

There are many more overloads so you can map objects the way you need. The same while using MediatR and/or Optional.

Finaly, we can now register AutoMapper directly with the new WithAutoMapperMappingHandler() configuration option to get things shorter.

>> Doc article <<

Mediatr

MediatR get the same upgraded features than the core ones:

  • We can now clear cached data right before sending a request thanks to the new optional clearCache  boolean parameter (see Caching)
  • We can now map data by providing types while calling any of its Send methods (see Mapping)
  • We can now handle request exceptions by providing an optional onException catching action (see Exceptions)

Also, MediatR integration now offers  brand new IApizrMediator and IApizrCrudMediator interfaces to get things shorter than ever.

>> Doc article <<

optional

Optional.Async get the same upgraded features than the core ones:

  • We can now clear cached data right before sending a request thanks to the new optional clearCache  boolean parameter (see Caching)
  • We can now map data by providing types while calling any of its Send methods (see Mapping)
  • We can now handle request exceptions by providing an optional onException catching action (see Logging)

Also, Optional.Async integration now offers  brand new IApizrOptionalMediator and IApizrCrudOptionalMediator interfaces to get things shorter than ever.

>> Doc article <<

Registry

You may want to register multiple api interfaces within the same project. Also, you may want to share some common configuration between apis without repeating yourself, but at the same time, you may need to set some specific ones for some of it. This is where the ApizrRegistry comes on stage.

Here is an example with extended approach but the same could be done with the static one:

// Some policies
var registry = new PolicyRegistry
{
    {
        "TransientHttpError", HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(new[]
        {
            TimeSpan.FromSeconds(1),
            TimeSpan.FromSeconds(5),
            TimeSpan.FromSeconds(10)
        })
    }
};
services.AddPolicyRegistry(registry);

// Apizr registration
services.AddApizr(
    registry => registry
        .AddFor<IReqResService>()
        .AddFor<IHttpBinService>(
            options => options
                .WithLogging(
                    HttpTracerMode.Everything, 
                    HttpMessageParts.All, 
                    LogLevel.Trace))
        .AddCrudFor<User, int, PagedResult<User>, IDictionary<string, object>>(
            options => options
                .WithBaseAddress("https://reqres.in/api/users"))),

    config => config
        .WithPolicyRegistry(registry)
        .WithAkavacheCacheHandler()
        .WithLogging(
            HttpTracerMode.ExceptionsOnly, 
            HttpMessageParts.ResponseAll, 
            LogLevel.Error)
);

And here is what we’re saying in this example:

  • Add a manager for IReqResService api interface into the registry, to register it into the container
  • Add a manager for IHttpBinService api interface into the registry, to register it into the container
    • Apply logging options dedicated to IHttpBinService’s manager
  • Add a manager for User entity with CRUD api interface and custom types into the registry, to register it into the container
    • Apply address option dedicated to User’s manager
  • Apply common options to all managers by:
    • Providing a policy registry
    • Providing a cache handler
    • Providing some logging settings (won’t apply to IHttpBinService’s manager as we set some specific ones)

See? It’s like doing some shopping about what we want  for all but this one, and so on…

There’s some other cool things comming with the registry.

With extended approach, everything is auto registered into your DI container, where with static approach, the returned registry instance let you populate everthing so that you could register all by yourself:

// Apizr registry
var apizrRegistry = Apizr.Create(
    registry => registry
        ...,

    config => config
        ...
);

// Container registration
apizrRegistry.Populate((type, factory) => 
    myContainer.RegistrationMethodFactory(type, factory)
);

Also, with the registry itself registered (extended: auto, static: manual), you can definitly inject/resolve IApizrRegistry and get your managed api interfaces from it, where and when you need it:

var reqResManager = apizrRegistry.GetFor<IReqResService>();

// OR

var userManager = apizrRegistry.GetCrudFor<User>();

The same for IApizrMediationRegistry if you’re using MediatR and IApizrOptionalMediationRegistry if you’re using Optional.Async

A single interface to rule them all.

conclusion

I think I’m done with highlighting Apizr v4.0 features, but you should keep an eye on the changelog to get all the changes picture.

Feel free to ask me anything on twitter, or openning an issue or a PR on GitHub.

I get some ideas for what’s next 🙂

Specialized since 2013 in cross-platform applications development for iOS, Android and Windows, using technologies such as Microsoft Xamarin and Microsoft Azure. Initially focused, since 2005, on development, then administration of Customer Relationship Management systems, mainly around solutions such as Microsoft SharePoint and Microsoft Dynamics CRM.

Related Posts

Leave a comment

eleven + seven =