Giter VIP home page Giter VIP logo

Comments (7)

shandysawyer avatar shandysawyer commented on June 12, 2024 1

Thank you for taking time to demonstrate what can be done in my scenario. This is very elegant, well thought out, and best of all works. I agree that the registration I initially posted is difficult to grasp, and is probably not doing any favors to future programmers that will inherit this code. Additionally, this translates very well amongst the various DI frameworks.

from simpleinjector.

dotnetjunkie avatar dotnetjunkie commented on June 12, 2024

Why do you think you need to register AuthenticationClient in the first place? Can't you hide all the library's logic behind a (simple) application-specified abstraction? And does the composition of the third party library's classes really have to be done by Simple Injector? Wouldn't it be simpler to just new up those classes by hand?

from simpleinjector.

shandysawyer avatar shandysawyer commented on June 12, 2024

Because I am not very good at software development apparently? I mean when you are using DI, you are under the impression all services (i.e. classes you make or classes that someone else made that you need) should be registered into the container, allowing you all the benefits IoC provides. So why should AuthenticationClient and ForceClient be any different compared to say this answer? What would you do in this scenario?

from simpleinjector.

dotnetjunkie avatar dotnetjunkie commented on June 12, 2024

Software development is hard; there's no single way to do things and there are many different philosophies and schools which one can follow. As you already noticed (by reading my blog posts and SO answers), I'm pretty opinionated. Even when it comes to runtime data there are multiple schools of thought, which is something I dove deeper into in the past in this series of blog posts.

When it comes to working with third-party components, I often try to hide them behind application-tailored abstractions. This is something that I learned from the Dependency Inversion Principle. But it's not just about following some arbitrary rules, I've found that this actually leads to reduced overall complexity in my applications. That said, it's not always easy to come up with an abstraction that works great for your application. I love to help you with this. But in order to do so, I need a bit more context.

In your first code example, you demonstrated how this third-party library can be used. But can you show me some typical examples of how you use this code in your application?

from simpleinjector.

shandysawyer avatar shandysawyer commented on June 12, 2024

I am using this third party library to query data from Salesforce. My plan was to encapsulate in a repository layer like so:

public class SalesforceRepository : ISalesforceRepository
{
    private Func<IForceClient> _forceClientFactory;
    public SalesforceRepository(Func<IForceClient> forceClientFactory) =>
        _forceClientFactory = forceClientFactory;
        
    public async Task<List<Things>> FindAllThingsAsync()
    {
        using (var client = _forceClientFactory())
        {
            var query = $@"SELECT Id FROM Table";
            var result = await client.QueryAsync<Things>(query);
            return result.Records;
        }
    }
    // other repo methods
}

And then injecting this repository layer in my business classes so that I can retrieve the data and do things with it:

public class MyBusinessClass
{
    private ISalesforceRepository _salesforceRepository;
    public MyBusinessClass(ISalesforceRepository salesforceRepository) =>
        _salesforceRepository = salesforceRepository;
        
    public async Task DoStuff()
    {
        var data = await _salesforceRepository.FindAllThingsAsync();
        // do stuff with the data
    }
}

Side note: I am currently working in .NET 4.8 Framework for this project.

from simpleinjector.

dotnetjunkie avatar dotnetjunkie commented on June 12, 2024

Thank you for this addition. This gives me a decent idea of what you're aiming at. In your case, I'd suggest the following.

Introduce an IForceClientFactory abstraction, with a single factory method returning a Task<IForceClient>:

public interface IForceClientFactory
{
	Task<IForceClient> CreateClient();
}

By making the CreateClient method async, it allows you to lazily authenticate the AuthenticationClient (will show an implementation shortly). This means you will have a small change to your SalesforceRepository:

public class SalesforceRepository : ISalesforceRepository
{
    private IForceClientFactory _factory;
    public SalesforceRepository(IForceClientFactory factory) =>
        _factory = factory;
        
    public async Task<List<Things>> FindAllThingsAsync()
    {
        // NOTE: Here you must await the CreateClient method
        using (var client = await _factory.CreateClient())
        {
            var query = $@"SELECT Id FROM Table";
            var result = await client.QueryAsync<Things>(query);
            return result.Records;
        }
    }
    // other repo methods
}

The interesting part of the solution I'm proposing here is inside the IForceClientFactory implementation:

public sealed class ForceAuthenticationCredentials { ... }

// This class caches AuthenticationClient. Should be register as scoped.
public sealed class ForceClientFactory : IForceClientFactory, IDisposable
{
    private readonly ForceAuthenticationCredentials _credentials;
    private readonly AuthenticationClient _auth;
    
    private bool _authenticated;
    
    public ForceClientFactory(ForceAuthenticationCredentials credentials)
    {
        _credentials = credentials ?? throw new ArgumentNullException();
        _auth = new AuthenticationClient();
    }
    
    public async Task<IForceClient> CreateClient()
    {
        var auth = await GetAuthenticatedClient();
        
        return new ForceClient(
            auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
    }
    
    private async Task<AuthenticationClient> GetAuthenticatedClient()
    {
        if (!_authenticated)
        {
            await _auth.UsernamePasswordAsync(
                _credentials.ConsumerKey,
                _credentials.ConsumerSecret,
                _credentials.UserName,
                _credentials.Password + _credentials.SecurityToken);
        
            _authenticated = true;
        }
        
        return _auth;
    }
    
    public void Dispose() => _auth.Dispose();
}

Here a few interesting things are happening:

  • This ForceClientFactory itself contains the logic authenticate
  • It remembers whether it is authenticated and this information is cached
  • As it stores the AuthenticationClient, it is responsible for disposing of it.
  • Any subsequent call to CreateClient would be fast, as the call to UsernamePasswordAsync is skipped.
  • This class encapsulates what you we trying to implement inside your registrations.

While it is possible to completely remove ForceClientFactory and have all this logic crammed inside delegates that are registered inside of Simple Injector (or any other Container), I believe you'll find the existence of the ForceClientFactory to provide an easier to grasp solution. If you like, you can place the ForceClientFactory inside your Composition Root; in other words, close to the place where you register Simple Injector. This keeps the code close to the place you initially wanted it to be.

The only part missing from the equation now is of course the registrations. Considering all classes you posted, the following set of Simple Injector registrations would probably do the trick:

container.RegisterInstance(new ForceAuthenticationCredentials(...));
container.Register<IForceClientFactory, ForceClientFactory>(Lifestyle.Scoped);
container.Register<ISalesforceRepository, SalesforceRepository>();
container.Register<MyBusinessClass>();

One last note: Considering that the SalesforceRepository is about communicating with Salesforce, you could consider the IForceClientFactory optional. You could SalesforceRepository depend directly on the ForceClientFactory implementation instead. Doing that, however, does mean that you won't be able to place the implementation inside the Composition Root any longer.

I hope this helps.

from simpleinjector.

dotnetjunkie avatar dotnetjunkie commented on June 12, 2024

You're very welcome.

from simpleinjector.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.