From 3e22f608e261ae55643bf0fb0faff5a929c5151b Mon Sep 17 00:00:00 2001 From: Oxana Date: Wed, 15 May 2024 11:04:24 -0400 Subject: [PATCH 1/2] Implemented bulkified Queueable Apex version for after update --- .../default/classes/ContactTriggerHandler.cls | 13 +++- .../main/default/classes/DummyJSONCallout.cls | 78 ++++++++++--------- .../default/classes/DummyJSONCalloutTest.cls | 15 +++- .../classes/PostDummyJSONUserQueueable.cls | 22 ++++++ .../PostDummyJSONUserQueueable.cls-meta.xml | 5 ++ 5 files changed, 90 insertions(+), 43 deletions(-) create mode 100644 force-app/main/default/classes/PostDummyJSONUserQueueable.cls create mode 100644 force-app/main/default/classes/PostDummyJSONUserQueueable.cls-meta.xml diff --git a/force-app/main/default/classes/ContactTriggerHandler.cls b/force-app/main/default/classes/ContactTriggerHandler.cls index 5dbdc67..67af4c0 100644 --- a/force-app/main/default/classes/ContactTriggerHandler.cls +++ b/force-app/main/default/classes/ContactTriggerHandler.cls @@ -35,7 +35,7 @@ public with sharing class ContactTriggerHandler extends TriggerHandler { if (cont.DummyJSON_Id__c == null) { cont.DummyJSON_Id__c = String.valueOf(Math.round(Math.random() * 100)); } - if (Integer.valueOf(cont.DummyJSON_Id__c) <= 100 && !System.isFuture()) { + if (Integer.valueOf(cont.DummyJSON_Id__c) <= 100 && !System.isFuture() && !System.isQueueable()) { DummyJSONCallout.getDummyJSONUserFromId(cont.DummyJSON_Id__c); } } @@ -48,10 +48,15 @@ public with sharing class ContactTriggerHandler extends TriggerHandler { * if DummyJSON_Id__c is greater than 100, call the postCreateDummyJSONUser API */ public override void afterUpdate() { - for (Contact cont : newConts) { - if (Integer.valueOf(cont.DummyJSON_Id__c) > 100 && !System.isFuture()) { - DummyJSONCallout.postCreateDummyJSONUser(cont.Id); + if (!System.isQueueable() && !System.isFuture()) { + Map contsById = new Map(); + for (Contact cont : newConts) { + if (Integer.valueOf(cont.DummyJSON_Id__c) > 100) { + // DummyJSONCallout.postCreateDummyJSONUser(cont.Id); + contsById.put(cont.Id, cont); + } } + System.enqueueJob(new PostDummyJSONUserQueueable(contsById)); } } } \ No newline at end of file diff --git a/force-app/main/default/classes/DummyJSONCallout.cls b/force-app/main/default/classes/DummyJSONCallout.cls index 8981103..f615bf8 100644 --- a/force-app/main/default/classes/DummyJSONCallout.cls +++ b/force-app/main/default/classes/DummyJSONCallout.cls @@ -111,31 +111,30 @@ public with sharing class DummyJSONCallout { * * @param contactId The Salesforce Contact ID used to generate the JSON payload for the external system. */ - @future(callout = true) - public static void postCreateDummyJSONUser(String contactId) { - // Create HTTP request to send. - HttpRequest request = new HttpRequest(); - // Set the endpoint URL. Use direct URL or for best practices use Named Credential. - request.setEndpoint('callout:DummyJsonUser/add'); - // Set the HTTP method to POST. - request.setMethod('POST'); - // Set the body using generateDummyJsonUserPayload method. - // String jsonBody = generateDummyJsonUserPayload(); - // if (jsonBody.length() > 0) - request.setBody(generateDummyJsonUserPayload(contactId)); - - // Send the HTTP request and get the response. - Http http = new Http(); - HttpResponse response = http.send(request); - // If the HTTP response code is successful, update the contact. - if (response.getStatusCode() >= 200 || response.getStatusCode() <= 299) { - Contact cont = [ - SELECT Id, DummyJSON_Last_Updated__c - FROM Contact - WHERE Id = :contactId]; - cont.DummyJSON_Last_Updated__c = Datetime.now(); - update cont; + + public static List postCreateDummyJSONUser(Set contactIds) { + Map jsonById = generateDummyJsonUserPayload(contactIds); + + List contactIsToUpdate = new List(); + for (Id contId : contactIds) { + // Create HTTP request to send. + HttpRequest request = new HttpRequest(); + // Set the endpoint URL. Use direct URL or for best practices use Named Credential. + request.setEndpoint('callout:DummyJsonUser/add'); + // Set the HTTP method to POST. + request.setMethod('POST'); + // Set the body using generateDummyJsonUserPayload method. + request.setBody(jsonById.get(contId)); + + // Send the HTTP request and get the response. + Http http = new Http(); + HttpResponse response = http.send(request); + // If the HTTP response code is successful, update the contact. + if (response.getStatusCode() >= 200 || response.getStatusCode() <= 299) { + contactIsToUpdate.add(contId); + } } + return contactIsToUpdate; } /* @@ -154,25 +153,30 @@ public with sharing class DummyJSONCallout { * @return String The JSON string payload that represents the Contact's details. */ @TestVisible // Allows test class to see this method. Since it is private, it would not be visible otherwise. - private static String generateDummyJsonUserPayload(String contactId) { + private static Map generateDummyJsonUserPayload(Set contactIds) { // Query the contact to get the field values to generate the JSON payload. - Contact cont = [ + List contacts = [ SELECT Id, FirstName, LastName, Email, Phone, DummyJSON_Id__c FROM Contact - WHERE Id = :contactId + WHERE Id IN :contactIds ]; - // Create a map of the field values. - Map valuesMap = new Map(); - valuesMap.put('salesforceId', contactId); - valuesMap.put('firstName', String.isNotBlank(cont.FirstName) ? cont.FirstName : 'unknown'); - valuesMap.put('lastName', String.isNotBlank(cont.LastName) ? cont.LastName : 'unknown'); - valuesMap.put('email', String.isNotBlank(cont.Email) ? cont.Email : 'unknown'); - valuesMap.put('phone', String.isNotBlank(cont.Phone) ? cont.Phone : 'unknown'); - // Serialize the map into a JSON string. - String json = JSON.serialize(valuesMap); + Map jsonById = new Map(); + for (Contact cont : contacts) { + // Create a map of the field values. + Map valuesMap = new Map(); + valuesMap.put('salesforceId', String.valueOf(cont.Id)); + valuesMap.put('firstName', String.isNotBlank(cont.FirstName) ? cont.FirstName : 'unknown'); + valuesMap.put('lastName', String.isNotBlank(cont.LastName) ? cont.LastName : 'unknown'); + valuesMap.put('email', String.isNotBlank(cont.Email) ? cont.Email : 'unknown'); + valuesMap.put('phone', String.isNotBlank(cont.Phone) ? cont.Phone : 'unknown'); + // Serialize the map into a JSON string. + String json = JSON.serialize(valuesMap); + jsonById.put(cont.Id, json); + } + // Make sure to check that required contacts fields have a value. Default the value to unknown if it does not exists. // Integration data can change over time. It is a best practice to add safeguards/validation to ensure the integration does not break. - return json; + return jsonById; } } \ No newline at end of file diff --git a/force-app/main/default/classes/DummyJSONCalloutTest.cls b/force-app/main/default/classes/DummyJSONCalloutTest.cls index dbc6043..56bff8f 100644 --- a/force-app/main/default/classes/DummyJSONCalloutTest.cls +++ b/force-app/main/default/classes/DummyJSONCalloutTest.cls @@ -66,13 +66,18 @@ public with sharing class DummyJSONCalloutTest { Phone = '+123456789' ); insert testContact; + // Suvorova + Map contsById = new Map(); + contsById.put(testContact.Id, testContact); // Set the mock callout class Test.setMock(HttpCalloutMock.class, new DummyJSONCalloutMockGenerator()); // As this is a future method, we need to enclose it in Test.startTest() and Test.stopTest() to ensure it's executed in the test context. Test.startTest(); - DummyJSONCallout.postCreateDummyJSONUser(testContact.Id); + // Suvorova + // DummyJSONCallout.postCreateDummyJSONUser(testContact.Id); + System.enqueueJob(new PostDummyJSONUserQueueable(contsById)); Test.stopTest(); // Retrieve the updated contact to verify the changes @@ -97,9 +102,15 @@ public with sharing class DummyJSONCalloutTest { DummyJSON_Id__c = '101' ); insert testContact; + // Suvorova + Set contactIds = new Set(); + contactIds.add(testContact.Id); // Call the method - String payload = DummyJSONCallout.generateDummyJsonUserPayload(testContact.Id); + // Suvorova + // String payload = DummyJSONCallout.generateDummyJsonUserPayload(testContact.Id); + Map payloadById = DummyJSONCallout.generateDummyJsonUserPayload(contactIds); + String payload = payloadById.get(testContact.Id); // Parse the generated JSON Map payloadMap = (Map) JSON.deserializeUntyped(payload); diff --git a/force-app/main/default/classes/PostDummyJSONUserQueueable.cls b/force-app/main/default/classes/PostDummyJSONUserQueueable.cls new file mode 100644 index 0000000..1b15017 --- /dev/null +++ b/force-app/main/default/classes/PostDummyJSONUserQueueable.cls @@ -0,0 +1,22 @@ +public class PostDummyJSONUserQueueable implements Queueable, Database.AllowsCallouts { + + private Map contsById; + + public PostDummyJSONUserQueueable(Map contactsById) { + this.contsById = contactsById; + } + + public void execute(QueueableContext qc) { + List contactIdsForUpdate = DummyJSONCallout.postCreateDummyJSONUser(this.contsById.keySet()); + + List contacts = [ + SELECT Id, DummyJSON_Last_Updated__c + FROM Contact + WHERE Id IN :contactIdsForUpdate + ]; + for (Contact cont : contacts) { + cont.DummyJSON_Last_Updated__c = Datetime.now(); + } + Database.update(contacts); + } +} \ No newline at end of file diff --git a/force-app/main/default/classes/PostDummyJSONUserQueueable.cls-meta.xml b/force-app/main/default/classes/PostDummyJSONUserQueueable.cls-meta.xml new file mode 100644 index 0000000..019e850 --- /dev/null +++ b/force-app/main/default/classes/PostDummyJSONUserQueueable.cls-meta.xml @@ -0,0 +1,5 @@ + + + 59.0 + Active + \ No newline at end of file From 1660879ec6d6115f93d3845ce2819e0346f93bc7 Mon Sep 17 00:00:00 2001 From: Oxana Date: Tue, 2 Jul 2024 12:22:13 -0400 Subject: [PATCH 2/2] Refactor generateDummyJsonUserPayload: get rid of extra Contacts loop --- .../main/default/classes/DummyJSONCallout.cls | 47 +++++++++---------- .../default/classes/DummyJSONCalloutTest.cls | 6 +-- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/force-app/main/default/classes/DummyJSONCallout.cls b/force-app/main/default/classes/DummyJSONCallout.cls index f615bf8..a4b775d 100644 --- a/force-app/main/default/classes/DummyJSONCallout.cls +++ b/force-app/main/default/classes/DummyJSONCallout.cls @@ -113,10 +113,16 @@ public with sharing class DummyJSONCallout { */ public static List postCreateDummyJSONUser(Set contactIds) { - Map jsonById = generateDummyJsonUserPayload(contactIds); + // Query the contact to get the field values to generate the JSON payload. + List contacts = [ + SELECT Id, FirstName, LastName, Email, Phone, DummyJSON_Id__c + FROM Contact + WHERE Id IN :contactIds + ]; List contactIsToUpdate = new List(); - for (Id contId : contactIds) { + for (Contact cont : contacts) { + String json = generateDummyJsonUserPayload(cont); // Create HTTP request to send. HttpRequest request = new HttpRequest(); // Set the endpoint URL. Use direct URL or for best practices use Named Credential. @@ -124,14 +130,14 @@ public with sharing class DummyJSONCallout { // Set the HTTP method to POST. request.setMethod('POST'); // Set the body using generateDummyJsonUserPayload method. - request.setBody(jsonById.get(contId)); + request.setBody(json); // Send the HTTP request and get the response. Http http = new Http(); HttpResponse response = http.send(request); // If the HTTP response code is successful, update the contact. if (response.getStatusCode() >= 200 || response.getStatusCode() <= 299) { - contactIsToUpdate.add(contId); + contactIsToUpdate.add(cont.Id); } } return contactIsToUpdate; @@ -153,30 +159,21 @@ public with sharing class DummyJSONCallout { * @return String The JSON string payload that represents the Contact's details. */ @TestVisible // Allows test class to see this method. Since it is private, it would not be visible otherwise. - private static Map generateDummyJsonUserPayload(Set contactIds) { - // Query the contact to get the field values to generate the JSON payload. - List contacts = [ - SELECT Id, FirstName, LastName, Email, Phone, DummyJSON_Id__c - FROM Contact - WHERE Id IN :contactIds - ]; - Map jsonById = new Map(); - for (Contact cont : contacts) { - // Create a map of the field values. - Map valuesMap = new Map(); - valuesMap.put('salesforceId', String.valueOf(cont.Id)); - valuesMap.put('firstName', String.isNotBlank(cont.FirstName) ? cont.FirstName : 'unknown'); - valuesMap.put('lastName', String.isNotBlank(cont.LastName) ? cont.LastName : 'unknown'); - valuesMap.put('email', String.isNotBlank(cont.Email) ? cont.Email : 'unknown'); - valuesMap.put('phone', String.isNotBlank(cont.Phone) ? cont.Phone : 'unknown'); - // Serialize the map into a JSON string. - String json = JSON.serialize(valuesMap); - jsonById.put(cont.Id, json); - } + private static String generateDummyJsonUserPayload(Contact cont) { + + // Create a map of the field values. + Map valuesMap = new Map(); + valuesMap.put('salesforceId', String.valueOf(cont.Id)); + valuesMap.put('firstName', String.isNotBlank(cont.FirstName) ? cont.FirstName : 'unknown'); + valuesMap.put('lastName', String.isNotBlank(cont.LastName) ? cont.LastName : 'unknown'); + valuesMap.put('email', String.isNotBlank(cont.Email) ? cont.Email : 'unknown'); + valuesMap.put('phone', String.isNotBlank(cont.Phone) ? cont.Phone : 'unknown'); + // Serialize the map into a JSON string. + String json = JSON.serialize(valuesMap); // Make sure to check that required contacts fields have a value. Default the value to unknown if it does not exists. // Integration data can change over time. It is a best practice to add safeguards/validation to ensure the integration does not break. - return jsonById; + return json; } } \ No newline at end of file diff --git a/force-app/main/default/classes/DummyJSONCalloutTest.cls b/force-app/main/default/classes/DummyJSONCalloutTest.cls index 56bff8f..1af3921 100644 --- a/force-app/main/default/classes/DummyJSONCalloutTest.cls +++ b/force-app/main/default/classes/DummyJSONCalloutTest.cls @@ -102,15 +102,11 @@ public with sharing class DummyJSONCalloutTest { DummyJSON_Id__c = '101' ); insert testContact; - // Suvorova - Set contactIds = new Set(); - contactIds.add(testContact.Id); // Call the method // Suvorova // String payload = DummyJSONCallout.generateDummyJsonUserPayload(testContact.Id); - Map payloadById = DummyJSONCallout.generateDummyJsonUserPayload(contactIds); - String payload = payloadById.get(testContact.Id); + String payload = DummyJSONCallout.generateDummyJsonUserPayload(testContact); // Parse the generated JSON Map payloadMap = (Map) JSON.deserializeUntyped(payload);