0

I am building a REST integration between two Salesforce Environments. One environment will be used to setup and stage Data and when ready the Integration will be able to push data from the src to the target.

It is not a lot of data at any one time, but it is a lot of related objects that would need to be Upserted to the target org in the right order.

The steps for one migration looks more or less like the following:

  • Retrieve object1 data with SOQL.
  • JSON Serialize the retrieved data using a wrapper class.
  • Send the JSON as a HTTP PUT Request to the Source Org
  • Rest Webservice in the targer org gets the JSON, Deserializes it using the same wrapper class and Upserts the data

This all works and I am able to send and work with Lists of data for each object, however to get it to work I need: One wrapper class per object, which has all the fields that I want to migrate as Class variables, then as part of the constructor then as part of a jsonSerializeMethod for serialization and as part of a getMethod for deserialization, plus as a retrieveBySOQL method to return the lists of related data.

This would not be a problem if I only had one object (or even 3) as part of the migration, but I happen to have 20ish objects, which mean I would need one such class for each object.

It is the same fields in both Salesforce Environments.

Specifically I would like to know if I can accomplish the same without using these wrapper classes for serializing/deserializing the json. Here is an example:

public class PriceListItemClass {
public String aptsExtId;

public PriceListItemClass(String aptsExtId){
    this.aptsExtId = aptsExtId;
}

public Apttus_Config2__PriceListItem__c getRecord(){
    Apttus_Config2__PriceListItem__c priceListItem = new Apttus_Config2__PriceListItem__c();
    priceListItem.APTS_Ext_Id__c = aptsExtId;
    return priceListItem;
}

public static List<Apttus_Config2__PriceListItem__c> retrieveBySOQL(Set<Id> productExtIdSet){
    List<Apttus_Config2__PriceListItem__c> priceListItems = [
        SELECT APTS_Ext_Id__c, Apttus_Config2__PriceListId__c
        FROM Apttus_Config2__PriceListItem__c
        WHERE Apttus_Config2__ProductId__r.APTS_Ext_ID__c
        IN : productExtIdSet
        ];

    priceListItems = updateAPTSExtIds(priceListItems);

    return priceListItems;
}

public static String jsonSerialize(List<Apttus_Config2__PriceListItem__c> priceListItemList){
    List<PriceListItemClass> priceListItemClassList = new List<PriceListItemClass>();

    for(Apttus_Config2__PriceListItem__c priceListItem : priceListItemList){
        PriceListItemClass priceListItemClass = new PriceListItemClass(priceListItem.Name);
        priceListItemClassList.add(priceListItemClass);
    }

    String httpRequestBody = JSON.serialize(priceListItemClassList);
    System.debug('httpRequestBody: ' + httpRequestBody);

    return httpRequestBody;
}

I am able to send a List of Products without the wrapper class: This is the JSON: [{"attributes":{"type":"Product2","url":"/services/data/v45.0/sobjects/Product2/01t0E000004dz71QAA"},"LastModifiedDate":"2019-02-28T11:24:53.000+0000","Description":"test","APTS_Enable_Reselling__c":true,"APTS_Exclude_from_SBL_PPL__c":false,"IsActive":true,"Apttus_Config2__HasOptions__c":false,"Apttus_Config2__HasSearchAttributes__c":false,"APTS_MBL_Enabled__c":false,"Apttus_Config2__ConfigurationType__c":"Standalone","Apttus_Config2__Uom__c":"Each","APTS_Foundations__c":false,"APTS_Fixed_Price__c":false,"Name":"Oracle5 - Update11","APTS_Exclude_from_MBL__c":false,"CreatedById":"005570000082wlWAAQ","pse__IsServicesProduct__c":false,"APTS_PSA_Product__c":false,"APTS_Product_Link__c":"_HL_ENCODED_/01t0E000004dz71_HL_01t0E000004dz71_HL__blank_HL_","APTS_Solution_Owner_Prod_Sum__c":" ","APTS_Follows_IMS_Users__c":false,"APTS_Price_Point__c":1,"APTS_Safe_ID__c":"01t0E000004dz71QAA","Apttus_Config2__HasAttributes__c":false,"IsDeleted":false,"Migrate_To_Production__c":false,"APTS_Pre_CPQ__c":false,"Apttus_Config2__Version__c":1.00,"Apttus_Config2__HasDefaults__c":false,"TECH_MigratePreprod__c":false,"CanUseRevenueSchedule":false,"Apttus_Config2__Customizable__c":false,"CurrencyIsoCode":"EUR","SystemModstamp":"2019-02-28T11:24:53.000+0000","IsArchived":false,"Apttus_Config2__ExcludeFromSitemap__c":false,"Is_SBL_Product__c":false,"Migrate_To_Preprod__c":true,"APTS_Quantity_Check_Against_Asset__c":false,"CreatedDate":"2019-02-21T13:48:32.000+0000","Id":"01t0E000004dz71QAA","APTS_Enable_Base_Price_Edit__c":false,"LastModifiedById":"005570000082wlWAAQ","APTS_Ext_ID__c":"01t0E000004dz71QAA","Apttus_Config2__Icon__c":"_HL_ENCODED_/apex/Apttus_Config2__IconUploader?productId=01t0E000004dz71_HL__IM1_/resource/Apttus_Config2__Image_UploadIcon_IM2_Upload Icon_IM3__50_IM4_50_IM5__HL__top_HL_"}]

I use the APTS_Ext_Id__c as the external Id.

This is the REST Ressource:

@RestResource(urlMapping='/Product/*')

global class ProductToolsRest {

//Upsert a List of Products
@HttpPut
global static void doPut() {
    RestRequest request = RestContext.request;
    RestResponse response = RestContext.response;

    String jSONRequestBody = request.requestBody.toString().trim();
    System.debug('jSONRequestBody: ' + jSONRequestBody);

    // List<ProductClass> productClassRequests = (List<ProductClass>)JSON.deserialize(jSONRequestBody, List<ProductClass>.class);

    List<Product2> productsToUpsert = new List<Product2>();
    List<Product2> productList = JSON.deserialize(jSONRequestBody, productsToUpsert);
    // for(ProductClass productClassRequest : productClassRequests){
    //     productsToUpsert.add(productClassRequest.getRecord());
    // }

    // for(Product2 product : productList){
    //     productsToUpsert.add(product);
    // }

    if(!productsToUpsert.isEmpty()){
        System.debug('productsToUpsert: ' + productsToUpsert);
        Schema.SObjectField extID = Product2.Fields.APTS_Ext_Id__c;
        System.debug('productsToUpsert: ' + productsToUpsert);

        // Database.UpsertResult[] cr = Database.upsert(productsToUpsert, extID);
        Database.UpsertResult[] cr = Database.upsert(productsToUpsert, extID);

        upsert productsToUpsert;
    }
}

}

Thank you!

  • I have edited the question to be more specific, hope you can reopen it. Thx. – MR.M. Feb 28 at 10:31
0

You might be interested in the SObject tree resource from the REST API that Salesforce provides.

SObject tree allows you to use a single request to insert a record hierarchy up to 5 levels deep. The SObject tree resource is mostly aimed at creating a hierarchy of records. If you're not looking for that per se, you may want to simply insert single records using the Composite resource. You may also try the composite resource if your record hierarchy is greater than 5 levels deep.

For the cost of understanding and prototyping a solution using this, you get to avoid the need to create any wrapper classes (if the SObjects in both orgs share your target fields).

I'm assuming that you already have the authentication part taken care of, since you already rolled your own cross-org integration using an Apex webservice.

  • At first glance it looks like I can only insert records using those resources, do you know if that is true? I am looking to make Upserts, meaning that some data will exist already in the target, but I would need to update fields on those records. – MR.M. Feb 28 at 8:17
  • Also, any chance you can help me changing a simple wrapper class to use the composite call, for me to better understand how it avoids the wrapper class? – MR.M. Feb 28 at 8:25
  • @MR.M. Inserting records is usually done via POST, whereas updating is usually done via PATCH. You avoid the wrapper class (and the Apex you use) on the receiving org because the REST API is taking care of the inserts/updates for you. The other thing you may want to consider is making an external id field on your objects (because the normal Ids will be different between your orgs). Give it a try, and then ask another question if you get stuck. – Derek F Feb 28 at 12:45
  • I am now able to send the JSON to the receiving Org without using the wrapper class to serialize it, which was easier than I thought. Can you help me with deserializing without it? I will add the JSON and the REST Webservice to the question. It looks like JSON.deserialize(String, ApexClass) must have an Apex Class to deserialize into - any idea how I can get around that? – MR.M. Mar 1 at 9:24
  • @MR.M. Again, this is something that you should create a new question for. – Derek F Mar 1 at 12:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.