I wrote a small client for a project I'm working on that allows me to connect to various implementations of the application API. The client turned out to be so effortless and versatile that I decided to generalize it and publish it as its own library. I would like some feedback on the design and usefulness of the pattern and the library - specifically: Is it even necessary given the widespread use of VPNs? Is it over-engineered? A wrong approach to begin with? What specific recommendations do you have to improve it?

You can get the code for AdaptiveClient from Github. You can also get a simple colsole app demo, or an end-to-end working example application. A nuget package is also available.

public partial class MainWindow : Window
{
    private IAdaptiveClient<IUsersService> client;

    public MainWindow(IAdaptiveClient<IUsersService> client)
    {
        this.client = client;
    }

    public async Task<IActionResult> Login(int userID)
    {
        // AdaptiveClient will use the best server available at the time 
        // the request is made. 
        // Server may be SQL, WCF, REST, etc. - your application does not need to know or care.
        // If the request fails AdaptiveClient will begin an orderly fall back to other 
        // servers that can handle the request regardless of platform or protocol:

        User user = await client.CallAsync(x => x.GetUser(userID));
    }
}

What AdaptiveClient does

Rather than make a service call directly to a specific server or type of server you make a call using AdaptiveClient instead. AdaptiveClient will attempt to execute the call using the best available server. If the call fails AdaptiveClient will make successive attempts, each time falling back to other servers of the same type or other types.

For example, a mobile user who is on-site and connected to a local area network will enjoy the performance of an in-process connection directly to the database server. If the user tries to reconnect from a remote location AdaptiveClient will attempt a LAN connection again but will fall back to a WebAPI server (for example) when the LAN connection fails. Should the WebAPI connection fail, AdaptiveClient may attempt to connect to other WebAPI servers, a WCF server, or any other server as configured.

Who will benefit from using it

  • AdaptiveClient is ideally targeted to organizations that need to give local users access to their APIs over a local area network but who also wish to expose their APIs to remote users.
  • Developers who want to implement retry and/or fall back logic when making service calls.

How it works

AdaptiveClient is a design pattern that leverages n-tier architecture and a dependency injection container. The client and utility classes assist you in implementing the pattern. The current version includes a client implementation based on Autofac. You should be able to implement similar functionality using other DI containers.

All of you who have used a dependency injection container are familiar with the concept of registration:

builder.RegisterType<UsersService>().As<IUsersService>();

In the line above an implementation of an interface is associated with the interface so the container knows what implementation to return when the interface is resolved. AdaptiveClient works in the same way but the registrations are slightly different. Also, AdaptiveClient uses a small configuration file to maintain metadata about EndPointsConfigurations. An EndPointConfiguration (a.k.a EndPoint for short) is like a connection string or a URL but it includes some extra properties that are useful:

  • Name: Name of the EndPoint: DevServer01, QASloth02, etc.
  • API_Name: Name of the application or API exposed by the EndPoint: OurCompanyApp, xyz.com, etc. NOT the name of a contract or interface.
  • Preference: Number that allows ClientFactory to rank this EndPoint. Lower numbers are ranked higher (more preferred).
  • EndPointType: May be one of the following: InProcess, HTTP, WCF, ESB. Assists ClientFactory in determining if the EndPoint is alive. Multiple EndPointConfigurations of the same EndPointType may be defined for an API_Name.
  • ConnectionString: Valid connection string OR URL if pointing to a HTTP server.
  • Parameters: Not used at this time.
  • IsActive: Set this value to false to prevent using this EndPointConfiguration.

When you register the components of your application with your DI container you also register your EndPoints with Adatapive client. You register the EndPoints with a couple lines of code like this:

RegistrationHelper registrationHelper = new RegistrationHelper(builder);
IEnumerable<IEndPointConfiguration> endPoints = ... // read endpoints from config file 
registrationHelper.RegisterEndPoints(endPoints);

Each EndPoint in your config file has an API Name (API Name is just a string of your choosing). When you register your interfaces you associate them with an API Name - which effectively associates them with the collection of EndPoints that expose them:

// psudocode
Interfaces (IUsersService, IOrdersService) are registered with API Name `MyAppAPI` which is registered with EndPoints (ProdSQLServer01, ProdWebServer01, ProdWebServer02, ProdWCFServer01)
Interfaces(IChargeUser, IRefundUser) are registered with API Name `BankAPI` which is registered with EndPoints (BankServer01, BankServer02)

Each EndPoint has an EndPointType (WebAPI, REST, WCF, etc) which helps AdaptiveClient resolve a specific client (implementation of your interface) for use with a specific EndPoint.

For example you may register UsersServiceRESTClient, UsersServiceWCFClient, and UsersServiceWebAPIClient with IUsersService. When you request an implementation of IUsersService and AdaptiveClient finds a working EndPoint of type HTTP, AdaptiveClient will know to return the UsersServiceRESTClient.

AdaptiveClient has a helper class for Autofac that allows you to register your clients with just a few lines of code:

string apiName = "OurCompanyAPI";
// client that communicates directly with the database (the service itself)
registrationHelper.Register<MyApp.Services.UsersService, IUsersService>(EndPointType.InProcess, apiName);
// WebAPI client 
registrationHelper.Register<MyApp.WebAPIClient.UsersClient, IUsersService>(EndPointType.HTTP, apiName);
// WCF client 
registrationHelper.Register<MyApp.WCFClient.UsersClient, IUsersService>(EndPointType.WCF, apiName);

Now that you know how the components of your application are registered with AdaptiveClient, the process of resolving a client as explained in the diagram below should make sense.

How AdaptiveClient resolves a client from start to finish:

alt an image

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.