Skip to content

Instantly share code, notes, and snippets.

@kevinohara80
Created January 10, 2014 16:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kevinohara80/8357088 to your computer and use it in GitHub Desktop.
Save kevinohara80/8357088 to your computer and use it in GitHub Desktop.
nforce 0.7.0 proposal

nforce 0.7.0

Overview / tl;dr

nforce 0.7.0's main goal is to introduce a new plugin system. In addition, I am proposing some breaking API changes that I believe will make nforce more flexible and make plugin authoring far easier.

I'm looking for feedback so please comment if you have anything to add.

Plugin System

nforce 0.7.0 will introduce a plugin system. This will enables developers to easily extend nforce with custom functionality and let nforce be more modular.

So why is this a good idea?

This will help prevent nforce from becoming a monolithic beast that implements all current and future API's provided by Salesforce. Also, breaking up functionality into modules is sort of the node way. Modules in node tend to be small and applications are built by combining only the functionality that you need using the vast module ecosystem. Today, nforce handles the base REST API very well (auth, crud, queries, etc). When you start looking at adding support for the Tooling API, Chatter, and future apis, cramming all of this functionality into nforce (core) would turn nforce into a huge project. I believe Chatter itself might be bigger than the core nforce package!

A plugin system will allow you to easily extend nforce with just the functionality you need. I already have repos for nforce-tooling, nforce-chatter, nforce-canvas, and nforce-express. They're just placeholders, but Jeff Douglas is already hacking on the Tooling API plugin and I know Chris Bland has done some work here that he could contribute.

Here is an example of the api for building a module. As you can see, it's pretty simple.

Proposed API changes

Like most plugin authors, I have my regrets about the initial implementation. In particular, there are two things that I don't like.

  • API methods - variable arguments: Single-user mode let's you omit the oauth argument in API method calls because it's cached locally in the connection object. This forces you to juggle the arguments passed in and each function has to do this.
  • sObject (Record.js) class: This was a bad implementation all around. Javascript getters/setters lead to tough inspection in console.log and that object functions are not on the prototype.

API Change Proposal 1 - API Method Arguments

The initial implementation should have implemented all api methods the same way...the node way.

  1. Limit the methods to two arguments
  2. Accept a hash as the first argument
  3. Second argument is the callback, unless no hash is provided meaning the only argument is the callback

So updating nforce to leverage this means breaking most of the api. These changes would likely be pretty trivial though. This would mean changing a query from this...

org.query('SELECT Id FROM Account', oauth, function(err, records) {
  // code
});

to this...

org.query({ query: 'SELECT Id FROM Account', oauth: oauth }, function(err, records {
  //code
});

or single user mode...

org.query({ query: 'SELECT Id FROM Account' }, function(err, records {
  //code
});

Internally, this makes things much easier to reason about. We could easilty create a utility function that could parse the options and find the oauth object. Currently, each method with variable arguments needs to implement something like this...

Connection.protoype.insert = function(data, oauth, cb) {
  if(this.mode === 'single') {
    var args = Array.prototype.slice.call(arguments);
    oauth = this.oauth;
    if(args.length == 2) callback = args[1];
  }
  
  if(!callback) callback = function(){}
    
  // rest of implementation
  
});

Knowing that the arguments will be either 1 or 2 arguments make it so that we can create a utility method that handles it all for you...

Connection.protoype.insert = function(data, oauth, cb) {
  var opts = this.getOpts(arguments)
  //  {
  //    "sobject": [Object],
  //    "oauth": [Object],
  //    "callback": [Function]
  //  }
});

This will make plugin authoring much easier because we can provide the utility functions to handle most of what you need.

API Change Proposal 2 - sObject Class (Record.js) changes

I'd like to update the sObject (Record.js) class to implement explicit set() and get() functions to set properties on an sObject. The reason why we do this is so that the class can track changes to sObjects and send only the changed attributes in an update call. Today, this is done with javascript getters and setters...which work fine...but make it difficult to console.log since the values show as [Getter/Setter].

I'd like the API to feel very similar to how you interact with a model in Backbone.js. They're essentially doing the same thing. I plan on adding a bunch of utility methods to make working with the sObjects easier as well.

In addition, there are currently some methods that should be moved to the prototype. These also show up in console.log statements.

Here are some example on how this API would look.

var acc = nforce.createSObject('Account', { Name: 'Salesforce.com' });

acc.set({ 
  Industry: 'Technology',
  Employees: 25
});

console.log(acc.get('Name'));     // => "Salesforce.com"
console.log(acc.get('Industry')); // => "Technology"
console.log(acc.toJSON());        // => { "Name": "Salesforce.com", "Industry": "Cloud Stuff", "Employees": 25 }

example showing change caching...

var query = 'SELECT Id, Industry, Employees FROM Account WHERE Name = "Salesforce.com" LIMIT 1';

org.query({ query: query }, function(err, recs) {
  var acc = recs[0];
  
  console.log(acc.changed());    // => {}
  console.log(acc.hasChanged()); // => false
  
  acc.set('Industry', 'Cloud Stuff');
  
  console.log(acc.hasChanged());           // => true
  console.log(acc.hasChanged('Industry')); // => true
  console.log(acc.hasChanged('Name'));     // => false
  console.log(acc.changed());              // => { "Industry": "Cloud Stuff" }
  console.log(acc.previous());             // => { "Industry": "Technology" }
  console.log(acc.previous('Industry');    // => "Technology" 
  
  console.log(account.toJSON()); // => { "Name": "Salesforce.com", "Industry": "Cloud Stuff", "Employees": 25 }
 
});

Supporting This

If we implement these changes, I plan on keeping 0.6 around for a while and releasing on that as well. That way, we can give users plenty of time to update to >= 0.7.0. I would also plan on creating an upgrade guide.

Conclusion

I feel like this is the right time to make some of these changes. I want to get this to 1.0 in the near future but before that happens, I feel like these API changes are necessary.

I'd like your feedback. Please comment with thoughts, suggestions, concerns, or even other api changes that you'd like to see made.

Thanks, KO

@joshbirk
Copy link

Sounds like a good approach. I never met a plugin system I didn't like. OK, I have - but never one I liked less than simply not having one at all.

@kevinohara80
Copy link
Author

Haha, I agree. I just think that plugins are the way to go especially since the Tooling API and Chatter API's are so large.

@jeffdonthemic
Copy link

I think the proposed changes for a plugin system would make my life easier. I'm writing WAY TOO MUCH boilerplate code for each method. If I could just delegate the validation of config, oauth, etc to core utility methods I would be a happy camper. The change in variable arguments isn't that important to me but it is the node way so that's good. The proposed changes to Record.js would be nice as it is a PITA to see what is being returned sometimes plus with a Java background getters/setters just feel warm and fuzzy. If there is a voting system, I vote "yes". If not, I still vote "yes" but you can disregard it.

@monscamus
Copy link

Definitely agree with the uniform calls approach - can you make sure that the sample/example projects are up to date though - as this is very misleading if you npm the library currently. Otherwise it's a really solid tool, thanks KO.

@smithl
Copy link

smithl commented Feb 5, 2014

+1

@thegogz
Copy link

thegogz commented Feb 10, 2014

Seems like the logical and correct approach to me.

@dexxo
Copy link

dexxo commented Mar 6, 2014

hey how i set up a proxy settings with you library?

@bitbay
Copy link

bitbay commented Mar 29, 2014

Dev Team, as far i see the new plugin system is focused on the extension of nforce in the way it communicates with salesforce (and its objects, retrieving/modifying records of the CMS), but not the authentication process. Right now it does support oauth2 out-of-the-box, but what about SAML?
I used the module successfully with the "OAuth 2.0 Refresh Token Flow" and "OAuth 2.0 Web Server Authentication Flow" mentioned on the help.salesforce.com site, and now it's time to update to a Single Sign-On with "OAuth 2.0 SAML Bearer Assertion Flow"...

Another thing (bumper, if You like) while looking up the solution to this is the new "My Domain" feature on salesforce.
Reading up on the topic i found that organization administrators will be able to disable logins from the https://login.salesforce.com domain (used so far), and enforce the use of the public domain of the organization (mydomain.my.salesforce.com) to enhance security.

Since these salesforce end-points are hardcoded (static) variables in the nforce module like (TEST_)AUTH_ENDPOINT and (TEST_)LOGIN_URI, it makes impossible to login/connect to an organization with these new security policies enabled.

Does Your development roadmap include support to these new features (SAML and MyDomain)?

Thank You for Your time reading this and giving the community a useful node module!
Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment