> For the complete documentation index, see [llms.txt](https://docs.coherent.global/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.coherent.global/integrations/salesforce/developer-guide.md).

# Developer guide

{% hint style="info" %}
The guide is intended for Salesforce developers with hands-on experience. If you're a Salesforce admin user with very little to zero experience in software development, you might want to check out the [Business user guide](/integrations/salesforce/business-user-guide.md) instead to learn how to manage the Spark integration.
{% endhint %}

## Getting started

Feel free to skip this section If you’re already familiar with developing Salesforce (SF) applications. For those who need a refresher, this section covers the basic concepts of Salesforce development.

There are multiple ways to develop a Salesforce application. The most common method is to use the **Developer Console**, a web-based integrated development environment (IDE). Alternatively, many developers prefer **Visual Studio Code (VSCode)**, which offers a more user-friendly and versatile experience. Before diving in, review the [Get Ready to Develop](https://trailhead.salesforce.com) guide on Trailhead.&#x20;

When using the VSCode approach, it’s important to understand that your local project directory mirrors the structure of Salesforce components in the connected [DevHub or Scratch Org](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_enable_devhub.htm). In essence, Salesforce artifacts—such as Apex classes, triggers, and SOQL queries—are represented as files in the project. While most actions you perform in VSCode are executed directly in the Salesforce Org, some exceptions exist, which is irrelevant for this guide.

This development model differs from traditional software development. Think of it as a convenient way to interact with Salesforce components, manage version control, and bundle subsets of components for packaging and deployment, such as publishing apps on [AppExchange](https://1.appexchange.com/partnerprogram).

<figure><img src="https://res.cloudinary.com/hy4kyit2a/f_auto,fl_lossy,q_70/learn/modules/starting_force_com/starting_understanding_arch/images/b779912c89a05efb313ccf366c0be030_kix.k7y40gbobza5.png" alt=""><figcaption><p>Salesforce Architecture (Source: <a href="https://res.cloudinary.com/hy4kyit2a/f_auto,fl_lossy,q_70/learn/modules/starting_force_com/starting_understanding_arch/images/b779912c89a05efb313ccf366c0be030_kix.k7y40gbobza5.png">Trailhead</a>)</p></figcaption></figure>

If this seems abstract now, don’t worry — it will all make sense as you progress through the development process.

At the end of this guide, you will be able to:

* Create, edit, and query custom metadata objects.
* Create and update Apex classes, including unit tests.
* Create and update Lightning Web Components.

<details>

<summary>Prerequisites</summary>

* Access to Salesforce DevHub or Org
* Access to a Coherent Spark account

Additional tools if you're using VSCode are:

* [Salesforce CLI 2.0](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_install_cli.htm)
* [Node.js 18.x](https://nodejs.org/en/download)
* [Java 11+](https://www.java.com/en/download/)
* [Salesforce Extension Pack](https://marketplace.visualstudio.com/items?itemName=salesforce.salesforcedx-vscode)

Remember to create a scratch org as your developer workspace to avoid introducing unnecessary and/or breaking changes.&#x20;

</details>

## Development process

Your primary objective with this integration is to utilize Coherent Spark's [Execute API](/spark-apis/execute-api.md) to trigger and invoke your externalized calculations. In simple terms, this involves [invoking callouts using Apex](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts.htm).&#x20;

You can think of the Salesforce development process as being divided into three main phases:

1. **Backend development**: create or update Apex classes to manage your integration logic;
2. **Frontend development**: create or update Lightning Web Components for user interaction;
3. **Custom metadata**: define your Spark configurations as needed.

### Phase 1: Backend development

In the backend phase, you’ll use Apex classes to represent the input and output data required by your calculation engine. Since Apex is a structurally-typed language, your classes must match the JSON structure expected by the version of [Execute API](/spark-apis/execute-api.md) you choose to work with.

<details>

<summary>Sample <code>SparkClient</code> base class</summary>

```apex
public virtual class SparkClient {
  private Config config;

  private static final Integer DEFAULT_TIMEOUT_IN_MS = 60000; // 60 seconds

  public SparkClient(Config config) {
    this.config = config;
  }

  public Map<String, String> getHeaders() {
    Map<String, String> headers = new Map<String, String>{
      'Content-Type' => 'application/json;charset=UTF-8',
      'User-Agent' => 'Spark Client (Salesforce)',
      'x-spark-ua' => 'agent=salesforce-spark-client;version=1.0.0',
      'x-request-id' => String.valueOf(Datetime.now().getTime()),
      'x-tenant-name' => config.tenant
    };

    if (config.auth != null)
      headers.putAll(config.auth); // No auth needed for public APIs

    if (config.headers != null)
      headers.putAll(config.headers);

    return headers;
  }

  public String buildUrl(String endpoint) {
    String baseUrl = config.baseUrl.endsWith('/') ? config.baseUrl : config.baseUrl + '/';
    String tenant = config.tenant + '/api/' + config.apiVersion;
    return baseUrl + tenant + (endpoint.startsWith('/') ? endpoint : '/' + endpoint);
  }

  public HttpRequest buildRequest(String method, String endpoint, String body) {
    HttpRequest request = new HttpRequest();
    request.setMethod(method == null ? 'GET' : method);
    request.setEndpoint(buildUrl(endpoint));
    request.setTimeout(DEFAULT_TIMEOUT_IN_MS);

    if (body != null)
      request.setBody(body);

    Map<String, String> headers = getHeaders();
    for (String key : headers.keySet()) {
      request.setHeader(key, headers.get(key));
    }

    return request;
  }

  public String fetch(HttpRequest request) {
    System.debug('fetching <' + request.getEndpoint() + '>');
    HttpResponse response = new Http().send(request);

    if (response.getStatusCode() != 200) {
      String msg = 'failed to fetch <' + request.getEndpoint() + '>; ' + response.getStatus();
      System.debug(msg);
      throw new SparkException(msg);
    }

    return response.getBody();
  }

  public Object fetch(HttpRequest request, Type returnType) {
    String body = fetch(request);
    JsonParser parser = Json.createParser(body);
    parser.nextToken();
    return parser.readValueAs(returnType);
  }

  public virtual class Config {
    protected String baseUrl;
    protected String tenant;
    protected String apiVersion = 'v3';
    protected Map<String, String> auth;
    protected Map<String, String> headers;

    public Config(String baseUrl, String tenant) {
      this.baseUrl = baseUrl;
      this.tenant = tenant;
    }

    public Config(String baseUrl, String tenant, Map<String, String> auth) {
      this(baseUrl, tenant);
      this.auth = auth;
    }

    public Config(String baseUrl, String tenant, Map<String, String> auth, Map<String, String> headers) {
      this(baseUrl, tenant, auth);
      this.headers = headers;
    }

    public void setApiVersion(String apiVersion) {
      if (apiVersion == 'v3' || apiVersion == 'v4')
        this.apiVersion = apiVersion;
    }
  }

  public virtual class SparkException extends Exception {
  }
}
```

</details>

<details>

<summary>Apex Classes in VSCode</summary>

In **VSCode**, you’ll need to create two files:

1. `force-app/main/default/classes/SparkClient.cls`
   * This Apex class handles HTTP requests to Spark using the configured settings.
   * You can either **hardcode these settings** or **use custom metadata** to fetch them dynamically.
   * Keep in mind: these settings may include **user credentials**, so they must be handled securely.
2. `force-app/main/default/classes/SparkClient.cls-meta.xml`
   * This metadata file is essential for deploying the Apex class to your Salesforce org.
   * It provides **Salesforce-specific details** about `SparkClient.cls`, such as its **name**, **description**, and **visibility settings**.

**Important Note:** If there’s a syntax error in your Apex class—something the IDE might not catch—the deployment will fail. Yes, it’s not the smooth developer experience you were hoping for, but that’s how Salesforce operates. Always remember: changes made in VSCode are merely a local representation of what’s actually implemented in Salesforce.

</details>

To harness `SparkClient.cls`, let's now implement a basic use case simulating a callout to Spark service.

<details>

<summary>Sample <code>SparkClient</code> class</summary>

```apex
public class SparkService {
  /**
   * Execute a Spark service using v3 format.
   *
   * @param serviceUri - the URI of the service to call (e.g. 'version/{version_id}').
   * @param payload - expected to be a JSON string.
   *
   * A sample of the request payload using v3 format is as follows:
   * {
   *  "request_data": {
   *    "inputs": { "param1": "value1", "param2": "value2", ... }
   *  },
   *  "request_meta": { "compiler_type": "Neuron", ... }
   * }
   */
  @AuraEnabled(cacheable=true)
  public static String execute(String serviceUri, String payload) {
    // NOTE: Ideally, pull Spark settings from Salesforce using custom metadata.
    String baseUrl = 'https://excel.my-env.coherent.global';
    String tenant = 'my-tenant';
    String apiKey = 'my-api-key';

    SparkClient.Config config = new SparkClient.Config(
      baseUrl,
      tenant,
      new Map<String, String>{ 'x-synthetic-key' => apiKey }
    );
    SparkClient client = new SparkClient(config);

    return client.fetch(client.buildRequest('POST', serviceUri, payload));
  }
}
```

</details>

The example above of a callout service implementation uses dummy data to refer to Spark settings needed to trigger the calculation engine residing in Coherent Spark. In a real-world scenario, you would want to collect your input data as a custom object from SF, then feed it into Spark to run complex calculations, and finally collect the output data as part of your business data for further processing.

Below is what you'll need to add more unit tests to your test suite for `SparkClient.cls`:

1. An HTTP callout mock class to simulate what the backend service would respond with based on a given request.
2. Then, the actual unit tests for the `SparkClient.cls` and `SparkService.cls`.

{% tabs %}
{% tab title="CalloutServiceMock.cls" %}

```apex
@isTest
public class CalloutServiceMock implements HTTPCalloutMock {
  private Integer statusCode;
  private String body;

  public static final String METHOD = 'POST';
  public static final Map<String, String> HEADERS = new Map<String, String>{
    'x-tenant-name' => 'fake-tenant',
    'x-synthetic-key' => 'some-synthetic-key'
  };

  public CalloutServiceMock(Integer statusCode, String body) {
    this.statusCode = statusCode;
    this.body = body;
  }

  public HTTPResponse respond(HTTPRequest request) {
    System.assertEquals(METHOD, request.getMethod(), 'only supports ' + METHOD + ' method');

    for (String key : HEADERS.keySet()) {
      System.assertNotEquals(null, request.getHeader(key), 'header "' + key + '" is missing');
      System.assertNotEquals('', request.getHeader(key), 'header "' + key + '" should exist');
    }

    HTTPResponse response = new HTTPResponse();
    response.setStatusCode(statusCode);
    response.setBody(body);
      
    return response;
  }
}
```

{% endtab %}

{% tab title="SparkClientTest.cls" %}

```apex
@isTest
public class SparkServiceTest {
  private static final String INPUTS = '{"inputs":{"radius":3,"height":4.2}}';
  private static final String METADATA = '{"call_purpose":"Salesforce Integration","compiler_type":"Neuron"}';
  private static final String BODY = '{"request_data":' + INPUTS + ',"request_meta":' + METADATA + '}';

  @isTest
  public static void testMockCalloutWithPayload() {
    Test.startTest();

    String payload = '{"status":"Success","error":null,"response_data":{"outputs":{"volume":42}},"response_meta":{"version_id":"uuid","process_time":13,"call_id":"uuid"}}';
    CalloutServiceMock serviceMock = new CalloutServiceMock(200, payload);
    Test.setMock(HTTPCalloutMock.class, serviceMock);

    String response = SparkService.execute('folders/my-folder/services/my-service/execute', BODY);
    System.assertEquals(response, payload, 'callout should be successful');

    Test.stopTest();
  }
}
```

{% endtab %}
{% endtabs %}

Further readings on Apex classes can be found here:

* [Intro to Apex](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_intro.htm)
* [Adding an Apex class](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_qs_class.htm)
* [Apex development process](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_intro_development_process.htm)

### Phase 2: Front-end development

The frontend phase might feel more approachable than the backend development, mainly because it’s more visual and interactive. That is, you'll be creating user-interactive components to enhance the user experience for Salesforce admins.

If you’re already familiar with web development technologies like HTML, CSS, and JavaScript, you’ll find this phase straightforward. If you’re not a web developer, we recommend starting with the [LWC Guide](https://developer.salesforce.com/docs/component-library/documentation/en/lwc) to help you understand the basics of Lightning Web Components (LWC) before you can proceed. Roughly speaking, LWC is a Salesforce-proprietary modern framework for building web components. The following resources can help you grasp how LWC works behind the scenes:

* Explore design principles and pre-built components with [Lightning Design System](https://www.lightningdesignsystem.com/components/overview/).
* Learn about individual components like buttons, badges, and cards from [LWC Component References](https://developer.salesforce.com/docs/component-library/bundle/lightning-button).
* Access examples of commonly used patterns and components from [LWC Recipes on GitHub](https://github.com/trailheadapps/lwc-recipes).

<details>

<summary>LWC Setup in VSCode</summary>

In VSCode, your project should look more or less like this:

```
.
├── ...
├── .eslintignore
├── .eslintrc
├── .prettierignore
├── .prettierrc
├── jest.config.js
├── package.json
...
```

The files listed above are usually used to configure the LWC block of the Salesforce project. While some of them aren’t mandatory, they play a crucial role in maintaining code quality and consistency. For instance, they help enforce coding standards such as linting and formatting and facilitate running unit tests locally using Jest.

Here’s a quick recall of key tools you should consider installing:

* **Prettier**: Ensures consistent code formatting.
* **ESLint**: Enforces coding standards and identifies potential issues.
* **Jest**: Enables running unit tests locally.
* **Husky**: Runs pre-commit hooks to enforce linting, formatting, and tests before committing changes.

</details>

Here's an LWC example using the existing backend service `SparkService.cls`:

{% tabs %}
{% tab title="SparkService.html" %}

```html
<template>
  <div class="cs-container">
    <div class="cs-fieldset">
      <lightning-combobox 
          label="Gender" 
          placeholder="Select your gender" 
          options={genderOptions} 
          onchange={handleGenderChanged}>
      </lightning-combobox>
      <div class="cs-fieldset-placeholder">
        <template if:true={generatedOutputs}>
          <pre>{generatedOutputs}</pre>
        </template>
        <template if:false={generatedOutputs}>
          <div class="cs-fieldset-none">No outputs to display yet!</div>
        </template>
      </div>
      <div class="cs-action-buttons">
        <lightning-button
          data-id="copy-btn"
          label="Copy"
          title="Copy"
          disabled={disableButton}
          onclick={handleCopy}
          variant="brand-outline"
          class="slds-m-top_medium"
        >
        </lightning-button>
        <lightning-button
          label="Cancel"
          title="Cancel"
          onclick={cancel}
          disabled={disableButton}
          variant="brand"
          class="slds-m-top_medium slds-m-left_x-small"
        >
        </lightning-button>
      </div>
    </div>
  </div>
</template>
```

{% endtab %}

{% tab title="SparkService.css" %}

```css
.cs-container {
  width: 100%;
  height: 100%;
  min-width: 792px;
  min-height: 480px;
  overflow: auto;
  background: #fff;
  border: 1px solid #d8dde6;
  padding: 5px;
}

.cs-fieldset {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.cs-fieldset-placeholder {
  flex-grow: 1;
  overflow: auto;
  height: 400px;
  max-height: 100%;
  max-width: 782px;
  padding: 8px;
  border: 1px dashed #d8dde6;
  background-color: #f4f6f9;
}

.cs-fieldset-placeholder pre {
  margin: 0;
}

.cs-fieldset-none {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  color: #a7b0be;
  font-size: 1.2rem;
}

.cs-action-buttons {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  border-top: 1px solid #d8dde6;
}
```

{% endtab %}

{% tab title="SparkService.js" %}

```javascript
import { LightningElement } from 'lwc';
import callSparkService from '@salesforce/apex/SparkService.execute';

export default class SparkServiceComponent extends LightningElement {
  // The inputs required by my-service.
  gender = 'Male';

  // The outputs generated by my-service.
  generatedOutputs = null;

  get disableButton() {
    return !this.generatedOutputs;
  }

  get genderOptions() {
    return [
      { label: 'Male', value: 'M' },
      { label: 'Female', value: 'F' },
      { label: 'Other', value: 'O' }
    ];
  }

  handleGenderChanged(event) {
    if (this.gender && event.detail.value === this.gender) return;
    this.gender = event.detail.value;

    const serviceUri = 'folders/my-folder/services/my-service';
    const payload = { request_data: { inputs: this.gender } };

    // Invoke callouts to Coherent Spark service
    callSparkService({ serviceUri, payload })
      .then((result) => (this.generatedOutputs = typeof result === 'string' ? JSON.parse(result) : result))
      .catch(console.log);
  }

  async handleCopy() {
    console.log(this.generatedOutputs);
    // do something with the generated outputs
  }
}
```

{% endtab %}

{% tab title="SparkService.test.js" %}

```javascript
import { createElement } from 'lwc';
import SparkServiceComponent from 'c/SparkService';
import callSparkService from '@salesforce/apex/SparkService.execute';

// Mocking imperative Apex method calls
jest.mock('@salesforce/apex/SparkService.execute', () => ({ default: jest.fn() }), { virtual: true });

// Helper function to wait until the microtask queue is empty. 
// This is needed for promise timing when calling imperative Apex.
async function flushPromises() {
  return Promise.resolve();
}

describe('c-spark-service', () => {
  afterEach(() => {
    // The jsdom instance is shared across test cases in a single file so reset the DOM
    while (document.body.firstChild) {
      document.body.removeChild(document.body.firstChild);
    }

    // Clear mocks so that every test run has a clean implementation
    jest.clearAllMocks();
  });

  test('renders select element with options', async () => {
    callSparkService.mockResolvedValue({ response_data: { outputs: {} } }); // should returns default view model.

    const element = createElement('c-spark-service', { is: SparkServiceComponent });
    document.body.appendChild(element);

    await flushPromises(); // wait for any asynchronous DOM updates.

    const selectEl = element.shadowRoot.querySelector('lightning-combobox');
    expect(selectEl).not.toBeNull();
    expect(selectEl.options).toHaveLength(3);
    expect(selectEl.options[0].label).toBe('Male');
    expect(selectEl.options[1].label).toBe('Female');
    expect(selectEl.options[2].label).toBe('Other');
  });
});
```

{% endtab %}
{% endtabs %}

### Phase 3: Custom Metadata

Custom metadata in Salesforce stores reusable, deployable configurations for application logic, such as business rules or integration settings. It’s ideal for static data, as records can be queried in Apex without consuming SOQL limits.

Custom metadata also supports custom fields, declarative setup, and seamless deployment between orgs, making it a powerful tool for centralizing and managing configurations efficiently. Hence, it makes sense to store Spark settings such as base URL, tenant name, API key, or other static resources  there.

In the example below, an anonymous Apex script (a more direct way to execute Apex code directly in the SF org) is used to query the defined custom metadata object `Spark_Settings__mdt` and print its content.

```apex
Spark_Settings__mdt customMetadata;
String settingName = 'my-setting';

try {
    customMetadata = [
        SELECT base_url__c, tenant__c
        FROM Spark_Settings__mdt
        WHERE SettingName =:settingName
    ];
} catch (Exception e) {
    System.debug('error occurred while fetching custom metadata');
}

System.debug(customMetadata.base_url__c);
System.debug(customMetadata.tenant__c);
```

### What's next?

This was a brief explanation of how to bring your Excel-based calculation logic to Salesforce. There's definitely more that can be done with Spark within Salesforce. Please reach out to the [Coherent Sales team](https://www.coherent.global/book-a-demo) for more information.&#x20;


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.coherent.global/integrations/salesforce/developer-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
