SlideShare a Scribd company logo
1 of 25
Applying Enterprise Application
Design Patterns on Force.com

Andrew Fawcett, FinancialForce.com, CTO
@andyinthecloud
Safe harbor
Safe harbor statement under the Private Securities Litigation Reform Act of 1995:

This presentation may contain forward-looking statements that involve risks, uncertainties, and assumptions. If any such uncertainties
materialize or if any of the assumptions proves incorrect, the results of salesforce.com, inc. could differ materially from the results expressed or
implied by the forward-looking statements we make. All statements other than statements of historical fact could be deemed forward-looking,
including any projections of product or service availability, subscriber growth, earnings, revenues, or other financial items and any statements
regarding strategies or plans of management for future operations, statements of belief, any statements concerning new, planned, or upgraded
services or technology developments and customer contracts or use of our services.

The risks and uncertainties referred to above include – but are not limited to – risks associated with developing and delivering new functionality
for our service, new products and services, our new business model, our past operating losses, possible fluctuations in our operating results
and rate of growth, interruptions or delays in our Web hosting, breach of our security measures, the outcome of intellectual property and other
litigation, risks associated with possible mergers and acquisitions, the immature market in which we operate, our relatively limited operating
history, our ability to expand, retain, and motivate our employees and manage our growth, new releases of our service and successful
customer deployment, our limited history reselling non-salesforce.com products, and utilization and selling to larger enterprise customers.
Further information on potential factors that could affect the financial results of salesforce.com, inc. is included in our annual report on Form 10-
Q for the most recent fiscal quarter ended July 31, 2012. This documents and others containing important disclosures are available on the SEC
Filings section of the Investor Information section of our Web site.

Any unreleased services or features referenced in this or other presentations, press releases or public statements are not currently available
and may not be delivered on time or at all. Customers who purchase our services should make the purchase decisions based upon features
that are currently available. Salesforce.com, inc. assumes no obligation and does not intend to update these forward-looking statements.
All about FinancialForce.com
                 Leading Native ISV on Force.com

                 • #1 Accounting App on Force.com
                 • #1 Professional Services Automation App on Force.com


                 Backed by Salesforce.com & UNIT4

                 • UNIT4 - $600 million, 33 years building business apps


                 Growing Rapidly

                 • San Francisco HQ – 595 Market St.
                 • 145 Employees
                 • Customers in 23 countries
Andrew Fawcett
CTO
@andyinthecloud
What are Enterprise Design Patterns?
 Recipes for Engineers
 Key Patterns
        Separation of Concerns (SOC)
        Data Mapper
        Domain Model
        Services
        Unit of Work
 Reference Martin Fowler
        http://martinfowler.com/eaaCatalog/
        Author of “Patterns of Enterprise
         Application Architecture”

         NOTE: Some additional care is required
         to select and apply to Apex and Force.com
Agenda and sample code overview
“Opportunity Extensions” Package
    Required Features
      • Ability to Discount an Opportunity easily
      • Ability to create Invoices from an Opportunity
      • Quick Opportunity Wizard from Account and
        MRU Products

Agenda : Code walk throughs…
    Review code sets to compare,
     discuss and contrast
    Sample A is implemented without
     considering patterns…
Sample A: Apply Discount @ v1
                         01: // Calculate discount factor
                         02: Decimal factor = Util.calculateFactor(discountPercentage);
       /**               03: if(opportunity.OpportunityLineItems.size()==0)
        * Version: v1 04: {
        * Visualforce Controller accepts a discount value Opportunity if no lines
                         05:           // Adjust the Amount on the
                         06:           opportunity.Amount = opportunity.Amount * factor;
        * from the user and applies it to the Opportunity
        */               07:           update opportunity;
                         08: }
                         09: else
      01: public PageReference applyDiscount()
                         10: {
      02: {
                         11:           // Adjust UnitPrice of each line
      03:        try
                         12:           for(OpportunityLineItem line : opportunity.OpportunityLineItems)
      04:        {
                         13:                        line.UnitPrice = line.UnitPrice * factor;
      05:                     // Query Opportunity and line items to apply discount to
                         14:           update opportunity.OpportunityLineItems;
      06:                     Opportunity opportunity =
                         15: }
      07:                                  [select Id, Name, (select UnitPrice from OpportunityLineItems)
      08:                                   from Opportunity o
      09:                                   where Id = :standardController.getId()];
      10:                     // Apply discount to line items
      11:                     Util.applyDiscount(opportunity, DiscountPercentage);
      12:        }
      13:        catch (Exception e)
      14:        {
      15:                     ApexPages.addMessages(e);
      16:        }
      17:        return null;
      18: }
Sample A: Create Invoice @ v1
/**
 * Version: v1
 * Developer has reused Util.applyDiscount to combine as a convenience for the user the two
 * tasks into one. But what would be the state of the Opportunity if Util.createInvoice threw
 * an exception?
 */
01: public PageReference createInvoice()
02: {
03:        try {
04:                  // Query Opportunity and line items to apply discount to
05:                  Opportunity opportunity =
06:                     [select Id, Name, AccountId, (select UnitPrice, Description, Quantity from OpportunityLineItems)
07:                       from Opportunity o
08:                       where Id = :standardController.getId()];
09:                  // Apply discount to line items
10:                  Util.applyDiscount(opportunity, DiscountPercentage);                                                        Transaction issue!
11:                  // Create Invoice from line items
                                                                                                                           Opportunity line changes are
12:                  Id invoiceId = Util.createInvoice(opportunity);
13:                  // Redirect to Invoice                                                                                 not rolled back on Invoice
14:                  return new PageReference('/'+invoiceId);                                                               creation failure. Automatic
15:        }                                                                                                                  rollback only occurs for
16:        catch (Exception e) {                                                                                              unhandled exceptions.
17:                  ApexPages.addMessages(e);
18:        }
Sample A: Apply Discount @ v2
/**
 * Version: v2
 * Developer has implemented an enhancement to apply discounts conditionally per line based on the
 * two new fields Opportunity.DiscountType__c and Product.DiscoutingApproved__c
 */                 01: // ENH:1024. Adjust UnitPrice of each line according to Discount Type of Opportunity
                       02: for(OpportunityLineItem line : opportunity.OpportunityLineItems)
01:public PageReference applyDiscount()
                       03: {
02:{                   04:         // ENH:1024. Skip products that have not been approved for discounting01:
03:        try         05:         if(opportunity.DiscountType__c == 'Approved Products')
04:        {           06:                    if(line.PricebookEntry.Product2.DiscountingApproved__c == false)
05:                 // 07:
                       Query Opportunity and line items to apply discount to
                                                           continue;
06:                 // 08:
                       ENH:1024. Added new columns to support new features in applyDiscount
                                   // Adjust UnitPrice
07:                 Opportunity line.UnitPrice = line.UnitPrice * factor;
                       09:         opportunity =
08:                  [select Id, Name, DiscountType__c,
                       10: }
09:                    (select UnitPrice, PricebookEntry.Product2.DiscountingApproved__c from OpportunityLineItems)
10:                              from Opportunity o
11:                              where Id = :standardController.getId()];
12:                 // Apply discount to line items
13:                 Util.applyDiscount(opportunity, DiscountPercentage);
Sample A: Create Invoice @ v2
/**
 * Version: v2
 * NOTE: This code has not been changed since v1. What error occurs when Util.applyDiscount is
 * executed given the changes in v2 on the slide before?
 */
01: public PageReference createInvoice()
02: {
03:        try
04:        {
05:                 // Query Opportunity and line items to apply discount to
06:                 Opportunity opportunity =
07:                             [select Id, Name, AccountId,
08:                                         (select UnitPrice, Description, Quantity from OpportunityLineItems)
09:                              from Opportunity o
10:                              where Id = :standardController.getId()];
11:                 // Apply discount to line items
12:                 Util.applyDiscount(opportunity, DiscountPercentage);
13:                 // Create Invoice from line items
14:                 Id invoiceId = Util.createInvoice(opportunity);
15:                 // Redirect to Invoice
16:                 return n
17: }
Sample A: Opportunity Wizard
 /**
  * A Opportunity Wizard that displays the most recently used Products and allows the user to
  * select them to create an new Opportunity. Is there any logic here that does not belong?
  */

01: public QuickOpportunityWizardController(ApexPages.StandardController controller)
02: {
03:        standardController = controller;
04:
05:        // Create a new Opportunity defaulting from the Account
06:        Account account = (Account) standardController.getRecord();
07:        viewState = new ViewState();
08:        viewState.Opportunity = new Opportunity();
09:        viewState.Opportunity.Name = account.Name;
10:        viewState.Opportunity.AccountId = account.Id;
11:        viewState.Opportunity.DiscountType__c = OpportunitySettings__c.getInstance().DiscountType__c;
12:        viewState.SelectLineItemList = new List<SelectLineItem>();
13:
14:        // Recently used Opportunity lines                                   In order to ensure consistent
15:        List<OpportunityLineItem> lines =                                         behavior, across this
                                                                                  wizard, Salesforce UI and
                                                                                 API’s, this logic needs to be
                                                                                      placed in a trigger
Sample A: Create Invoice @ v2

Issues Discussed
   • Partial database updates in error conditions
   • Data dependent runtime errors
   • Inconsistent behavior between UI’s, API’s and Tools
Other Concerns
   • Code not factored for exposure via an Application API
         • Business functionality implemented in controller classes
    •   Util methods are not bulkified, causing callers to inhert bulkification issues
    •   CRUD security not enforced
Sample B: Class Diagram
Sample B: Controller Methods
        /**
         * Controller methods interact and call only the
         * Service class methods. There is no transaction
         * management needed in the controller methods. However
         * error handling and reporting is managed by controller.
         */
      01: public PageReference applyDiscount()
      01: public PageReference createInvoice()
                 PageReference createOpportunity()
      02: {{
      02:
      03:
      03:        try
                  try
                  try
      04:
      04:        {{
                  {
      05:
      05:                     // Apply discount entered to line items selected by the user
                               // Create Invoice from fromitems of the current Opportunity
                               // Create Opportunity line the current Opportunity
      06:
      06:                     OpportunitiesService.applyDiscounts(
                               List<ID> invoiceIds = =
                               List<ID> opportunityIds
      07:
      07:                                 new List<ID> { standardController.getId() }, DiscountPercentage);
                                           OpportunitiesService.createInvoices(
                                           OpportunitiesService.createOpportunities(
      08:
      08:        }                                    new List<ID> { standardController.getId() }, DiscountPercentage);
                                                           List<ID> { standardController.getId() }, selectedLines);
      09:
      09:        catch (Exception e)
      10:
      10:        {             // Redirect to Invoice
                               // Redirect to Opportunity
      11:
      11:                     ApexPages.addMessages(e);
                               return new PageReference('/'+invoiceIds[0]);
                               return       PageReference('/'+opportunity[0]);
      12:
      12:        }
      13:
      13:        return null;
                  }
                  }
      14: }
      14:         catch (Exception e)
                  catch (Exception e)
      15:         {
                  {
      16:                      ApexPages.addMessages(e);
                               ApexPages.addMessages(e);
      17:         }
                  }
      18:         return null;
                  return null;
      19: }
Sample B: Service and Unit Of Work
      /**
       * Service methods are the main entry point for your applications
       * logic. Your controller methods call these. In addition they can
       * form a public API for partners and developers using your
       * application. Service methods utilize code from Domain, Selector
       * and Unit Of Work classes.
       */
      01: global with sharing class OpportunitiesService
      02: {
      03:        global static void applyDiscounts(Set<ID> opportunityIds, Decimal discountPercentage)
      04:        {
      05:                    // Create unit of work to capture work and commit it under one transaction
      06:                    SObjectUnitOfWork uow = new SObjectUnitOfWork(SERVICE_SOBJECTS);
      07:                    // Query Opportunities (including products) and apply discount
      08:                    Opportunities opportunities = new Opportunities(
      09:                               new OpportunitiesSelector().selectByIdWithProducts(opportunityIds));
      10:                    opportunities.applyDiscount(discountPercentage, uow);
      11:                    // Commit updates to opportunities
      12:                    uow.commitWork();
      13:        }
      14:
      15:        // SObject's used by the logic in this service, listed in dependency order
      16:        private static List<Schema.SObjectType> SERVICE_SOBJECTS =
      17:                    new Schema.SObjectType[] {
      18:                               Invoice__c.SObjectType,
      19:                               Opportunity.SObjectType,
      20:                               OpportunityLineItem.SObjectType };
Sample B: Service and Unit Of Work
      /**
       * This service method passes its Unit Of Work to a related domain
       * class such that it can also register any database changes.
       * See Opportunities.applyDiscount method.
       */
      01: global static List<Id> createInvoices(Set<ID> opportunityIds, Decimal discountPercentage)
      02: {
      03:        // Create unit of work to capture work and commit it under one transaction
      04:        SObjectUnitOfWork unitOfWork = new SObjectUnitOfWork(SERVICE_SOBJECTS);
      05:        // Query Opportunities
      06:        Opportunities opportunities = new Opportunities(
      07:                     new OpportunitiesSelector().selectByIdWithProducts(opportunityIds));
      08:        // Optionally apply discounts as part of invoice creation
      09:        if(discountPercentage!=null && discountPercentage>0)
      10:                     opportunities.applyDiscount(discountPercentage, unitOfWork);
      11:        // Create Invoices from the given opportunities
      12:        List<Invoice__c> invoices = new List<Invoice__c>();
      13:        for(Opportunity opportunityRecord : (List<Opportunity>) opportunities.Records)
      14:        {
      15:                     // Create Invoice and populate invoice fields (removed here) from Opportunity ...
      16:                     Invoice__c invoice = new Invoice__c();
      17:                     unitOfWork.registerNew(invoice);
      18:        }
      19:        // Commit any Opportunity updates and new Invoices
      20:        unitOfWork.commitWork();
Sample B: Unit of Work : Commit Work
01: public with sharing class SObjectUnitOfWork
02: {
03:         public void commitWork()
04:         {
05:                     // Wrap the work in its own transaction
06:                     Savepoint sp = Database.setSavePoint();
07:                     try
08:                     {
09:                                 // Insert by type
10:                                 for(Schema.SObjectType sObjectType : m_sObjectTypes)
11:                                 {
12:                                               m_relationships.get(sObjectType.getDescribe().getName()).resolve();
13:                                               insert m_newListByType.get(sObjectType.getDescribe().getName());
14:                                 }
15::                                // Update by type
16:                                 for(Schema.SObjectType sObjectType : m_sObjectTypes)
17:                                               update m_dirtyListByType.get(sObjectType.getDescribe().getName());
18:                                 // Delete by type (in reverse dependency order)
19:                                 Integer objectIdx = m_sObjectTypes.size() - 1;
20:                                 while(objectIdx>=0)
21:                                               delete m_deletedListByType.get(m_sObjectTypes[objectIdx--].getDescribe().getName());
22:                     }
23:                     catch (Exception e)
24:                     {
25:                                 // Rollback
26:                                 Database.rollback(sp);
27:                                 // Throw exception on to caller
28:                                 throw e;
29:                     }
30:         }
31: }
Sample B: Data Mapper : Simple
/**
 * These classes manage in a single place all queries to a given object. Maintaining a single list of
 * fields that will be queried. The base class SObjectSelector provides selectSObjectById method for free!
 * NOTE: This base class also handles CRUD security.
 */
01: public with sharing class OpportunitiesSelector extends SObjectSelector
02: {
03:        public List<Schema.SObjectField> getSObjectFieldList()
04:        {
05:                    return new List<Schema.SObjectField> {
06:                      Opportunity.AccountId, Opportunity.Amount, Opportunity.CloseDate, Opportunity.Description,
07:                      Opportunity.ExpectedRevenue, Opportunity.Id, Opportunity.Name, Opportunity.Pricebook2Id,
08:                      Opportunity.Probability, Opportunity.StageName, Opportunity.Type, Opportunity.DiscountType__c
09:                    };
10:        }
11:        public Schema.SObjectType getSObjectType()
12:        {
13:                    return Opportunity.sObjectType;
14:        }
15:        public List<Opportunity> selectById(Set<ID> idSet)
16:        {
17:                    return (List<Opportunity>) selectSObjectsById(idSet);
18:        }
19: }
Sample B: Data Mapper : Complex
 /**
  * This additional Selector method illustrates how other selectors can be consumed to ensure that
  * field lists from other objects queried via a parent object query are also consistently applied.
  * NOTE: The SObjectSelector logic is also multi-company aware, injecting CurrencyIsoCode as needed.
  */
01: public List<Opportunity> selectByIdWithProducts(Set<ID> idSet)
02: {
03:        /** Omitted for brevity: Construction of dependent selectors */
04:
05:        String query = String.format(
06:                              'select {0},
07:                                   (select {3},{5},{6},{7} from OpportunityLineItems order by {4}) ' +
08:                                'from {1} where id in :idSet order by {2}',
09:                   new List<String> {
10:                              getFieldListString(), getSObjectName(), getOrderBy(),
11:                              opportunityLineItemSelector.getFieldListString(),
12:                              opportunityLineItemSelector.getOrderBy(),
13:                              pricebookEntrySelector.getRelatedFieldListString('PricebookEntry'),
14:                              productSelector.getRelatedFieldListString('PricebookEntry.Product2'),
15:                              pricebookSelector.getRelatedFieldListString('PricebookEntry.Pricebook2’) });
16:
17:        return (List<Opportunity>) Database.query(query);
18: }
Sample B: Domain Model : Trigger and Domain Class
 /**
  * Implement the Apex Trigger by calling the SObjectDomain method triggerHandler, which routes to the
  * appropriate methods implemented in the domain class. Such as applyDefaults() during
  * record inserts. Other methods are validate(), beforeInsert (), afterInsert() etc..
  */
01: trigger OpportunitiesTrigger on Opportunity
02: (after delete, after insert, after update, before delete, before insert, before update)
03: {
04:         // Creates Domain class instance and calls appropriate override methods according to Trigger state
05:         SObjectDomain.triggerHandler(Opportunities.class);
06: }

01: public with sharing class Opportunities extends SObjectDomain
02: {
03:        public Opportunities(List<Opportunity> sObjectList)
04:        {
05:                   super(sObjectList); // Classes are initialized with lists to enforce bulkification throughout
06:        }
07:
08:        public override void applyDefaults()
09:        {
10:                   for(Opportunity opportunity : (List<Opportunity>) Records) // Apply defaults to Opportunities
11:                   {
12:                              opportunity.DiscountType__c = OpportunitySettings__c.getInstance().DiscountType__c;
13:                   }
14:        }
15: }
Sample B: Domain Model : Business Logic
                                     01: public void applyDiscount(Decimal discountPercentage, SObjectUnitOfWork unitOfWork)
/**
                                     02: {
 * Domain class method implements    03:         // Calculate discount factor
 * the discount logic, note          04:         Decimal factor = Util.calculateDiscountFactor(discountPercentage);
 * bulkification is enforced here.   05:         // Opportunity lines to apply discount to
 * Unit of Work for database         06:         List<OpportunityLineItem> linesToApplyDiscount = new List<OpportunityLineItem>();
 * updates. While delegating to      07:         // Apply discount
 * the domain class responsible      08:         for(Opportunity opportunity : (List<Opportunity>) Records)
                                     09:         {
 * for Opportunity Products to
                                     10:                      // Apply to the Opportunity Amount?
 * apply discounts as needed to      11:                      if(opportunity.OpportunityLineItems.size()==0)
 * product lines.                    12:                      {
 */                                  13:                                   // Adjust the Amount on the Opportunity if no lines
                                     14:                                   opportunity.Amount = opportunity.Amount * factor;
                                     15:                                   unitOfWork.registerDirty(opportunity);
                                     16:                      }
                                     17:                      else
                                     18:                      {
                                     19:                                   // Collect lines to apply discount to
                                     20:                                   linesToApplyDiscount.add(opportunity.OpportunityLineItems);
                                     21:                      }
                                     22:         }
                                     23:         // Apply discount to lines
                                     24:         OpportunityLineItems lineItems = new OpportunityLineItems(linesToApplyDiscount);
                                     25:         lineItems.applyDiscount(discountPercentage, unitOfWork);
                                     26: }
Summary
 Separation of Concern Pattern. Code is factored in respect to its purpose
 and responsibility and thus easier to maintain and behaves consistently.
 Service Pattern, task and process based logic is exposed consistently from a
 single place and consumed by all ‘clients’. Controllers and external callers.
 Data Mapper, the selector classes provide a single place to query objects and
 enforce CRUD security.
 Unit Of Work Pattern provides a single transactional context via the
 commitWork method. Bulkification of DML operations is simplified via
 registerXXX methods.
 Domain Pattern, provides object orientated model for defaulting, validation
 and behavioral logic. Since domain classes manage record sets, they also
 enforce bulkfication throughout your logic not just in triggers. Trigger
 implementations can be standardized.
Resources
Java Code : {SourceObject__c} Listener
• Other // TODO:’s and Ideas?! 
  Review SObjectUnitOfWork further, it also aids in inserting related
    records and updating references!!
  Auto handle CRUD security in SObjectDomain base class trigger
    delegate methods?

      • Source Code and Contact Details
          GitHub: https://github.com/financialforcedev
          Twitter: andyinthecloud
DF12 - Applying Enterprise Application Design Patterns on Force.com
DF12 - Applying Enterprise Application Design Patterns on Force.com

More Related Content

Viewers also liked

Salesforce Coding techniques that keep your admins happy (DF13)
Salesforce Coding techniques that keep your admins happy (DF13)Salesforce Coding techniques that keep your admins happy (DF13)
Salesforce Coding techniques that keep your admins happy (DF13)Roy Gilad
 
Salesforce API Series: Fast Parallel Data Loading with the Bulk API Webinar
Salesforce API Series: Fast Parallel Data Loading with the Bulk API WebinarSalesforce API Series: Fast Parallel Data Loading with the Bulk API Webinar
Salesforce API Series: Fast Parallel Data Loading with the Bulk API WebinarSalesforce Developers
 
From Sandbox To Production: An Introduction to Salesforce Release Management
From Sandbox To Production: An Introduction to Salesforce Release ManagementFrom Sandbox To Production: An Introduction to Salesforce Release Management
From Sandbox To Production: An Introduction to Salesforce Release ManagementSalesforce Developers
 
Secure Development on the Salesforce Platform - Part 3
Secure Development on the Salesforce Platform - Part 3Secure Development on the Salesforce Platform - Part 3
Secure Development on the Salesforce Platform - Part 3Mark Adcock
 
Advanced Apex Development - Asynchronous Processes
Advanced Apex Development - Asynchronous ProcessesAdvanced Apex Development - Asynchronous Processes
Advanced Apex Development - Asynchronous ProcessesSalesforce Developers
 
Salesforce Release Management - Best Practices and Tools for Deployment
Salesforce Release Management - Best Practices and Tools for DeploymentSalesforce Release Management - Best Practices and Tools for Deployment
Salesforce Release Management - Best Practices and Tools for DeploymentSalesforce Developers
 
Secure Development on the Salesforce Platform - Part I
Secure Development on the Salesforce Platform - Part ISecure Development on the Salesforce Platform - Part I
Secure Development on the Salesforce Platform - Part ISalesforce Developers
 
The Ideal Salesforce Development Lifecycle
The Ideal Salesforce Development LifecycleThe Ideal Salesforce Development Lifecycle
The Ideal Salesforce Development LifecycleJoshua Hoskins
 
Salesforce Jumpstart: Getting Started as a Consulting Partner
Salesforce Jumpstart: Getting Started as a Consulting PartnerSalesforce Jumpstart: Getting Started as a Consulting Partner
Salesforce Jumpstart: Getting Started as a Consulting PartnerSalesforce Partners
 
Salesforce.com process map (from lead to opportunity)
Salesforce.com process map (from lead to opportunity)Salesforce.com process map (from lead to opportunity)
Salesforce.com process map (from lead to opportunity)Roger Borges Grilo
 
Secure Development on the Salesforce Platform - Part 2
Secure Development on the Salesforce Platform - Part 2Secure Development on the Salesforce Platform - Part 2
Secure Development on the Salesforce Platform - Part 2Salesforce Developers
 
Enterprise Architecture Salesforce
Enterprise Architecture SalesforceEnterprise Architecture Salesforce
Enterprise Architecture SalesforcePeter Doolan
 
Salesforce com-architecture
Salesforce com-architectureSalesforce com-architecture
Salesforce com-architecturedrewz lin
 
Build Cloud & Mobile App on Salesforce Force.com Platform in 15 mins
Build Cloud & Mobile App on Salesforce Force.com Platform in 15 minsBuild Cloud & Mobile App on Salesforce Force.com Platform in 15 mins
Build Cloud & Mobile App on Salesforce Force.com Platform in 15 minsKashi Ahmed
 

Viewers also liked (19)

Apex Design Patterns
Apex Design PatternsApex Design Patterns
Apex Design Patterns
 
Salesforce Coding techniques that keep your admins happy (DF13)
Salesforce Coding techniques that keep your admins happy (DF13)Salesforce Coding techniques that keep your admins happy (DF13)
Salesforce Coding techniques that keep your admins happy (DF13)
 
Campaign management
Campaign managementCampaign management
Campaign management
 
Salesforce API Series: Fast Parallel Data Loading with the Bulk API Webinar
Salesforce API Series: Fast Parallel Data Loading with the Bulk API WebinarSalesforce API Series: Fast Parallel Data Loading with the Bulk API Webinar
Salesforce API Series: Fast Parallel Data Loading with the Bulk API Webinar
 
From Sandbox To Production: An Introduction to Salesforce Release Management
From Sandbox To Production: An Introduction to Salesforce Release ManagementFrom Sandbox To Production: An Introduction to Salesforce Release Management
From Sandbox To Production: An Introduction to Salesforce Release Management
 
Secure Development on the Salesforce Platform - Part 3
Secure Development on the Salesforce Platform - Part 3Secure Development on the Salesforce Platform - Part 3
Secure Development on the Salesforce Platform - Part 3
 
Advanced Apex Development - Asynchronous Processes
Advanced Apex Development - Asynchronous ProcessesAdvanced Apex Development - Asynchronous Processes
Advanced Apex Development - Asynchronous Processes
 
Salesforce Release Management - Best Practices and Tools for Deployment
Salesforce Release Management - Best Practices and Tools for DeploymentSalesforce Release Management - Best Practices and Tools for Deployment
Salesforce Release Management - Best Practices and Tools for Deployment
 
Secure Development on the Salesforce Platform - Part I
Secure Development on the Salesforce Platform - Part ISecure Development on the Salesforce Platform - Part I
Secure Development on the Salesforce Platform - Part I
 
The Ideal Salesforce Development Lifecycle
The Ideal Salesforce Development LifecycleThe Ideal Salesforce Development Lifecycle
The Ideal Salesforce Development Lifecycle
 
Salesforce Data Structures
Salesforce Data StructuresSalesforce Data Structures
Salesforce Data Structures
 
Salesforce Jumpstart: Getting Started as a Consulting Partner
Salesforce Jumpstart: Getting Started as a Consulting PartnerSalesforce Jumpstart: Getting Started as a Consulting Partner
Salesforce Jumpstart: Getting Started as a Consulting Partner
 
Salesforce.com process map (from lead to opportunity)
Salesforce.com process map (from lead to opportunity)Salesforce.com process map (from lead to opportunity)
Salesforce.com process map (from lead to opportunity)
 
Secure Development on the Salesforce Platform - Part 2
Secure Development on the Salesforce Platform - Part 2Secure Development on the Salesforce Platform - Part 2
Secure Development on the Salesforce Platform - Part 2
 
Enterprise Architecture Salesforce
Enterprise Architecture SalesforceEnterprise Architecture Salesforce
Enterprise Architecture Salesforce
 
Data model in salesforce
Data model in salesforceData model in salesforce
Data model in salesforce
 
Salesforce com-architecture
Salesforce com-architectureSalesforce com-architecture
Salesforce com-architecture
 
Top 10 Checklist For Successful Salesforce Implementation
Top 10 Checklist For Successful Salesforce ImplementationTop 10 Checklist For Successful Salesforce Implementation
Top 10 Checklist For Successful Salesforce Implementation
 
Build Cloud & Mobile App on Salesforce Force.com Platform in 15 mins
Build Cloud & Mobile App on Salesforce Force.com Platform in 15 minsBuild Cloud & Mobile App on Salesforce Force.com Platform in 15 mins
Build Cloud & Mobile App on Salesforce Force.com Platform in 15 mins
 

Recently uploaded

Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhisoniya singh
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 

Recently uploaded (20)

Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 

DF12 - Applying Enterprise Application Design Patterns on Force.com

  • 1. Applying Enterprise Application Design Patterns on Force.com Andrew Fawcett, FinancialForce.com, CTO @andyinthecloud
  • 2. Safe harbor Safe harbor statement under the Private Securities Litigation Reform Act of 1995: This presentation may contain forward-looking statements that involve risks, uncertainties, and assumptions. If any such uncertainties materialize or if any of the assumptions proves incorrect, the results of salesforce.com, inc. could differ materially from the results expressed or implied by the forward-looking statements we make. All statements other than statements of historical fact could be deemed forward-looking, including any projections of product or service availability, subscriber growth, earnings, revenues, or other financial items and any statements regarding strategies or plans of management for future operations, statements of belief, any statements concerning new, planned, or upgraded services or technology developments and customer contracts or use of our services. The risks and uncertainties referred to above include – but are not limited to – risks associated with developing and delivering new functionality for our service, new products and services, our new business model, our past operating losses, possible fluctuations in our operating results and rate of growth, interruptions or delays in our Web hosting, breach of our security measures, the outcome of intellectual property and other litigation, risks associated with possible mergers and acquisitions, the immature market in which we operate, our relatively limited operating history, our ability to expand, retain, and motivate our employees and manage our growth, new releases of our service and successful customer deployment, our limited history reselling non-salesforce.com products, and utilization and selling to larger enterprise customers. Further information on potential factors that could affect the financial results of salesforce.com, inc. is included in our annual report on Form 10- Q for the most recent fiscal quarter ended July 31, 2012. This documents and others containing important disclosures are available on the SEC Filings section of the Investor Information section of our Web site. Any unreleased services or features referenced in this or other presentations, press releases or public statements are not currently available and may not be delivered on time or at all. Customers who purchase our services should make the purchase decisions based upon features that are currently available. Salesforce.com, inc. assumes no obligation and does not intend to update these forward-looking statements.
  • 3. All about FinancialForce.com Leading Native ISV on Force.com • #1 Accounting App on Force.com • #1 Professional Services Automation App on Force.com Backed by Salesforce.com & UNIT4 • UNIT4 - $600 million, 33 years building business apps Growing Rapidly • San Francisco HQ – 595 Market St. • 145 Employees • Customers in 23 countries
  • 5. What are Enterprise Design Patterns? Recipes for Engineers Key Patterns  Separation of Concerns (SOC)  Data Mapper  Domain Model  Services  Unit of Work Reference Martin Fowler  http://martinfowler.com/eaaCatalog/  Author of “Patterns of Enterprise Application Architecture” NOTE: Some additional care is required to select and apply to Apex and Force.com
  • 6. Agenda and sample code overview “Opportunity Extensions” Package  Required Features • Ability to Discount an Opportunity easily • Ability to create Invoices from an Opportunity • Quick Opportunity Wizard from Account and MRU Products Agenda : Code walk throughs…  Review code sets to compare, discuss and contrast  Sample A is implemented without considering patterns…
  • 7. Sample A: Apply Discount @ v1 01: // Calculate discount factor 02: Decimal factor = Util.calculateFactor(discountPercentage); /** 03: if(opportunity.OpportunityLineItems.size()==0) * Version: v1 04: { * Visualforce Controller accepts a discount value Opportunity if no lines 05: // Adjust the Amount on the 06: opportunity.Amount = opportunity.Amount * factor; * from the user and applies it to the Opportunity */ 07: update opportunity; 08: } 09: else 01: public PageReference applyDiscount() 10: { 02: { 11: // Adjust UnitPrice of each line 03: try 12: for(OpportunityLineItem line : opportunity.OpportunityLineItems) 04: { 13: line.UnitPrice = line.UnitPrice * factor; 05: // Query Opportunity and line items to apply discount to 14: update opportunity.OpportunityLineItems; 06: Opportunity opportunity = 15: } 07: [select Id, Name, (select UnitPrice from OpportunityLineItems) 08: from Opportunity o 09: where Id = :standardController.getId()]; 10: // Apply discount to line items 11: Util.applyDiscount(opportunity, DiscountPercentage); 12: } 13: catch (Exception e) 14: { 15: ApexPages.addMessages(e); 16: } 17: return null; 18: }
  • 8. Sample A: Create Invoice @ v1 /** * Version: v1 * Developer has reused Util.applyDiscount to combine as a convenience for the user the two * tasks into one. But what would be the state of the Opportunity if Util.createInvoice threw * an exception? */ 01: public PageReference createInvoice() 02: { 03: try { 04: // Query Opportunity and line items to apply discount to 05: Opportunity opportunity = 06: [select Id, Name, AccountId, (select UnitPrice, Description, Quantity from OpportunityLineItems) 07: from Opportunity o 08: where Id = :standardController.getId()]; 09: // Apply discount to line items 10: Util.applyDiscount(opportunity, DiscountPercentage); Transaction issue! 11: // Create Invoice from line items Opportunity line changes are 12: Id invoiceId = Util.createInvoice(opportunity); 13: // Redirect to Invoice not rolled back on Invoice 14: return new PageReference('/'+invoiceId); creation failure. Automatic 15: } rollback only occurs for 16: catch (Exception e) { unhandled exceptions. 17: ApexPages.addMessages(e); 18: }
  • 9. Sample A: Apply Discount @ v2 /** * Version: v2 * Developer has implemented an enhancement to apply discounts conditionally per line based on the * two new fields Opportunity.DiscountType__c and Product.DiscoutingApproved__c */ 01: // ENH:1024. Adjust UnitPrice of each line according to Discount Type of Opportunity 02: for(OpportunityLineItem line : opportunity.OpportunityLineItems) 01:public PageReference applyDiscount() 03: { 02:{ 04: // ENH:1024. Skip products that have not been approved for discounting01: 03: try 05: if(opportunity.DiscountType__c == 'Approved Products') 04: { 06: if(line.PricebookEntry.Product2.DiscountingApproved__c == false) 05: // 07: Query Opportunity and line items to apply discount to continue; 06: // 08: ENH:1024. Added new columns to support new features in applyDiscount // Adjust UnitPrice 07: Opportunity line.UnitPrice = line.UnitPrice * factor; 09: opportunity = 08: [select Id, Name, DiscountType__c, 10: } 09: (select UnitPrice, PricebookEntry.Product2.DiscountingApproved__c from OpportunityLineItems) 10: from Opportunity o 11: where Id = :standardController.getId()]; 12: // Apply discount to line items 13: Util.applyDiscount(opportunity, DiscountPercentage);
  • 10. Sample A: Create Invoice @ v2 /** * Version: v2 * NOTE: This code has not been changed since v1. What error occurs when Util.applyDiscount is * executed given the changes in v2 on the slide before? */ 01: public PageReference createInvoice() 02: { 03: try 04: { 05: // Query Opportunity and line items to apply discount to 06: Opportunity opportunity = 07: [select Id, Name, AccountId, 08: (select UnitPrice, Description, Quantity from OpportunityLineItems) 09: from Opportunity o 10: where Id = :standardController.getId()]; 11: // Apply discount to line items 12: Util.applyDiscount(opportunity, DiscountPercentage); 13: // Create Invoice from line items 14: Id invoiceId = Util.createInvoice(opportunity); 15: // Redirect to Invoice 16: return n 17: }
  • 11. Sample A: Opportunity Wizard /** * A Opportunity Wizard that displays the most recently used Products and allows the user to * select them to create an new Opportunity. Is there any logic here that does not belong? */ 01: public QuickOpportunityWizardController(ApexPages.StandardController controller) 02: { 03: standardController = controller; 04: 05: // Create a new Opportunity defaulting from the Account 06: Account account = (Account) standardController.getRecord(); 07: viewState = new ViewState(); 08: viewState.Opportunity = new Opportunity(); 09: viewState.Opportunity.Name = account.Name; 10: viewState.Opportunity.AccountId = account.Id; 11: viewState.Opportunity.DiscountType__c = OpportunitySettings__c.getInstance().DiscountType__c; 12: viewState.SelectLineItemList = new List<SelectLineItem>(); 13: 14: // Recently used Opportunity lines In order to ensure consistent 15: List<OpportunityLineItem> lines = behavior, across this wizard, Salesforce UI and API’s, this logic needs to be placed in a trigger
  • 12. Sample A: Create Invoice @ v2 Issues Discussed • Partial database updates in error conditions • Data dependent runtime errors • Inconsistent behavior between UI’s, API’s and Tools Other Concerns • Code not factored for exposure via an Application API • Business functionality implemented in controller classes • Util methods are not bulkified, causing callers to inhert bulkification issues • CRUD security not enforced
  • 13. Sample B: Class Diagram
  • 14. Sample B: Controller Methods /** * Controller methods interact and call only the * Service class methods. There is no transaction * management needed in the controller methods. However * error handling and reporting is managed by controller. */ 01: public PageReference applyDiscount() 01: public PageReference createInvoice() PageReference createOpportunity() 02: {{ 02: 03: 03: try try try 04: 04: {{ { 05: 05: // Apply discount entered to line items selected by the user // Create Invoice from fromitems of the current Opportunity // Create Opportunity line the current Opportunity 06: 06: OpportunitiesService.applyDiscounts( List<ID> invoiceIds = = List<ID> opportunityIds 07: 07: new List<ID> { standardController.getId() }, DiscountPercentage); OpportunitiesService.createInvoices( OpportunitiesService.createOpportunities( 08: 08: } new List<ID> { standardController.getId() }, DiscountPercentage); List<ID> { standardController.getId() }, selectedLines); 09: 09: catch (Exception e) 10: 10: { // Redirect to Invoice // Redirect to Opportunity 11: 11: ApexPages.addMessages(e); return new PageReference('/'+invoiceIds[0]); return PageReference('/'+opportunity[0]); 12: 12: } 13: 13: return null; } } 14: } 14: catch (Exception e) catch (Exception e) 15: { { 16: ApexPages.addMessages(e); ApexPages.addMessages(e); 17: } } 18: return null; return null; 19: }
  • 15. Sample B: Service and Unit Of Work /** * Service methods are the main entry point for your applications * logic. Your controller methods call these. In addition they can * form a public API for partners and developers using your * application. Service methods utilize code from Domain, Selector * and Unit Of Work classes. */ 01: global with sharing class OpportunitiesService 02: { 03: global static void applyDiscounts(Set<ID> opportunityIds, Decimal discountPercentage) 04: { 05: // Create unit of work to capture work and commit it under one transaction 06: SObjectUnitOfWork uow = new SObjectUnitOfWork(SERVICE_SOBJECTS); 07: // Query Opportunities (including products) and apply discount 08: Opportunities opportunities = new Opportunities( 09: new OpportunitiesSelector().selectByIdWithProducts(opportunityIds)); 10: opportunities.applyDiscount(discountPercentage, uow); 11: // Commit updates to opportunities 12: uow.commitWork(); 13: } 14: 15: // SObject's used by the logic in this service, listed in dependency order 16: private static List<Schema.SObjectType> SERVICE_SOBJECTS = 17: new Schema.SObjectType[] { 18: Invoice__c.SObjectType, 19: Opportunity.SObjectType, 20: OpportunityLineItem.SObjectType };
  • 16. Sample B: Service and Unit Of Work /** * This service method passes its Unit Of Work to a related domain * class such that it can also register any database changes. * See Opportunities.applyDiscount method. */ 01: global static List<Id> createInvoices(Set<ID> opportunityIds, Decimal discountPercentage) 02: { 03: // Create unit of work to capture work and commit it under one transaction 04: SObjectUnitOfWork unitOfWork = new SObjectUnitOfWork(SERVICE_SOBJECTS); 05: // Query Opportunities 06: Opportunities opportunities = new Opportunities( 07: new OpportunitiesSelector().selectByIdWithProducts(opportunityIds)); 08: // Optionally apply discounts as part of invoice creation 09: if(discountPercentage!=null && discountPercentage>0) 10: opportunities.applyDiscount(discountPercentage, unitOfWork); 11: // Create Invoices from the given opportunities 12: List<Invoice__c> invoices = new List<Invoice__c>(); 13: for(Opportunity opportunityRecord : (List<Opportunity>) opportunities.Records) 14: { 15: // Create Invoice and populate invoice fields (removed here) from Opportunity ... 16: Invoice__c invoice = new Invoice__c(); 17: unitOfWork.registerNew(invoice); 18: } 19: // Commit any Opportunity updates and new Invoices 20: unitOfWork.commitWork();
  • 17. Sample B: Unit of Work : Commit Work 01: public with sharing class SObjectUnitOfWork 02: { 03: public void commitWork() 04: { 05: // Wrap the work in its own transaction 06: Savepoint sp = Database.setSavePoint(); 07: try 08: { 09: // Insert by type 10: for(Schema.SObjectType sObjectType : m_sObjectTypes) 11: { 12: m_relationships.get(sObjectType.getDescribe().getName()).resolve(); 13: insert m_newListByType.get(sObjectType.getDescribe().getName()); 14: } 15:: // Update by type 16: for(Schema.SObjectType sObjectType : m_sObjectTypes) 17: update m_dirtyListByType.get(sObjectType.getDescribe().getName()); 18: // Delete by type (in reverse dependency order) 19: Integer objectIdx = m_sObjectTypes.size() - 1; 20: while(objectIdx>=0) 21: delete m_deletedListByType.get(m_sObjectTypes[objectIdx--].getDescribe().getName()); 22: } 23: catch (Exception e) 24: { 25: // Rollback 26: Database.rollback(sp); 27: // Throw exception on to caller 28: throw e; 29: } 30: } 31: }
  • 18. Sample B: Data Mapper : Simple /** * These classes manage in a single place all queries to a given object. Maintaining a single list of * fields that will be queried. The base class SObjectSelector provides selectSObjectById method for free! * NOTE: This base class also handles CRUD security. */ 01: public with sharing class OpportunitiesSelector extends SObjectSelector 02: { 03: public List<Schema.SObjectField> getSObjectFieldList() 04: { 05: return new List<Schema.SObjectField> { 06: Opportunity.AccountId, Opportunity.Amount, Opportunity.CloseDate, Opportunity.Description, 07: Opportunity.ExpectedRevenue, Opportunity.Id, Opportunity.Name, Opportunity.Pricebook2Id, 08: Opportunity.Probability, Opportunity.StageName, Opportunity.Type, Opportunity.DiscountType__c 09: }; 10: } 11: public Schema.SObjectType getSObjectType() 12: { 13: return Opportunity.sObjectType; 14: } 15: public List<Opportunity> selectById(Set<ID> idSet) 16: { 17: return (List<Opportunity>) selectSObjectsById(idSet); 18: } 19: }
  • 19. Sample B: Data Mapper : Complex /** * This additional Selector method illustrates how other selectors can be consumed to ensure that * field lists from other objects queried via a parent object query are also consistently applied. * NOTE: The SObjectSelector logic is also multi-company aware, injecting CurrencyIsoCode as needed. */ 01: public List<Opportunity> selectByIdWithProducts(Set<ID> idSet) 02: { 03: /** Omitted for brevity: Construction of dependent selectors */ 04: 05: String query = String.format( 06: 'select {0}, 07: (select {3},{5},{6},{7} from OpportunityLineItems order by {4}) ' + 08: 'from {1} where id in :idSet order by {2}', 09: new List<String> { 10: getFieldListString(), getSObjectName(), getOrderBy(), 11: opportunityLineItemSelector.getFieldListString(), 12: opportunityLineItemSelector.getOrderBy(), 13: pricebookEntrySelector.getRelatedFieldListString('PricebookEntry'), 14: productSelector.getRelatedFieldListString('PricebookEntry.Product2'), 15: pricebookSelector.getRelatedFieldListString('PricebookEntry.Pricebook2’) }); 16: 17: return (List<Opportunity>) Database.query(query); 18: }
  • 20. Sample B: Domain Model : Trigger and Domain Class /** * Implement the Apex Trigger by calling the SObjectDomain method triggerHandler, which routes to the * appropriate methods implemented in the domain class. Such as applyDefaults() during * record inserts. Other methods are validate(), beforeInsert (), afterInsert() etc.. */ 01: trigger OpportunitiesTrigger on Opportunity 02: (after delete, after insert, after update, before delete, before insert, before update) 03: { 04: // Creates Domain class instance and calls appropriate override methods according to Trigger state 05: SObjectDomain.triggerHandler(Opportunities.class); 06: } 01: public with sharing class Opportunities extends SObjectDomain 02: { 03: public Opportunities(List<Opportunity> sObjectList) 04: { 05: super(sObjectList); // Classes are initialized with lists to enforce bulkification throughout 06: } 07: 08: public override void applyDefaults() 09: { 10: for(Opportunity opportunity : (List<Opportunity>) Records) // Apply defaults to Opportunities 11: { 12: opportunity.DiscountType__c = OpportunitySettings__c.getInstance().DiscountType__c; 13: } 14: } 15: }
  • 21. Sample B: Domain Model : Business Logic 01: public void applyDiscount(Decimal discountPercentage, SObjectUnitOfWork unitOfWork) /** 02: { * Domain class method implements 03: // Calculate discount factor * the discount logic, note 04: Decimal factor = Util.calculateDiscountFactor(discountPercentage); * bulkification is enforced here. 05: // Opportunity lines to apply discount to * Unit of Work for database 06: List<OpportunityLineItem> linesToApplyDiscount = new List<OpportunityLineItem>(); * updates. While delegating to 07: // Apply discount * the domain class responsible 08: for(Opportunity opportunity : (List<Opportunity>) Records) 09: { * for Opportunity Products to 10: // Apply to the Opportunity Amount? * apply discounts as needed to 11: if(opportunity.OpportunityLineItems.size()==0) * product lines. 12: { */ 13: // Adjust the Amount on the Opportunity if no lines 14: opportunity.Amount = opportunity.Amount * factor; 15: unitOfWork.registerDirty(opportunity); 16: } 17: else 18: { 19: // Collect lines to apply discount to 20: linesToApplyDiscount.add(opportunity.OpportunityLineItems); 21: } 22: } 23: // Apply discount to lines 24: OpportunityLineItems lineItems = new OpportunityLineItems(linesToApplyDiscount); 25: lineItems.applyDiscount(discountPercentage, unitOfWork); 26: }
  • 22. Summary Separation of Concern Pattern. Code is factored in respect to its purpose and responsibility and thus easier to maintain and behaves consistently. Service Pattern, task and process based logic is exposed consistently from a single place and consumed by all ‘clients’. Controllers and external callers. Data Mapper, the selector classes provide a single place to query objects and enforce CRUD security. Unit Of Work Pattern provides a single transactional context via the commitWork method. Bulkification of DML operations is simplified via registerXXX methods. Domain Pattern, provides object orientated model for defaulting, validation and behavioral logic. Since domain classes manage record sets, they also enforce bulkfication throughout your logic not just in triggers. Trigger implementations can be standardized.
  • 23. Resources Java Code : {SourceObject__c} Listener • Other // TODO:’s and Ideas?!  Review SObjectUnitOfWork further, it also aids in inserting related records and updating references!! Auto handle CRUD security in SObjectDomain base class trigger delegate methods? • Source Code and Contact Details GitHub: https://github.com/financialforcedev Twitter: andyinthecloud

Editor's Notes

  1. Rehab Breakfast on Friday, talking to people in the area for roles we have open, come along even if your tired! ;-)Expanding Development Group, adding Development Team in SFIndustrial Strength Applications
  2. Timing: 1 minuteNOTE: Method should return to Opportunity page?
  3. Timing: 1 minute
  4. Timing: 1 minuteNOTE: Probably easier to just say DiscountApproved__c == true and then do the UnitPrice adjustment?
  5. Timing: 1 minute
  6. Timing: 1 minute
  7. Timing: 2 minutes
  8. Timing: 2 minutes
  9. Timing: 1 minutes
  10. Timing: 2 minutes
  11. Timing: 4 minutes
  12. Timing: 4 minutes
  13. Timing: 1 minuteNOTE: Need to make the above field list reflect that used by the Sample A queries ideally.
  14. Timing: 1 minute
  15. Timing: 2 minutes
  16. Timing: 3 minutes
  17. Timing: 3 minutes
  18. Timing: Code Walkthrough 8 minutes