This Framework is an accelerator that I’m working on since years and I’ve started to rethink the design based on project experience. The principal objectives is to bring guidelines to develop faster, to reduce code, to reuse components, to ensure high quality, to be compliant with governor limits, to be atomic, to be DRY compliant.
Apex Code Design Pattern
I’ve drawn the following schema almost 7 years ago and I’m still using it with some adjustments.
This pattern is inspired from great principles from JAVA/SPRING/HIBERNATE world. It was adjusted to Salesforce in order to improve development in APEX.
The code is split into layers, each one is responsible of handling a restricted perimeter of features. Vertical path is used from top to bottom to avoid cyclic and cross references, horizontal path is generally not allowed except in certain circumstances.
Layer : Various
This layer represents an independent level allowing to write libraries or classes without any dependency with other components. This layer will be mainly composed of utility classes (ex StringUtils) or Wrappers which are used to encapsulate information from different sources or in a specific format.
TestDataFactory is also in this layer because it main role is to create test data for every test classes.
Layer : Database
By default, all components interact with Salesforce database. But, more and more, data are not stored in Salesforce but in external systems that can be accessed by Webservices (SOAP/REST) , External Objects (OData) …
A test context can be created for every SObject within Salesforce, in which access to real data is not allowed (seeAllData=true is prohibited). CRUD operations in test context will not have any incidence on real data. On the other hand, when we deal with external systems or databases, it’s not possible to guarantee reversibility of any action which will result in critical impact on the external system sanity (data loss, corruption …). Fortunately, Salesforce does not allow to interact with external systems in test context, but this can have incidences on the developed feature that needs this callout to go further on and then cannot be fully tested (An exception will be fired when 1st callout is made). To come with this issue, we can create MOCK that will simulate the behavior of the calling system and bring test data to ensure the continuity of the process in test context.
Writing MOCK classes can consume code characters and for this reason we will tag them with the @isTest annotation. Test classes and comments do not count against code characters limit.
Layer : Data Manager
This layer is the unique and only point of interaction between code and data within Salesforce or from external systems. Each SObject will be mapped with one unique Data Manager class (DM) and each external system service will be mapped or encapsulated in one unique DM. After writing a bunch of code for a bunch of SObject, I have found this approach a little bit painful and sometime it does not bring that much. Finally, what you really need is one generic DM for each kind of service (SObject, Rest API, External Object…), we will try to mutualize them as much as possible to reduce the code we write. Finally, virtually you will be on a 1:1 pattern but physically you will be on 1:N.
This approach will guarantee a unique repository for your SOQL queries and DML statements avoiding having those duplicated in many classes. This layer is logic less and dedicated to raw access on data or external systems.
In order to write a good mocking system, we need to ensure that DM will implement the same interface as the mock, containing the signature of each callable method.
Finally, one main rule is to cover all DM with a coverage of 100% because it will be the technical base of your entire application.
Layer : Entity Manager
This layer allows to manipulate data coming from DM. Each Entity Manager (EM) is only associated to one and only one DM (which can be replaced by a mock on the fly).
The EM is responsible of handling data coming from its DM, it can contain methods to retrieve data or collection of data, to test picklist values, to retrieve related constants, and more importantly it contains an instance of the DM from which we can access methods to manipulate corresponding data (CRUD).
The data manager instance is in majority an instance of the DM for SObjects, and instance of the webservice manager for external system or an instance of the mock class for unit testing. It’s important that EM instantiates the right DM according to the context of execution to avoid rising exception (Ex: callout in test context).
Finally, one main rule is to cover all EM with a coverage of 100% because it will be also part of the technical base of your entire application.
Layer : Service Manager
This layer will implement all features for any business or technical requirement. The Service Manager (SM) will have direct access to all methods of all EM.
This layer will handle transaction behavior, error management (EM & DM will propagate errors without any handling), to orchestrate calls to external system and internal database, to aggregate results.
Every errors from below layers must be thrown to this layer in order to decide of the type of rejection to handle (partial or total) and to put in place replay/notification mechanisms.
Every SM can transform data from one format to another, to develop complex algorithms and handle any Salesforce governor limits.
Finally, one main rule is to cover all SM with a coverage of 100% with a functional approach to ensure that developed feature behave the way it has been designed and required.
Layer : Facade
This layer is the point of interaction between user/system and the requested service. It’s an orchestrator which goal is to drive information and bring results.
There is no functional or technical logic inside. However, it will be useful to test performance of the developed feature in the dedicated execution context:
- Batch : Scheduled job handling a high volume of data, the requested service must be compliant with this context. The batch will throttle requests by splitting data into chunk that will be compatible with the requested service.
- Trigger : Fired by an event on database and handling up to 200 records per trigger call, the requested service must be compliant with this context.
- VisualForce / Lightning : Fired by an user action on screen and handling generally 1 record at a time. The visual or lightning controller should be the point of interaction between the page and the requested service.
- Webservice : Fired by an external system and handling a various amount of data depending on the design. A webservice will expose a contract/resource that will point to the corresponding requested service.
- Process Builder, Flow, Email Service …. : There are many other ways to call SM, so the design should be bulkified to handle each situation or a dedicated service should be implemented depending on the context of execution.
Facade needs to be covered by unit test to ensure the scalability depending on volume.
Lightning Code Design Pattern
A lightning application is composed of many lightning components that help rendering components dynamically on a page (which is not possible with visualforce page) following SPA principles (Single Page Application). Each lightning component is delivered in independent bundle which contains all the code and customization. The event makes communication possible between them, one component can fire an event for which another has subscribed.
Each lightning component is atomic and have a clear role, the following framework highlights the interaction between each component and is an extension of the core framework:
|view||Component||This component is the view part of the MVC model, it’s composed of all aura and html markup allowing to build a custom page. This component is tightly coupled with Apex Controller.|
|view||CSS||Write all css used to format the lightning component.|
|view||Event||Allows to establish communication between components with a publish/subscribe model.|
|N/A||Design||Allows to declare all attributes available in Lightning App Builder.|
|N/A||SVG||Allows to define an icon for this application on the Lightning App Builder.|
|Client Side |
|Renderer||Allows to implement overload of methods like render, rerender, afterrender et unrender and allow to manipulate the DOM of the lightning component.|
|Server Side |
|Helper||It’s the bridge between client side js controller and server side apex controller, that’s why we call it server side js controller. It goals is to expose methods from each side and orchestrate the whole. A callback system is put in place that sends request and retrieves response in JSON format which can be parsed and transform into variables. It can handle multiple calls without refreshing the page.|
|Server Side |
|Apex||It’s the bridge between lightning component and apex services (SM). Attributs and methods of the apex controller must be static and with @AuraEnabled annotation to be visible from the helper.|
A sharing annotation has to be defined in each class to secure the access on records. But some use cases need to be handle by deactivation sharing control. If one class is restricted, it will be hard to open up the access, we often have to duplicate the code elsewhere. To avoid this, we should go with inheritance and specify the restriction at the controller level, so that for one implementation you could switch between 2 controllers, one having sharing enforced and another with permissive rights.
|Class Family||Sharing level|
|Batch||without sharing : Batch runs with a dedicated user that needs to access all data|
|Controller||Choose here which type of access you want to give with sharing or without sharing.|
|Service, Entity, Data Manager, Utility …||inherited sharing : the controller will assign the visibility level. Well we can say that inherited sharing can be set to all classes.|
The objectives of this article is to highlights the core framework and to give you enough knowledge to understand it and maybe to put in place in your projects. Patterns drive the quality and the robustness of the whole application and Salesforce is not an exception. You should take this as a starting point and experiment yourself, there are many path, you will adjust as you go depending on constraints and also on new releases.
Hope you enjoy reading this article, see you soon for the next one ...