# Monday, 26 July 2010
I missed the discussions on "open cloud" and "cloud standards" at OSCON 2010, but indirectly got the gist of it. Standards themselves aren't bad, but it's rarely a good idea to create a standard for the sake of just creating a standard.

I've observed 3 fundamental perspectives and their likely motivators on the issue of cloud standards:

1) Vendor: Invested $XXX million in software stack Y. Wants it proclaimed "standard" (for obvious reasons)
2) Developer: Wants "write once run everywhere" in the name of "portability" (doesn't want to learn 5 different APIs)
3) Consumer: Doesn't care. Wants it to "just work"

If you look across all cloud stacks today, what do you see that is common amongst them and could even remotely be called a "standard"? I see TCP/IP running on Intel x86 CPU architecture.

If someone told you 10 years ago "In order to democratize access to computing resources and enable ubiquitous access to information we all need to standardize on Intel x86 architecture and deliver services over IP", several people would have scoffed at that. It's incredible how far cloud computing has come as a "standard".

There is an art to minimizing standards in order to maximize adoption.

Look at electricity. For years there were debates over whether DC or AC power was better. The US finally standardized on a plug with 120V AC at 60Hz as a "standard" for electricity. 3 very simple data points that led to an explosion in adoption.

But look at the rest of the world. Different combinations of plugs, voltages, and frequencies abound! Fortunately these standards are all "open" and simple enough to allow for converters and transformers to fill the gaps. Fundamentally, the world really only agreed to standardize on alternating current (AC) at a range of voltages between 110-240 Volts. But for consumers, it "just works".

That's the way cloud computing is evolving. Building on the Internet (TCP/IP), running on servers (Intel x86), and bridging the gaps using a few basic tools (JSON, XML, SOAP, REST, CSV).

Check out Nicholas Carr's book "The Big Switch" for the back story on how industrial America moved towards standardized and centralized power utilities and how that parallels what's going on in cloud computing.

The Internet is called "the web" for a reason. It's in reference to its inherently chaotic data structure. Asserting standards to make sense of it won't help. It will only inhibit adoption. The web and cloud computing must be allowed to organically sprawl uninhibited.

Final thoughts:
1) Vendors: Explain to customers which specific primitive cloud services you offer (Windows/Linux, HTTP/SSL/SMTP/FTP) and preempt the discussion on integration by providing APIs to raw computing resources.
2) Developers: Sorry. The days of learning one language or standard are over. "Embrace the cloud!" The next killer app exists at the convergence of several cloud services (think mashups blending location, maps, social graph, consumer reviews, and merchant offers... all in one).
3) Customers: Sorry about the noise. We'll get this sorted out as soon as possible and ensure the cloud "just works". Thank you for your patience :-)


Monday, 26 July 2010 19:58:53 (Pacific Daylight Time, UTC-07:00)
# Saturday, 10 July 2010


Facebook has nearly 500 million users (at the time of this writing) and are poised to transform how customer relationships are acquired, cultivated, and supported.

Consider the evolution of Contact management over the past 50 years:
  • Paper: Write names, addresses, birthdays, and other notes down on paper
  • Rolodex: Everyone trades business cards and keeps a local paper copy
  • PC Software: Rolodex moved to PC (Spreadsheets, Goldmine, Act!)
  • Client / Server Software: Many people in same office share common contact database (Siebel, MS CRM)
  • Software as a Service: Contacts moved to Internet hosted server. Accessible from anywhere (Salesforce.com)
  • Crowd sourced: 3rd parties pooling contact information to improve data quality, keep lists up to date (Plaxo, Jigsaw)
  • Social Networking: Contact maintains singular identity. Always up to date. Control of privacy and disclosure (LinkedIn, Facebook)
The consumer now has more power than ever. Consumers now generate more information about themselves than can be generated by 3rd parties.

Buying a list of leads that may be interested in buying camping gear will have no where near the effectiveness of publishing an ad on Facebook targeted at consumers with a self-identified interest in camping (See The Role of Advertising at Facebook).

Consumers will increasingly defer to their friends recommendations on which restaurants to visit, which shoes to buy, and which cars to drive.

CRM systems no longer contain the master records for Contact information.

Businesses must evolve past collecting and cleansing Contacts and instead collect meta-information that refers to customer-managed online profiles, such as Facebook and LinkedIn, for these resources are now truly authoritative. When there is a conflict between CRM Contact information and an online social network profile, the social profile will be master record.

Websites must evolve to become applications. Web forms soliciting contact information will become a thing of the past. Consumers will not have the patience to do anything more than a single click to identify interest in a product or service.

The video embedded in this blog post (and available here) demonstrates a Cool Sites application integrated with Facebook Connect and Salesforce CRM.
Saturday, 10 July 2010 16:31:06 (Pacific Daylight Time, UTC-07:00)
# Thursday, 08 July 2010

No, not this Trigger... keep reading...

Trigger development (apologies to Roy Rogers' horse) is not done on a daily basis by a typical Force.com Developer.

In my case, Trigger development is similar to using regular expressions (regex) in that I often rely on documentation and previously developed code examples to refresh my memory, do the coding, then put it aside for several weeks/months.

I decided to create a more fluent Trigger template to address the following challenges and prevent me from repeatedly making the same mistakes:

  • Bulkification best practices not provisioned by the Trigger creation wizard
  • Use of the 7 boolean context variables in code (isInsert, isBefore, etc...) greatly impairs readability and long-term maintainability
  • Trigger.old and Trigger.new collections are not available in certain contexts
  • Asynchronous trigger support not natively built-in

The solution was to create a mega-Trigger that handles all events and delegates them accordingly to an Apex trigger handler class.

You may want to customize this template to your own style. Here are some design considerations and assumptions in this template:

  • Use of traditional event method names on the handler class (OnBeforeInsert, OnAfterInsert)
  • Maps are used where they are most relevant
  • Objects in map collections cannot be modified, however there is nothing in the compiler to prevent you from trying. Remove them whenever not needed.
  • Maps are most useful when triggers modify other records by IDs, so they're included in update and delete triggers
  • Encourage use of asynchronous trigger processing by providing pre-built @future methods.
  • @future methods only support collections of native types. ID is preferred using this style.
  • Avoid use of before/after if not relevant. Example: OnUndelete is simpler than OnAfterUndelete (before undelete is not supported)
  • Provide boolean properties for determining trigger context (Is it a Trigger or VF/WebService call?)
  • There are no return values. Handler methods are assumed to assert validation rules using addError() to prevent commit.

References:
Apex Developers Guide - Triggers
Steve Anderson - Two interesting ways to architect Apex triggers

AccountTrigger.trigger

trigger AccountTrigger on Account (after delete, after insert, after undelete, 
after update, before delete, before insert, before update) {
	AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
	
	if(Trigger.isInsert && Trigger.isBefore){
		handler.OnBeforeInsert(Trigger.new);
	}
	else if(Trigger.isInsert && Trigger.isAfter){
		handler.OnAfterInsert(Trigger.new);
		AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet());
	}
	
	else if(Trigger.isUpdate && Trigger.isBefore){
		handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
	}
	else if(Trigger.isUpdate && Trigger.isAfter){
		handler.OnAfterUpdate(Trigger.old, Trigger.new, Trigger.newMap);
		AccountTriggerHandler.OnAfterUpdateAsync(Trigger.newMap.keySet());
	}
	
	else if(Trigger.isDelete && Trigger.isBefore){
		handler.OnBeforeDelete(Trigger.old, Trigger.oldMap);
	}
	else if(Trigger.isDelete && Trigger.isAfter){
		handler.OnAfterDelete(Trigger.old, Trigger.oldMap);
		AccountTriggerHandler.OnAfterDeleteAsync(Trigger.oldMap.keySet());
	}
	
	else if(Trigger.isUnDelete){
		handler.OnUndelete(Trigger.new);	
	}
}

AccountTriggerHandler.cls

 
public with sharing class AccountTriggerHandler {
	private boolean m_isExecuting = false;
	private integer BatchSize = 0;
	
	public AccountTriggerHandler(boolean isExecuting, integer size){
		m_isExecuting = isExecuting;
		BatchSize = size;
	}
		
	public void OnBeforeInsert(Account[] newAccounts){
		//Example usage
		for(Account newAccount : newAccounts){
			if(newAccount.AnnualRevenue == null){
				newAccount.AnnualRevenue.addError('Missing annual revenue');
			}
		}
	}
	
	public void OnAfterInsert(Account[] newAccounts){
		
	}
	
	@future public static void OnAfterInsertAsync(Set<ID> newAccountIDs){
		//Example usage
		List<Account> newAccounts = [select Id, Name from Account where Id IN :newAccountIDs];
	}
	
	public void OnBeforeUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map<ID, Account> accountMap){
		//Example Map usage
		Map<ID, Contact> contacts = new Map<ID, Contact>( [select Id, FirstName, LastName, Email from Contact where AccountId IN :accountMap.keySet()] );
	}
	
	public void OnAfterUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map<ID, Account> accountMap){
		
	}
	
	@future public static void OnAfterUpdateAsync(Set<ID> updatedAccountIDs){
		List<Account> updatedAccounts = [select Id, Name from Account where Id IN :updatedAccountIDs];
	}
	
	public void OnBeforeDelete(Account[] accountsToDelete, Map<ID, Account> accountMap){
		
	}
	
	public void OnAfterDelete(Account[] deletedAccounts, Map<ID, Account> accountMap){
		
	}
	
	@future public static void OnAfterDeleteAsync(Set<ID> deletedAccountIDs){
		
	}
	
	public void OnUndelete(Account[] restoredAccounts){
		
	}
	
	public boolean IsTriggerContext{
		get{ return m_isExecuting;}
	}
	
	public boolean IsVisualforcePageContext{
		get{ return !IsTriggerContext;}
	}
	
	public boolean IsWebServiceContext{
		get{ return !IsTriggerContext;}
	}
	
	public boolean IsExecuteAnonymousContext{
		get{ return !IsTriggerContext;}
	}
}
Thursday, 08 July 2010 14:16:12 (Pacific Daylight Time, UTC-07:00)