Extensions

Introduction

Valence is designed to be extensible, and provide places for custom code and logic to be added. These extensions are gracefully integrated into the UI and behavior of Valence so that Users can configure and work with them as easily as they work with standard Valence features.

Deciding to add custom code does not compromise your ability to still configure, control, design, and observe everything using a nice, clean UI.

Valence uses Apex Interfaces to interact with extension Apex classes. These interfaces define a contract between the Valence framework and custom extensions that allows code developed at different times in different orgs to still work together.

In order for your custom code to be usable by Valence, you implement whichever interfaces match what you are trying to do.

As a design methodology, we have used many smaller interfaces to define discrete slices of functionality. You will almost certainly be implementing several interfaces each time you write a custom extension.

By combining your registration cMDT record and the interfaces your code implements the Valence framework can dynamically detect the presence of your extension and expose sophisticated behavior to end users.

Extension Requirements

In order to create your own custom extension, you need to do two things:

  1. Create a global Apex class that implements one or more Valence extension interfaces.
  2. Register your extension with the installed Valence app by creating a custom metadata type record in the appropriate table. This tells Valence that your extension exists and where to find it.

Types of Extensions

A Valence extension is an Apex class that can be hooked into Valence and called at specific points in time to run custom code. There are two types of Valence extensions: Adapters and Filters.

Adapters

An Adapter is an Apex class that knows how to talk to some external system or data store. Valence can make the connection to another system, but without an Adapter we wouldn’t know how to exchange information with that system. It’s as if Valence places a telephone call to someone that speaks Latin, and the Adapter translates the Latin into English.

Every Link defines two Adapters that it will use: a source Adapter to get data from, and a target Adapter to send data to. Some Adapters can only be used as a source, some only as a target, and some can be either.

Hint

You can use one Apex class and implement many interfaces (source + target), or split things up and have different Apex classes for source and for target. It’s up to you! Just remember that you need one cMDT registration record for each Apex class you create. The registration record is how Valence locates your Apex class to instantiate it.

Filters

A Filter is an Apex class that processes a data record as it moves through the Valence engine. It is an opportunity to observe and possibly manipulate records as they go by. Any number of Filters can be attached to a Link, and the order in which they fire (and any configuration of them) is controlled by the User setting up the Link.

Here are some example use cases for Filters:

  • Adding a constant value to records going by.
  • Manipulating date/time strings to be friendly to the target system.
  • Filtering out records that should be ignored and not processed.
  • Validating records for custom business logic that would add a warning or an error to a record.
  • Transforming record shape or field values.

Valence includes some Filters out of the box for you to use.

At a minimum you will write an Apex class that implements the TransformationFilter interface. Filters can be very, very simple or they can be quite sophisticated.

Filters are configured within a Link as a chain, where each is processed in turn and has access to whatever modifications earlier filters applied to the records (see: Intercepting Filter Pattern). This means Filters can be cumulative and build on the results of previous Filters. Users set the order in which Filters run as part of the configuration for a Link.

You can write sophisticated Filters that offer a Configuration Component for admins to configure your Filter behavior. We eat our own dog food, so if you’ve seen the UI for configuring the RelationshipFilter, for example, then you’ve seen ConfigurablePerMappingFilter with a custom UI component in action.

Interacting with Valence from your Custom Extension

Valence is an orchestration engine with lots of moving parts. When you build a custom extension, you are adding an additional cog to that machine. You tell Valence where your cog fits into the machine by implementing interfaces. These interfaces determine when your code will run, what values are passed to it, and what is expected back from it.

To facilitate sophisticated data exchange, a collection of global Valence classes are part of the method signatures of these interfaces.

Valence Classes

Class Description
AdapterException Special Exception class that we encourage you to use in your Adapters to indicate that something has gone wrong that cannot be recovered from.
CSVReader Utility class that makes it easier to parse raw CSV data
FetchStrategy FetchStrategy allows your source Adapter to tell Valence how best to ask your Adapter for records. It is an example of the Strategy Pattern https://en.wikipedia.org/wiki/Strategy_pattern.
Field The Field class represents a property that a record may have. It is analogous to a table column, or in Salesforce an object field.
Field Path FieldPath represents a the location of a Field within a schema tree, and is used to traverse that tree to find the right value.
FilterException Special Exception class that we encourage you to use in your Filters to indicate that something has gone wrong that cannot be recovered from.
JSONParse Utility class that makes it easier to parse raw JSON data
LinkContext This is a class that is full of information that might be useful to your Adapter or Filter while it is executing. It is an example of the Context Object pattern.
Mapping Mapping is a special Apex class that gives Adapters and Filters info about the mappings a user has defined for the Link.
Property Node PropertyNode helps us to interact with the data values on a record, and allows reading and writing using property path notation.
RecordInFlight RecordInFlight represents a single record as it moves through the Valence framework. RecordInFlight holds not just the record properties but also metadata such as errors and warnings associated with the record.
Table The Table class represents a possible source or target for a Link. It is analogous to a database table or Salesforce object.

Adapter Interfaces

Interface Source/Target Description
ChainFetchAdapter Source Implement if your Adapter is fetching data from a system that cannot predict in advance how many records (or how many batches) will be needed to retrieve all the records. This interface allows you to alternate record fetches with record processing indefinitely until all source records are exhausted.
ConfigurableSourceAdapter Source Implement if your Adapter is user-configurable when used as the source of records on a Link.
ConfigurableTargetAdapter Target Implement if your Adapter is user-configurable when used as the target of records on a Link.
DelayedPlanningAdapter Source Implement if your Adapter needs a bit of real-world time between when it is asked for data and when it is ready to serve that data.
LazyLoadSchemaAdapter Either Implement if your Adapter can load part of its schema independently and later in time. Helps to improve performance working with large schemas.
NamedCredentialAdapter Either Implement if your Adapter supports using a NamedCredential as its source of endpoint and credential info. Almost every Adapter will likely implement this interface.
SchemaAdapter Either Implement if your Adapter can describe its schema. This interface is not required, and in its absence Valence will still do what it normally does: discover record shape dynamically as records are processed.
SourceAdapterForPull Source Implement if your Adapter can be the source of records when fetching records from an external system. This is the most common Adapter variant.
SourceAdapterForRawDataPush Source Implement if your Adapter is parsing raw data (like a JSON packet) as the beginning of a Link run. You’d use this if you had real-time data packets arriving, for example via API into the Salesforce org.
SourceAdapterForSObjectPush Source Implement if your Adapter wants to convert sObject records into something that can be sent to another Adapter. Very unlikely that you would use this one.
SourceAdapterScopeSerializer Source Implementing this interface opens up several features for Users, such as parallel batch processing and replaying failed batches on Links where your Adapter was the source.
TargetAdapter Target Implement if your Adapter can be the endpoint of a Link, i.e. the place where records are sent at the end of processing.

Filter Interfaces

Interface Description
ConfigurablePerLinkFilter This interface allows a Filter to have a configuration applied to it that is different for each Mapping on the Link. This allows an admin to make the Filter behavior differently for each Mapping, or skip certain Mappings.
ConfigurablePerMappingFilter This interface allows a Filter to have a configuration applied to it that is different for each Mapping on the Link. This allows an admin to make the Filter behavior differently for each Mapping, or skip certain Mappings.
LinkSplitFilter Implement this interface if your Apex class is capable of helping to direct specific RecordInFlight instances to their matching Link Split destinations
SchemaAwareTransformationFilter This interface allows your Filter to describe its impact on the source and target schemas of records, and will better help Users understand what your Filter does.
TransformationFilter This is the primary interface to implement if you are building a Filter. It will allow your Apex class to inspect records that are being processed, and modify them if need be.

Register Your Extension With the Valence app

Tip

Registering a custom extensions is only necessary if you are writing the Apex code yourself! If you are simply installing a custom extension that someone else wrote, whomever developed it would have already followed these steps and included the cMDT record in their package alongside their Apex class.

Registering your extension tells Valence that it exists and what it is called, and also how to instantiate your Apex class. It is also an opportunity to specify some additional information about how your extension should be (and should not be) used.

To create this record go to your Setup menu and type “custom metadata types” in the Quick Find. Click “Manage Records” next to the custom metadata type you’re going to add a record to.

Register a Custom Adapter

Telling Valence about your custom Adapter is done by making a new record in the valence__ValenceAdapter__mdt custom metadata type.

../_images/register_your_class.png
API Name Label Description
DeveloperName Name The API name of this record, used whenever this record is edited or retrieved programmatically.
MasterLabel Label The user-friendly display label for this record.
ClassName__c Class Name The Apex class name of the Adapter class that will be instantiated.
Namespace__c Namespace The namespace of the Adapter class that will be instantiated. Does not have to be the same namespace as this cMDT record. If you are not packaging the Adapter, you can leave namespace blank.
Description__c Description A description of the purpose of this Adapter. Shown to users in the Valence UI.
RequiresNamedCredentialForSchema__c Requires NamedCredential for Schema Checked if this Adapter must be given access to a NamedCredential in order to return its Schema.
RequiresNamedCredentialForData__c Requires NamedCredential For Data Checked if this Adapter must be given access to a NamedCredential in order to exchange records with an external system.
IsTest__c Is Test Check this if this Adapter is used only for Apex tests, in which case you’ll also likely want to check the Protected Component field as well.

Register a Custom Filter

Telling Valence about your custom Filter is done by making a new record in the valence__ValenceFilter__mdt custom metadata type.

../_images/register_your_filter.png
API Name Label Description
DeveloperName Name The API name of this record, used whenever this record is edited or retrieved programmatically.
MasterLabel Label The user-friendly display label for this record.
ClassName__c Class Name The Apex class name of the Filter class that will be instantiated.
Namespace__c Namespace The namespace of the Filter class that will be instantiated. Does not have to be the same namespace as this cMDT record. If you are not packaging the Filter, you can leave namespace blank.
Description__c Description A description of the purpose of this Filter. Shown to Users in the Valence UI.
Default__c Default Checked if this Filter should automatically be included on any new Links that are created by Valence users.
DefaultSortOrder__c Default Sort Order If this Filter is used as a default, the starting Sort Order value that this Filter will have as part of a Link. This default can be overridden per Link.
BeforeMappingOnly__c Before Mapping Only Check this box if your Filter works with the original properties of a record and only makes sense to run before Mappings have moved values over to the properties side of RecordInFlight.
AfterMappingOnly__c After Mapping Only Check this box if your Filter works with the properties of a record and only makes sense to run after Mappings have moved values over to the properties side of RecordInFlight.

Packaging

Valence extensions, as well as configuration/customization done in the Valence UI, are packageable and deployable. Because extensions are Apex classes and registration is done with custom metadata types, you can create a Salesforce package for distribution that includes both registration records and the Apex classes they refer to.

At a minimum your package should include an Apex class and the matching cMDT record that registers your Apex class.

You can even go beyond this and package not just custom code but also Links, Mappings, etc. All Valence configuration is done with custom metadata types, which are themselves packageable.

Example:

Note

Let’s say you were a domain expert in the Real Estate business and you had a managed app that installed a custom object Property__c with a bunch of custom fields on it.

With Valence you could create a package that included:

  • A custom Adapter that knew how to talk to MLS servers (external servers that store listing information about real estate properties).
  • A pre-built and configured Link that pulled listing information out of the MLS server and put it into your custom Property__c object.
  • Pre-built mappings for all the usual fields that people need from the MLS to Property__c.

Not only could you package all this and install it for customers alongside your package, those customers could then further customize the Links and Mappings if they wanted to.