Apex SDK documentation
The InCountry Apex SDK allows users of the InCountry Data Residency for Salesforce package to make CRUD (create/read/update/delete) operations with their regulated data stored in InCountry PoP's (Points of Presence). The Apex SDK is provided as a part of the InCountry Data Residency for the Salesforce app. All the SDK components are defined as global Apex classes. That’s why the SDK can be used by the app subscribers within their custom code solutions.
The InCountry platdorm uses the testInCountry1
namespace prefix so please make sure you use this prefix when initiating any of the APEX SDK methods.
For example: testInCountry1.InCountry.getInstance();
Supported data regulation models
The InCountry Apex SDK supports the replication and restriction data regulation models. The Apex SDK provides the following operations on protected data depending on the used data regulation model:
Internally, the Apex SDK automatically differentiates records by country and makes callouts to InCountry Rest API.
InCountry Rest API endpoints must be registered within the InCountryRestAPIEndpoint
custom metadata type and added into the remote site settings to allow server-side callouts. This guide does not cover the initial setup and configuration of InCountry Data Residency for Salesforce. You can find all the relevant information about this at our documentation site.
Quick Start Guide
To access operations available in InCountry PoP, you need to create an instance of the InCountry
class. You should prepend the InCountry Managed Package namespace to the class name, like <namespace>.InCountry
. For example, if the namespace of the InCountry Data Residency for Salesforce package is testIncountry1
, you need to use the following class name: testIncountry1.InCountry
.
All the following code samples below are provided without a namespace. Please prepend the InCountry Namespace to class names.
How to instantiate the Apex SDK
You need to execute the static getInstance()
method from the InCountry
class. It returns an instance of the Apex SDK.
InCountry service = InCountry.getInstance();
How to find records stored in the InCountry platform
You can find records stored in the InCountry platform by using two different approaches:
-
by records ids;
-
by records instances.
The Apex SDK will automatically split records by country and make callouts to appropriate InCountry Points of Presence located in each target country. For example, if you want to fetch two leads stored in InCountry Sweden
and one account stored in InCountry Russia
, the Apex SDK will make two callouts: one callout to Sweden and the other callout to Russia.
Finding by records ids
In this scenario, you should provide a set of record ids. Please consider that the Apex SDK will perform additional SOQL queries to retrieve records from the Salesforce database. For example, if you provide three account Ids and five lead Ids, the SDK will perform two additional SOQL queries to retrieve accounts and leads from the database. These SOQL queries are required because the SDK should fetch all the regulated field values and the record originating countries from the database. In the following example, the SDK will fetch two leads and one account from the InCountry platform.
InCountry service = InCountry.getInstance();
Set<Id> input = new Set<Id>{'00Q5w00001vsLjVEAU', '00Q5w00001vrzjQEAQ', '0015w00002OUr9uAAD'};
List<InCountry.FindResult> results = service.find(input);
Finding by record instances
If you have queried all the necessary records from the Salesforce database, you can provide the record instances to the find()
method so that the SDK will skip additional SOQL queries. Please ensure that all the required fields are included in your SOQL queries. The Apex SDK requires all the regulated fields and the Source Country Field
field to use the record-level policy for determining the record originating country.
InCountry service = InCountry.getInstance();
List<Lead> input = [SELECT Id, FirstName, Email,
Phone, Country_Field__c
FROM Lead
ORDER BY CreatedDate DESC
LIMIT 10];
List<InCountry.FindResult> results = service.find(input);
The FindResult
class contains data returned from the InCountry platform:
@JsonAccess(serializable='always' deserializable='always')
global class FindResult {
@AuraEnabled global List<RecordData> data;
@AuraEnabled global Map<String, String> meta;
}
The RecordData
class represents a single Salesforce record stored in the InCountry platform:
@JsonAccess(serializable='always' deserializable='always')
global class RecordData {
@AuraEnabled global String key;
@AuraEnabled global String record_key;
@AuraEnabled global String profile_key;
@AuraEnabled global String range_key;
@AuraEnabled global String body;
@AuraEnabled global String key1;
@AuraEnabled global String key2;
@AuraEnabled global String key3;
@AuraEnabled global String key4;
@AuraEnabled global String key5;
@AuraEnabled global String key6;
@AuraEnabled global String key7;
@AuraEnabled global String key8;
@AuraEnabled global String key9;
@AuraEnabled global String key10;
@AuraEnabled global String key11;
@AuraEnabled global String key12;
@AuraEnabled global String key13;
@AuraEnabled global String key14;
@AuraEnabled global String key15;
@AuraEnabled global String key16;
@AuraEnabled global String key17;
@AuraEnabled global String key18;
@AuraEnabled global String key19;
@AuraEnabled global String key20;
@AuraEnabled global String created_at;
@AuraEnabled global String updated_at;
@AuraEnabled global String expires_at;
@AuraEnabled global List<FileData> attachments;
}
The @AuraEnabled global String expires_at;
snippet is not available for the restriction data regulation model.
The FileData
class represents a single file linked to a specific InCountry record:
@JsonAccess(serializable='always' deserializable='always')
global class FileData {
@AuraEnabled global String file_id;
@AuraEnabled global String filename;
@AuraEnabled global String hash;
@AuraEnabled global String mime_type;
@AuraEnabled global Integer size;
@AuraEnabled global String created_at;
@AuraEnabled global String updated_at;
@AuraEnabled global String download_link;
@AuraEnabled global String error;
}
How to write records into the InCountry platform
Like the find
operation, you can use two different methods to write single Salesforce records into the InCountry platform.
Writing by record ids
Please consider that the Apex SDK will perform an additional SOQL query to fetch a salesforce database record. If the current user on behalf of whom an operation is executed has no sufficient permissions to the record id, an exception will be thrown.
InCountry service = InCountry.getInstance();
InCountry.WriteRequest result = service.write('00Q5w00001vrzjQEAQ');
Writing by record instance
Before executing the write(SObject record)
method, ensure that you have included all the necessary regulated and country fields into your SOQL queries.
InCountry service = InCountry.getInstance();
Lead input = [SELECT Id, FirstName, Email, Phone, Country_Field__c
FROM Lead
LIMIT 1];
input.FirstName = 'ChangedValue';
InCountry.WriteRequest result = service.write(input);
The WriteRequest
class represents a confirmation which the InCountry platform returns upon a successful execution of the write request.
@JsonAccess(serializable='always' deserializable='always')
global class WriteRequest {
@AuraEnabled global String country;
@AuraEnabled global String profile_key;
@AuraEnabled global String body;
@AuraEnabled global String record_key;
@AuraEnabled global String key;
@AuraEnabled global String key1;
@AuraEnabled global String key2;
@AuraEnabled global String key3;
@AuraEnabled global String key4;
@AuraEnabled global String key5;
@AuraEnabled global String key6;
@AuraEnabled global String key7;
@AuraEnabled global String key8;
@AuraEnabled global String key9;
@AuraEnabled global String key10;
@AuraEnabled global String key11;
@AuraEnabled global String key12;
@AuraEnabled global String key13;
@AuraEnabled global String key14;
@AuraEnabled global String key15;
@AuraEnabled global String key16;
@AuraEnabled global String key17;
@AuraEnabled global String key18;
@AuraEnabled global String key19;
@AuraEnabled global String key20;
@AuraEnabled global String expires_at;
}
The @AuraEnabled global String expires_at;
snippet is not available for the restriction data regulation model.
How to write multiple records into the InCountry platform
The Apex SDK performs requests to a separate /api/records/batch
endpoint in InCountry Rest API to write multiple records to the InCountry platform to perform batch write operations. The Apex SDK will automatically split records by country and will make several callouts to different InCountry Points of Presence in the target countries. For example, if you want to write two Leads to InCountry Sweden
and one account to InCountry Russia
, the Apex SDK will make two callouts, as follows: one callout to Sweden and the other callout to Russia.
Writing a batch of records by records ids
Please consider that InCountry SDK will perform additional SOQL queries to fetch records from the Salesforce database. If the current user on behalf of whom an operation is executed has no sufficient permissions to record id, an exception will be thrown.
For example, if you provide three account ids and five lead ids, the SDK will perform two additional SOQL queries to fetch accounts and leads from the database. These SOQL queries are required because the Apex SDK should take all the regulated field values and record originating countries from the database.
InCountry service = InCountry.getInstance();
Set<Id> input = new Set<Id>{'00Q5w00001vsLjVEAU', '00Q5w00001vrzjQEAQ', '0015w00002OUr9uAAD'};
List<InCountry.WriteRequest> result = service.write(input);
Writing a batch of records by record instances
Before executing the write(List<SObject> records)
method, ensure that you have all the required regulated and country fields included in your SOQL queries.
InCountry service = InCountry.getInstance();
List<Lead> input = [SELECT Id, FirstName, Email, Phone, Country_Field__c
FROM Lead
LIMIT 40];
for (Lead leadRecord : input) {
leadRecord.Phone = '+37529000000';
}
List<InCountry.WriteRequest> result = service.write(input);
How to update multiple records on the InCountry platform
The Apex SDK performs requests to a separate /api/records/batch/update
endpoint in InCountry Rest API to update multiple records to the InCountry platform. The Apex SDK will automatically split records by country and will make several callouts to different InCountry Points of Presence in the target countries.
Updating a batch of records by record instances
To update certain fields you have to add them in your SOQL request and then execute the update(List<SObject> records)
method.
InCountry service = InCountry.getInstance();
List<Lead> input = [SELECT Id, Phone FROM Lead LIMIT 5];
for (Lead leadRecord : input) {
leadRecord.Phone = '+37529000000';
}
List<InCountry.WriteRequest> result = service.updateRecords(input);
How to synchronize single records with the InCountry platform
Like the find
and write
operations, you can use two different methods to synchronize single Salesforce records with the InCountry platform.
Synchronizing by record ids
Please consider that the Apex SDK will perform an additional SOQL query to fetch a record from the Salesforce database. If the current user on behalf of whom an operation is executed has no sufficient permissions to the record id, an exception will be thrown. Once the Apex SDK has written a data record into the InCountry platform, it will perform a DML query to update a record in the Salesforce database with hashed values.
InCountry service = InCountry.getInstance();
InCountry.WriteRequest result = service.sync('00Q5w00001vrzjQEAQ');
Synchronizing by record instance
Before executing the sync(SObject record)
method, ensure that you have included all the necessary regulated and country fields into your SOQL queries. Once the Apex SDK has written a data record into the InCountry platform, it will perform a DML query to update a record in the Salesforce database with hashed values.
InCountry service = InCountry.getInstance();
Lead input = [SELECT Id, FirstName, Email, Phone, Country_Field__c
FROM Lead
LIMIT 1];
input.FirstName = 'ChangedValue';
InCountry.WriteRequest result = service.sync(input);
The WriteRequest
class represents a confirmation which the InCountry platform returns upon a successful execution of the write request.
@JsonAccess(serializable='always' deserializable='always')
global class WriteRequest {
@AuraEnabled global String country;
@AuraEnabled global String profile_key;
@AuraEnabled global String body;
@AuraEnabled global String record_key;
@AuraEnabled global String key;
@AuraEnabled global String key1;
@AuraEnabled global String key2;
@AuraEnabled global String key3;
@AuraEnabled global String key4;
@AuraEnabled global String key5;
@AuraEnabled global String key6;
@AuraEnabled global String key7;
@AuraEnabled global String key8;
@AuraEnabled global String key9;
@AuraEnabled global String key10;
@AuraEnabled global String key11;
@AuraEnabled global String key12;
@AuraEnabled global String key13;
@AuraEnabled global String key14;
@AuraEnabled global String key15;
@AuraEnabled global String key16;
@AuraEnabled global String key17;
@AuraEnabled global String key18;
@AuraEnabled global String key19;
@AuraEnabled global String key20;
@AuraEnabled global String expires_at;
}
Additionally, be aware that the write(SObject)
, write(List<SObject>)
, sync(SObject)
, and sync(List<SObject>)
methods discard all data not included in the SOQL query, because it overrides all fields on incountry record. It might be useful to consider using the sync(Id)
and sync(List<Id>)
methods instead.
How to synchronize multiple records with the InCountry platform
The Apex SDK performs requests to a separate /api/records/batch
endpoint in InCountry Rest API to write multiple records to the InCountry platform to perform batch write operations. The Apex SDK will automatically split records by country and will make several callouts to different InCountry Points-of-Presence in the target countries. For example, if you want to write two Leads to InCountry Sweden
and one account to InCountry Russia
, the Apex SDK will make two callouts, as follows: one callout to Sweden and the other callout to Russia. Once the Apex SDK has written data records into the InCountry platform it will perform A DML query to update records in the Salesforce database with hashed values.
Synchronizing multiple records by records ids
Please consider that the Apex SDK will perform additional SOQL queries to fetch records from the Salesforce database. If the current user on behalf of whom an operation is executed has no sufficient permissions to record id, an exception will be thrown.
For example, if you provide three account ids and five lead ids, the Apex SDK will perform two additional SOQL queries to fetch accounts and leads from the database. These SOQL queries are required because the Apex SDK should take all the regulated field values and record originating countries from the database.
InCountry service = InCountry.getInstance();
Set<Id> input = new Set<Id>{'00Q5w00001vsLjVEAU', '00Q5w00001vrzjQEAQ', '0015w00002OUr9uAAD'};
List<InCountry.WriteRequest> result = service.sync(input);
Synchronizing multiple records by record instances
Before executing the sync(List<SObject> records)
method, ensure that you have all the required regulated and country fields included into your SOQL queries.
InCountry service = InCountry.getInstance();
List<Lead> input = [SELECT Id, FirstName, Email, Phone, Country_Field__c
FROM Lead
LIMIT 40];
for (Lead leadRecord : input) {
leadRecord.Phone = '+37529000000';
}
List<InCountry.WriteRequest> result = service.sync(input);
Synchronizing records with data loss prevention
There is a possible situation when records were not successfully synchronized to the InCountry platform but were hashed on the Salesforce side. In this case, the clear text data was lost. The script below shows how not to lose the clear text data if some records fail to synchronize to the InCountry platform. The records that failed to synchronize with the clear text values will be stored in the failedAccountsWithClearText
variable.
List<Account> accountListToSync = [SELECT Id FROM Account];
Map<Id,Account> backupAccountMap = new Map<Id, Account>(accountListToSync);
List<Account> failedAccountsWithClearText = new List<Account>();
InCountry service = InCountry.getInstance();
List<InCountry.WriteRequest> response = service.sync(accountListToSync);
for (InCountry.WriteRequest request : response) {
if(String.isNotBlank(request.error)) {
failedAccountsWithClearText.add(backupAccountMap.get(request.profile_key));
}
}
system.debug(failedAccountsWithClearText);
How to remove records from the InCountry platform
As of now, the InCountry platform does not support batch delete operations. If you want to delete multiple records from the InCountry platform, please consider that the Apex SDK will perform a callout for each record.
Removing a single record by record id
Please consider that the Apex SDK will perform an additional SOQL query to fetch a record and package configuration from the Salesforce database.
InCountry service = InCountry.getInstance();
Id input = '0015w00002OUr9uAAD';
InCountry.DeleteResult result = service.remove(input);
Removing multiple records by records ids
Please consider that the Apex SDK will perform additional SOQL queries to fetch your records and package configuration from the Salesforce database. In the following example, the Apex SDK will perform two additional SOQL queries and three callouts.
InCountry service = InCountry.getInstance();
Set<Id> input = new Set<Id>{'00Q5w00001vsLjVEAU', '00Q5w00001vrzjQEAQ', '0015w00002OUr9uAAD'};
List<InCountry.DeleteResult> result = service.remove(input);