Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 26 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,52 @@
FFLib Apex Common Sample
========================
=====================================
![Push Source and Run Apex Tests](https://github.com/apex-enterprise-patterns/fflib-apex-common-samplecode/workflows/Create%20a%20Scratch%20Org,%20Push%20Source%20and%20Run%20Apex%20Tests/badge.svg)

**Dependencies:** Must deploy [Apex Mocks](https://github.com/apex-enterprise-patterns/fflib-apex-mocks) and [Apex Common](https://github.com/apex-enterprise-patterns/fflib-apex-common) before deploying this library

Deploy Apex Mocks
<a href="https://githubsfdeploy.herokuapp.com?owner=apex-enterprise-patterns&repo=fflib-apex-mocks">
<img alt="Deploy to Salesforce"
src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png">
</a>

Deploy Apex Common
<a href="https://githubsfdeploy.herokuapp.com?owner=apex-enterprise-patterns&repo=fflib-apex-common">
<img alt="Deploy to Salesforce"
src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png">
</a>

Deploy Apex Common Sample Code
<a href="https://githubsfdeploy.herokuapp.com?owner=apex-enterprise-patterns&repo=fflib-apex-common-samplecode">
<img alt="Deploy to Salesforce"
src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png">
</a>
| Library | Deploy |
|---------|--------|
| Apex Mocks | <a href="https://githubsfdeploy.herokuapp.com?owner=apex-enterprise-patterns&repo=fflib-apex-mocks"><img alt="Deploy to Salesforce" src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png"></a> |
| Apex Common | <a href="https://githubsfdeploy.herokuapp.com?owner=apex-enterprise-patterns&repo=fflib-apex-common"><img alt="Deploy to Salesforce" src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png"></a> |
| Apex Common Sample Code | <a href="https://githubsfdeploy.herokuapp.com?owner=apex-enterprise-patterns&repo=fflib-apex-common-samplecode"><img alt="Deploy to Salesforce" src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png"></a> |

Sample Application
==================

This repository contains a sample application illustrating the Apex Enterprise Patterns library. The aim is to illustrate fullly working sample application to better illustrate the patterns in presentations and articles. You can see a [demo of this application in the Dreamforce 2013 session](http://www.youtube.com/watch?v=qlq46AEAlLI#t=572) presented by @afawcett
This repository contains a sample application illustrating the Apex Enterprise Patterns library. The aim is to illustrate a fully working sample application that demonstrates the patterns.

**NOTE:** The supporting **Apex Common** library can be found [here](https://github.com/apex-enterprise-patterns/fflib-apex-common).

![Alt text](/images/sampleappoverview.png "Optional title")
| Platform Feature | Patterns Used |
|------------------|---------------|
| Custom Buttons | Building **UI** logic and calling **Service Layer** code from Controllers |
| Batch Apex | Reusing **Service** and **Selector Layer** code from within a Batch context |
| Integration API | Exposing an Integration API via **Service Layer** using Apex and REST |
| Apex Triggers | Factoring your Apex Trigger logic via the **Domain Layer** (wrappers) |

User Mode and CRUD/FLS
----------------------

This sample enforces field-level security (FLS) by using fflib user mode throughout. Selectors pass `fflib_SObjectSelector.DataAccess.USER_MODE` into the `super(...)` constructor for FLS on queries; the UnitOfWork uses `UserModeDML()` via an inner `UserModeUnitOfWorkFactory` in `Application.cls` for FLS on DML. Tests that exercise USER_MODE code use `TestDataFactory` to create a Standard User with the `ApexEnterprisePatternsSampleApp` permission set, then run under `System.runAs(getRunAsUser())`; data setup requiring elevated permissions (e.g. PricebookEntry insert) runs in system context.

| Area | Approach |
|------|----------|
| **Selectors** | `super(false, fflib_SObjectSelector.DataAccess.USER_MODE)` in selector constructors (and `includeFieldSetFields` overload where present) |
| **UnitOfWork** | Inner `UserModeUnitOfWorkFactory` in Application.cls; uses `UserModeDML()` |
| **Tests** | `TestDataFactory`; `@TestSetup` + `System.runAs(getRunAsUser())` for DML/selector tests |
| **Permission set** | `ApexEnterprisePatternsSampleApp` grants field-level access for USER_MODE tests |

Application Enterprise Patterns on Salesforce Lightning Platform
================================================================

Design patterns are an invaluable tool for developers and architects looking to build enterprise solutions. Here are presented some tried and tested enterprise application engineering patterns that have been used in other platforms and languages. We will discuss and illustrate how patterns such as Data Mapper, Service Layer, Unit of Work and of course Model View Controller can be applied to Force.com. Applying these patterns can help manage governed resources (such as DML) better, encourage better separation-of-concerns in your logic and enforce Force.com coding best practices.

Dreamforce Session and Slides
-----------------------------

- View slides for the **Dreamforce 2013** session [here](https://docs.google.com/file/d/0B6brfGow3cD8RVVYc1dCX2s0S1E/edit)
- Video recording of the **Dreamforce 2014** advanced session [here](https://www.youtube.com/watch?v=BLXp0ZP0cF0).
- View slides for the **Dreamforce 2015** session [here](http://www.slideshare.net/andyinthecloud/building-strong-foundations-apex-enterprise-patterns)

More Information on Trailhead
--------------------------------------------

There are two Trailhead Modules for Apex Enterprise Patterns:

- [Apex Enterprise Patterns - Separation of Concerns](https://trailhead.salesforce.com/en/content/learn/modules/apex_patterns_sl/apex_patterns_sl_soc)
- [Apex Enterprise Patterns - Service Layer](https://trailhead.salesforce.com/en/content/learn/modules/apex_patterns_sl)
- [Separation of Concerns](https://trailhead.salesforce.com/en/content/learn/modules/apex_patterns_sl/apex_patterns_sl_soc)
- [Apex Enterprise Patterns - Domain and Selector Layer](https://trailhead.salesforce.com/en/content/learn/modules/apex_patterns_dsl)

Binary file removed images/sampleappoverview.png
Binary file not shown.
17 changes: 13 additions & 4 deletions sfdx-source/apex-common-samplecode/main/classes/Application.cls
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@

public class Application
{
// Configure and create the UnitOfWorkFactory for this Application
public static final fflib_Application.UnitOfWorkFactory UnitOfWork =
new fflib_Application.UnitOfWorkFactory(
// Configure and create the UnitOfWorkFactory for this Application (UserMode DML for FLS)
public static final fflib_Application.UnitOfWorkFactory UnitOfWork =
new UserModeUnitOfWorkFactory(
new List<SObjectType> {
Account.SObjectType,
Invoice__c.SObjectType,
Expand All @@ -40,7 +40,7 @@ public class Application
WorkOrder__c.SObjectType,
DeveloperWorkItem__c.SObjectType,
TrainingWorkItem__c.SObjectType,
DesignWorkItem__c.SObjectType });
DesignWorkItem__c.SObjectType });

// Configure and create the ServiceFactory for this Application
public static final fflib_Application.ServiceFactory Service =
Expand Down Expand Up @@ -71,4 +71,13 @@ public class Application
OpportunityLineItem.SObjectType => OpportunityLineItems.Constructor.class,
Account.SObjectType => Accounts.Constructor.class,
DeveloperWorkItem__c.SObjectType => DeveloperWorkItems.class });

// Custom UnitOfWork factory that uses UserModeDML for FLS enforcement
private class UserModeUnitOfWorkFactory extends fflib_Application.UnitOfWorkFactory {
public UserModeUnitOfWorkFactory(List<SObjectType> objectTypes) { super(objectTypes); }
public override fflib_ISObjectUnitOfWork newInstance() {
if (m_mockUow != null) return m_mockUow;
return new fflib_SObjectUnitOfWork(m_objectTypes, new fflib_SObjectUnitOfWork.UserModeDML());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,11 @@ public with sharing class OpportunityLineItems extends fflib_SObjectDomain
continue;
}
}

// Adjust UnitPrice
line.UnitPrice = line.UnitPrice * factor;

// Register this record as dirty with the unit of work
uow.registerDirty(line);

// Minimal record (Id + UnitPrice only): USER_MODE DML enforces FLS on every field in the record; queried records carry OpportunityId, PricebookEntryId, etc. that trigger "fields being inaccessible".
OpportunityLineItem updateOli = new OpportunityLineItem(Id = line.Id);
updateOli.UnitPrice = line.UnitPrice * factor;
uow.registerDirty(updateOli);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ public class AccountsSelector extends fflib_SObjectSelector
{
return (IAccountsSelector) Application.Selector.newInstance(Account.SObjectType);
}


public AccountsSelector()
{
super(false, fflib_SObjectSelector.DataAccess.USER_MODE);
}

public List<Schema.SObjectField> getSObjectFieldList()
{
return new List<Schema.SObjectField> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ public class OpportunitiesSelector extends fflib_SObjectSelector
{
return (IOpportunitiesSelector) Application.Selector.newInstance(Opportunity.SObjectType);
}


public OpportunitiesSelector()
{
super(false, fflib_SObjectSelector.DataAccess.USER_MODE);
}

public List<Schema.SObjectField> getSObjectFieldList()
{
return new List<Schema.SObjectField> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ public class OpportunityLineItemsSelector extends fflib_SObjectSelector
{
return (IOpportunityLineItemsSelector) Application.Selector.newInstance(OpportunityLineItem.SObjectType);
}


public OpportunityLineItemsSelector()
{
super(false, fflib_SObjectSelector.DataAccess.USER_MODE);
}

public List<Schema.SObjectField> getSObjectFieldList()
{
return new List<Schema.SObjectField> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ public class PricebookEntriesSelector extends fflib_SObjectSelector
{
return (IPricebookEntriesSelector) Application.Selector.newInstance(PricebookEntry.SObjectType);
}


public PricebookEntriesSelector()
{
super(false, fflib_SObjectSelector.DataAccess.USER_MODE);
}

public List<Schema.SObjectField> getSObjectFieldList()
{
return new List<Schema.SObjectField> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ public class PricebooksSelector extends fflib_SObjectSelector
{
return (IPricebooksSelector) Application.Selector.newInstance(Pricebook2.SObjectType);
}


public PricebooksSelector()
{
super(false, fflib_SObjectSelector.DataAccess.USER_MODE);
}

public List<Schema.SObjectField> getSObjectFieldList()
{
return new List<Schema.SObjectField> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public class ProductsSelector extends fflib_SObjectSelector

public ProductsSelector()
{
super(false);
super(false, fflib_SObjectSelector.DataAccess.USER_MODE);
}

public ProductsSelector(Boolean includeFieldSetFields)
{
super(includeFieldSetFields);
super(includeFieldSetFields, fflib_SObjectSelector.DataAccess.USER_MODE);
}

public List<Schema.SObjectField> getSObjectFieldList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ public class UsersSelector extends fflib_SObjectSelector
{
return (IUsersSelector) Application.Selector.newInstance(User.SObjectType);
}


public UsersSelector()
{
super(false, fflib_SObjectSelector.DataAccess.USER_MODE);
}

public List<Schema.SObjectField> getSObjectFieldList()
{
// Current app requirements driven solely by getUsersEmail at this stage
Expand Down
Loading
Loading