4

BACKGROUND

I need to implement an integration with Dropbox using its REST APIs. I don't want to reinvent the wheel so I decided to use Apex Dropbox API Framework. Although it's a bit outdated (it works with v1 Dropbox API), I found the framework to be really useful.

QUESTION

In the examples - Dropbox Sample Apps, I found that both the DropboxTestHarnessController.cls and DropboxAccountController.cls use the same routine to get a valid connector and dropbox client. Below are some part from those classes:

DropboxTestHarnessController.cls

private void init()
{
    if (connectorService != null)
    {
        String msg = connectorService.validateConnector();
        validConnector = String.isEmpty(msg);
        if (!validConnector)
        {
            error(msg);
        }
        else
        {
            info('Successful authentication. The \'' + connectorService.Connector.Name + '\' connector is being used for authentication.');
        }
    }
    else
    {
        error('Dropbox connector not found.');
    }
}

DropboxAccountController.cls

private void init()
{
    if (connectorService != null)
    {
        String msg = connectorService.validateConnector();
        validConnector = String.isEmpty(msg);
        if (!validConnector)
        {
            error(msg);
        }
    }
    else
    {
        error('Dropbox connector not found.');
    }
}

Both these pieces are nearly the same. I understand that the code given in those classes are for example purposes. Whereas my concern is how I can avoid code duplication in my project. Let's say I need to use a dropbox service from 4 apex controllers and repeating the same initialization routine doesn't look good.

I was thinking of creating another service layer that would be responsible for initialization, but I am not 100% sure that it is a correct approach. Again I don't want to reinvent the wheel here and I believe there should be a standard solution (pattern) to this kind of problem, yet I haven't found anything on the Internet that explains it clearly.

UPDATE

To be more specific on the question, let's see how one would use Apex Dropbox API Framework in their code.

public class Example1
{
    ConnectorService connectorService;
    public ffhttp_Dropbox client;

    public Example1()
    {
        conn = getConnectorService();
        init();
    }

    private void init()
    {
        // Error hadling dislpayed in the example above
        if (connectorService != null)
        {
            String msg = connectorService.validateConnector();
            validConnector = String.isEmpty(msg);
            if (!validConnector)
            {
                // Error 
                // ...
            }
        }
        else
        {
            // Error 
            // ...
        }
    }

    private getConnectorService() 
    {
        // This is omitted for brevity
        // ....
        // return ...;
    }

    public List<ffhttp_DropboxModelFile> getFiles()
    {
        // Exception handling  is omitted for brevity
        ffhttp_Dropbox client = getDropboxClient();
        ffhttp_DropboxFiles files = client.files();
        ffhttp_DropboxFiles.FilesListRequest filesListRequest = files.filesListRequest();
        
        return (ffhttp_DropboxModelFile) fileslistRequest.execute();
    }
    
    private ffhttp_Dropbox getDropboxClient()
    {
        if (client == null)
        {
            ffhttp_Client.AccessTokenCredentials credentials = new ffhttp_Client.AccessTokenCredentials(connectorService.connector.TokenType__c, 
                                                                                  connectorService.connector.AccessToken__c);
            client = new ffhttp_Dropbox(credentials);
        }
        return client;
    }

}

Now assume one wants to use the Dropbox service in another class "Example2", and then in "Example3" and so on. With the current approach methods init(), getConnectorService() and getDropboxClient() would be copy-pasted into those new classes. This is a kind of situation I want to avoid.

2
  • 2
    This a design question that applies to pretty much any Object Oriented language and the starting point often a Composition vs. Inheritance: How to Choose? debate. For this case I'd probably go with inheritance.
    – Keith C
    Oct 20 '18 at 13:32
  • That's a great article which explains the fundamental concepts to answer my question. Thanks for sharing, @KeithC.
    – Eduard
    Oct 22 '18 at 8:10
1

In Apex code, it's usually easier to use a base class, and then extend them as necessary. This is exactly how I build my AWS service framework; all of the core methods for handling errors and authentication are dealt with in a common class, and then extensions are built for specific purposes. For example, to get a list of AWS S3 buckets, we have this code:

public class AWSS3_GetService extends AWS {
    public override void init() {
        endpoint = new Url('https://s3.amazonaws.com/');
        resource = '/';
        region = 'us-east-1';
        service = 's3';
        accessKey = 'my-key-here';
        method = HttpMethod.XGET;
        //  Remember to set "payload" here if you need to specify a body
        //  payload = Blob.valueOf('some-text-i-want-to-send');
        //  This method helps prevent leaking secret key, 
        //  as it is never serialized
        createSigningKey('my-secret-key-here');
   }
   public String[] getBuckets() {
       HttpResponse response = sendRequest();
       String[] results = new String[0];
       // Read response XML; if we get this far, no exception happened
       // This code was omitted for brevity
       return results;
   }
}

This implementation deduplicates about 200 lines of code for each time I need a new method into AWS. There are some minor problems with the actual AWS implementation, but I'd invite you to look at the design. By using abstract and/or virtual methods in a parent class, the child classes can focus on implementing the parts that are different, and by providing protected methods, you can implement the core logic parts and avoid duplication.


Update

As a more specific example, the code you might start with might look like this:

public abstract class DropboxBaseApi {
        protected ConnectorService connectorService;
        protected ffhttp_Dropbox client;
        Boolean init() {
            connectorService = getConnectorService();
            if (connectorService != null) {
                String msg = connectorService.validateConnector();
                validConnector = String.isEmpty(msg);
                if (!validConnector) {
                    error(msg);
                } else {
                    info('Successful authentication. The \'' + connectorService.Connector.Name + '\' connector is being used for authentication.');
                    return true;
                }
            } else {
                error('Dropbox connector not found.');
            }
            return false;
        }
        // Other base methods here
    }

From there, each class would extend the base class:

public class Example1 extends DropboxBaseApi {
    public List<ffhttp_DropboxModelFile> getFiles()
        if(init()) {
            // do more logic here
        }
    }
}

I've taken some liberties here, because I haven't worked with this library, but hopefully it should demonstrate how you'd design the code.

3
  • Thanks for your answer. Your approach here is quite similar to what ffhttp-dropbox sdk does - wrap bare HTTP requests into user-friendly interface. My question though was targeting the actual use of the sdk's methods especially when the API requires access tokens. In your init() method you specify the accessKey to be able to make callouts. In a case when one wants to allow to use the getBuckets() with different accessKeys (when each user in an org authenticates with their own credentials), how would it be implemented?
    – Eduard
    Oct 22 '18 at 10:44
  • I updated my question and gave a specific example. Sorry for confusion.
    – Eduard
    Oct 22 '18 at 12:10
  • @Eduard Does my edit help?
    – sfdcfox
    Oct 22 '18 at 13:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.