Skip to content

Commit 84ae1b0

Browse files
committed
Merge pull request #2 from financialforcedev/master
update
2 parents 53cf0fe + cf2126f commit 84ae1b0

36 files changed

+1085
-239
lines changed

LICENSE

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Copyright (c), FinancialForce.com, inc
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without modification,
5+
are permitted provided that the following conditions are met:
6+
7+
- Redistributions of source code must retain the above copyright notice,
8+
this list of conditions and the following disclaimer.
9+
- Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
- Neither the name of the FinancialForce.com, inc nor the names of its contributors
13+
may be used to endorse or promote products derived from this software without
14+
specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19+
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
FinancialForce Apex Common
22
==========================
33

4-
**[Deploy to Salesforce](https://githubsfdeploy.herokuapp.com/app/githubdeploy/financialforcedev/fflib-apex-common)**
4+
**Dependencies:** Must deploy [ApexMocks](https://github.com/financialforcedev/fflib-apex-mocks) before deploying this library
5+
6+
<a href="https://githubsfdeploy.herokuapp.com?owner=financialforcedev&repo=fflib-apex-common">
7+
<img alt="Deploy to Salesforce"
8+
src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png">
9+
</a>
510

611
See here for [MavensMate Templates](http://andyinthecloud.com/2014/05/23/mavensmate-templates-and-apex-enterprise-patterns/)
712

813
Updates
914
=======
1015

16+
- **September 2014**, **IMPORTANT CHANGE**, changes applied to support Dreamforce 2014 advanced presentation, library now provides Application factories for major layers and support for ApexMocks. More details to follow! As a result [ApexMocks](https://github.com/financialforcedev/fflib-apex-mocks) must be deployed to the org before deploying this library. The sample application [here](https://github.com/financialforcedev/fflib-apex-common-samplecode) has also been updated to demonstrate the new features!
1117
- **July 2014**, **IMPORTANT CHANGE**, prior **23rd July 2014**, both the ``fflib_SObjectDomain.onValidate()`` and ``fflib_SObjectDomain.onValidate(Map<Id, SObject> existingRecords)`` methods where called during an on **after update** trigger event. From this point on the ``onValidate()`` method will only be called during on **after insert**. If you still require the orignal behaviour add the line ``Configuration.enableOldOnUpdateValidateBehaviour();`` into your constructor.
1218
- **June 2014**, New classes providing utilities to support security and dynamic queries, in addition to improvements to existing Apex Enterprise Pattern base classes. Read more [here](http://andyinthecloud.com/2014/06/28/financialforce-apex-common-updates/).
1319
- **June 2014**, Experimental [branch](https://github.com/financialforcedev/fflib-apex-common/tree/fls-support-experiment) supporting automated FLS checking, see [README](https://github.com/financialforcedev/fflib-apex-common/tree/fls-support-experiment#expirimental-crud-and-fls-support) for more details.
@@ -27,20 +33,25 @@ Design patterns are an invaluable tool for developers and architects looking to
2733
Dreamforce Session and Slides
2834
-----------------------------
2935

30-
View slides for the **Dreamforce 2013** session [here](https://docs.google.com/file/d/0B6brfGow3cD8RVVYc1dCX2s0S1E/edit) and a video recording of the session [here](http://www.youtube.com/watch?v=qlq46AEAlLI).
36+
- View slides for the **Dreamforce 2013** session [here](https://docs.google.com/file/d/0B6brfGow3cD8RVVYc1dCX2s0S1E/edit)
37+
- Video recording of the **Dreamforce 2013** session [here](http://www.youtube.com/watch?v=qlq46AEAlLI).
38+
- Video recording of the **Advanced Apex Enterprise Dreamforce 2014** session [here](http://dreamforce.vidyard.com/watch/7QtP2628KmtXfmiwI-7B1w%20).
3139

3240
Documentation
3341
-------------
3442

3543
I'm proud to have been given the opportunity to run a more detailed look at these patterns on developer.force.com.
3644

45+
- [Unit Testing, Apex Enterprise Patterns and ApexMocks – Part 1](http://andyinthecloud.com/2015/03/22/unit-testing-with-apex-enterprise-patterns-and-apexmocks-part-1/)
46+
- [Unit Testing, Apex Enterprise Patterns and ApexMocks – Part 2](http://andyinthecloud.com/2015/03/29/unit-testing-apex-enterprise-patterns-and-apexmocks-part-2/)
3747
- [Apex Enterprise Patterns - Separation of Concerns](http://wiki.developerforce.com/page/Apex_Enterprise_Patterns_-_Separation_of_Concerns)
3848
- [Apex Enterprise Patterns - Service Layer](http://wiki.developerforce.com/page/Apex_Enterprise_Patterns_-_Service_Layer)
3949
- [Apex Enterprise Patterns - Domain Layer](http://wiki.developerforce.com/page/Apex_Enterprise_Patterns_-_Domain_Layer)
4050
- [Apex Enterprise Patterns - Selector Layer](https://github.com/financialforcedev/df12-apex-enterprise-patterns#data-mapper-selector)
4151

4252
**Other Related Blogs**
4353

54+
- [Preview of Advanced Apex Patterns Session (Application Factory and ApexMocks Features)](http://andyinthecloud.com/2014/08/26/preview-of-advanced-apex-enterprise-patterns-session/)
4455
- [Unit Testing with the Domain Layer](http://andyinthecloud.com/2014/03/23/unit-testing-with-the-domain-layer/)
4556
- [MavensMate Templates](http://andyinthecloud.com/2014/05/23/mavensmate-templates-and-apex-enterprise-patterns/)
4657
- [FinancialForce Apex Common Updates](http://andyinthecloud.com/2014/06/28/financialforce-apex-common-updates/)
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
/**
2+
* Copyright (c) 2014, FinancialForce.com, inc
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without modification,
6+
* are permitted provided that the following conditions are met:
7+
*
8+
* - Redistributions of source code must retain the above copyright notice,
9+
* this list of conditions and the following disclaimer.
10+
* - Redistributions in binary form must reproduce the above copyright notice,
11+
* this list of conditions and the following disclaimer in the documentation
12+
* and/or other materials provided with the distribution.
13+
* - Neither the name of the FinancialForce.com, inc nor the names of its contributors
14+
* may be used to endorse or promote products derived from this software without
15+
* specific prior written permission.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20+
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22+
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23+
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
**/
26+
27+
/**
28+
* Class provides inner classes implementing factories for the main components
29+
* of the Apex Enterprise Patterns, Service, Unit Of Work, Selector and Domain.
30+
* See the sample applications Application.cls file for an example
31+
**/
32+
public class fflib_Application
33+
{
34+
/**
35+
* Class implements a Unit of Work factory
36+
**/
37+
public class UnitOfWorkFactory
38+
{
39+
private List<SObjectType> m_objectTypes;
40+
private fflib_ISObjectUnitOfWork m_mockUow;
41+
42+
/**
43+
* Constructs a Unit Of Work factory
44+
*
45+
* @param objectTypes List of SObjectTypes in dependency order
46+
**/
47+
public UnitOfWorkFactory(List<SObjectType> objectTypes)
48+
{
49+
m_objectTypes = objectTypes.clone();
50+
}
51+
52+
/**
53+
* Returns a new fflib_SObjectUnitOfWork configured with the
54+
* SObjectType list provided in the constructor, returns a Mock implementation
55+
* if set via the setMock method
56+
**/
57+
public fflib_ISObjectUnitOfWork newInstance()
58+
{
59+
// Mock?
60+
if(m_mockUow!=null)
61+
return m_mockUow;
62+
return new fflib_SObjectUnitOfWork(m_objectTypes);
63+
}
64+
65+
@TestVisible
66+
private void setMock(fflib_ISObjectUnitOfWork mockUow)
67+
{
68+
m_mockUow = mockUow;
69+
}
70+
}
71+
72+
/**
73+
* Simple Service Factory implementaiton
74+
**/
75+
public class ServiceFactory
76+
{
77+
private Map<Type, Type> m_serviceInterfaceTypeByServiceImplType;
78+
79+
private Map<Type, Object> m_serviceInterfaceTypeByMockService;
80+
81+
/**
82+
* Constructs a simple Service Factory,
83+
* using a Map of Apex Interfaces to Apex Classes implementing the interface
84+
* Note that this will not check the Apex Classes given actually implement the interfaces
85+
* as this information is not presently available via the Apex runtime
86+
*
87+
* @param serviceInterfaceTypeByServiceImplType Map ofi interfaces to classes
88+
**/
89+
public ServiceFactory(Map<Type, Type> serviceInterfaceTypeByServiceImplType)
90+
{
91+
m_serviceInterfaceTypeByServiceImplType = serviceInterfaceTypeByServiceImplType;
92+
m_serviceInterfaceTypeByMockService = new Map<Type, Object>();
93+
}
94+
95+
/**
96+
* Returns a new instance of the Apex class associated with the given Apex interface
97+
* Will return any mock implementation of the interface provided via setMock
98+
* Note that this method will not check the configured Apex class actually implements the interface
99+
*
100+
* @param serviceInterfaceType Apex interface type
101+
* @exception Is thrown if there is no registered Apex class for the interface type
102+
**/
103+
public Object newInstance(Type serviceInterfaceType)
104+
{
105+
// Mock implementation?
106+
if(m_serviceInterfaceTypeByMockService.containsKey(serviceInterfaceType))
107+
return m_serviceInterfaceTypeByMockService.get(serviceInterfaceType);
108+
109+
// Create an instance of the type impleneting the given interface
110+
Type serviceImpl = m_serviceInterfaceTypeByServiceImplType.get(serviceInterfaceType);
111+
if(serviceImpl==null)
112+
throw new DeveloperException('No implementation registered for service interface ' + serviceInterfaceType.getName());
113+
return serviceImpl.newInstance();
114+
}
115+
116+
@TestVisible
117+
private void setMock(Type serviceInterfaceType, Object serviceImpl)
118+
{
119+
m_serviceInterfaceTypeByMockService.put(serviceInterfaceType, serviceImpl);
120+
}
121+
}
122+
123+
/**
124+
* Class implements a Selector class factory
125+
**/
126+
public class SelectorFactory
127+
{
128+
private Map<SObjectType, Type> m_sObjectBySelectorType;
129+
private Map<SObjectType, fflib_ISObjectSelector> m_sObjectByMockSelector;
130+
131+
/**
132+
* Consturcts a Selector Factory linking SObjectType's with Apex Classes implement the fflib_ISObjectSelector interface
133+
* Note that the factory does not chekc the given Apex Classes implement the interface
134+
* currently this is not possible in Apex.
135+
*
136+
* @Param sObjectBySelectorType Map of SObjectType's to Selector Apex Classes
137+
**/
138+
public SelectorFactory(Map<SObjectType, Type> sObjectBySelectorType)
139+
{
140+
m_sObjectBySelectorType = sObjectBySelectorType;
141+
m_sObjectByMockSelector = new Map<SObjectType, fflib_ISObjectSelector>();
142+
}
143+
144+
/**
145+
* Creates a new instance of the associated Apex Class implementing fflib_ISObjectSelector
146+
* for the given SObjectType, or if provided via setMock returns the Mock implementaton
147+
*
148+
* @param sObjectType An SObjectType token, e.g. Account.SObjectType
149+
**/
150+
public fflib_ISObjectSelector newInstance(SObjectType sObjectType)
151+
{
152+
// Mock implementation?
153+
if(m_sObjectByMockSelector.containsKey(sObjectType))
154+
return m_sObjectByMockSelector.get(sObjectType);
155+
156+
// Determine Apex class for Selector class
157+
Type selectorClass = m_sObjectBySelectorType.get(sObjectType);
158+
if(selectorClass==null)
159+
throw new DeveloperException('Selector class not found for SObjectType ' + sObjectType);
160+
161+
// Construct Selector class and query by Id for the records
162+
return (fflib_ISObjectSelector) selectorClass.newInstance();
163+
}
164+
165+
/**
166+
* Helper method to query the given SObject records
167+
* Internally creates an instance of the registered Selector and calls its
168+
* selectSObjectById method
169+
*
170+
* @param recordIds The SObject record Ids, must be all the same SObjectType
171+
* @exception Is thrown if the record Ids are not all the same or the SObjectType is not registered
172+
**/
173+
public List<SObject> selectById(Set<Id> recordIds)
174+
{
175+
// No point creating an empty Domain class, nor can we determine the SObjectType anyway
176+
if(recordIds==null || recordIds.size()==0)
177+
throw new DeveloperException('Invalid record Id\'s set');
178+
179+
// Determine SObjectType
180+
SObjectType domainSObjectType = new List<Id>(recordIds)[0].getSObjectType();
181+
for(Id recordId : recordIds)
182+
if(recordId.getSobjectType()!=domainSObjectType)
183+
throw new DeveloperException('Unable to determine SObjectType, Set contains Id\'s from different SObject types');
184+
185+
// Construct Selector class and query by Id for the records
186+
return newInstance(domainSObjectType).selectSObjectsById(recordIds);
187+
}
188+
189+
/**
190+
* Helper method to query related records to those provided, for example
191+
* if passed a list of Opportunity records and the Account Id field will
192+
* construct internally a list of Account Ids and call the registered
193+
* Account selector to query the related Account records, e.g.
194+
*
195+
* List<Account> accounts =
196+
* (List<Account>) Applicaiton.Selector.selectByRelationship(myOpps, Opportunity.AccountId);
197+
*
198+
* @param relatedRecords used to extract the related record Ids, e.g. Opportunty records
199+
* @param relationshipField field in the passed records that contains the relationship records to query, e.g. Opportunity.AccountId
200+
**/
201+
public List<SObject> selectByRelationship(List<SObject> relatedRecords, SObjectField relationshipField)
202+
{
203+
Set<Id> relatedIds = new Set<Id>();
204+
for(SObject relatedRecord : relatedRecords)
205+
{
206+
Id relatedId = (Id) relatedRecord.get(relationshipField);
207+
if(relatedId!=null)
208+
relatedIds.add(relatedId);
209+
}
210+
return selectById(relatedIds);
211+
}
212+
213+
@TestVisible
214+
private void setMock(fflib_ISObjectSelector selectorInstance)
215+
{
216+
m_sObjectByMockSelector.put(selectorInstance.sObjectType(), selectorInstance);
217+
}
218+
}
219+
220+
/**
221+
* Class implements a Domain class factory
222+
**/
223+
public class DomainFactory
224+
{
225+
private fflib_Application.SelectorFactory m_selectorFactory;
226+
227+
private Map<SObjectType, Type> m_sObjectByDomainConstructorType;
228+
229+
private Map<SObjectType, fflib_ISObjectDomain> m_sObjectByMockDomain;
230+
231+
/**
232+
* Consturcts a Domain factory, using an instance of the Selector Factory
233+
* and a map of Apex classes implementing fflib_ISObjectDomain by SObjectType
234+
* Note this will not check the Apex classes provided actually implement the interfaces
235+
* since this is not possible in the Apex runtime at present
236+
*
237+
* @param selectorFactory, e.g. Application.Selector
238+
* @param sObjectByDomainConstructorType Map of Apex classes by SObjectType
239+
**/
240+
public DomainFactory(fflib_Application.SelectorFactory selectorFactory,
241+
Map<SObjectType, Type> sObjectByDomainConstructorType)
242+
{
243+
m_selectorFactory = selectorFactory;
244+
m_sObjectByDomainConstructorType = sObjectByDomainConstructorType;
245+
m_sObjectByMockDomain = new Map<SObjectType, fflib_ISObjectDomain>();
246+
}
247+
248+
/**
249+
* Dynamically constructs an instance of a Domain class for the given record Ids
250+
* Internally uses the Selector Factory to query the records before passing to a
251+
* dynamically constructed instance of the application Apex Domain class
252+
*
253+
* @param recordIds A list of Id's of the same type
254+
* @exception Throws an exception via the Selector Factory if the Ids are not all of the same SObjectType
255+
**/
256+
public fflib_ISObjectDomain newInstance(Set<Id> recordIds)
257+
{
258+
return newInstance(m_selectorFactory.selectById(recordIds));
259+
260+
}
261+
262+
/**
263+
* Dynamically constructs an instace of the Domain class for the given records
264+
* Will return a Mock implementation if one has been provided via setMock
265+
*
266+
* @param records A concreate list (e.g. List<Account> vs List<SObject>) of records
267+
* @exception Throws an exception if the SObjectType cannot be determined from the list
268+
* or the constructor for Domain class was not registered for the SOBjectType
269+
**/
270+
public fflib_ISObjectDomain newInstance(List<SObject> records)
271+
{
272+
SObjectType domainSObjectType = records.getSObjectType();
273+
if(domainSObjectType==null)
274+
throw new DeveloperException('Unable to determine SObjectType');
275+
276+
// Mock implementation?
277+
if(m_sObjectByMockDomain.containsKey(domainSObjectType))
278+
return m_sObjectByMockDomain.get(domainSObjectType);
279+
280+
// Determine SObjectType and Apex classes for Domain class
281+
Type domainConstructorClass = m_sObjectByDomainConstructorType.get(domainSObjectType);
282+
if(domainConstructorClass==null)
283+
throw new DeveloperException('Domain constructor class not found for SObjectType ' + domainSObjectType);
284+
285+
// Construct Domain class passing in the queried records
286+
fflib_SObjectDomain.IConstructable domainConstructor =
287+
(fflib_SObjectDomain.IConstructable) domainConstructorClass.newInstance();
288+
return (fflib_ISObjectDomain) domainConstructor.construct(records);
289+
}
290+
291+
@TestVisible
292+
private void setMock(fflib_ISObjectDomain mockDomain)
293+
{
294+
m_sObjectByMockDomain.put(mockDomain.sObjectType(), mockDomain);
295+
}
296+
}
297+
298+
public class ApplicationException extends Exception { }
299+
300+
/**
301+
* Exception representing a developer coding error, not intended for end user eyes
302+
**/
303+
public class DeveloperException extends Exception { }
304+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>31.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>

0 commit comments

Comments
 (0)