mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 11:19:27 +08:00
8.2 KiB
8.2 KiB
External Services Guide
Overview
External Services in Salesforce automatically generate Apex classes from OpenAPI (Swagger) specifications, enabling type-safe REST API integrations without writing HTTP code.
How It Works
┌─────────────────────────────────────────────────────────────────┐
│ EXTERNAL SERVICE FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. OpenAPI Spec (JSON/YAML) │
│ ↓ │
│ 2. External Service Registration (Metadata) │
│ ↓ │
│ 3. Auto-generated Apex Classes │
│ ↓ │
│ 4. Type-safe API calls from Apex │
│ │
└─────────────────────────────────────────────────────────────────┘
Prerequisites
- Named Credential configured for authentication
- OpenAPI Specification (2.0 or 3.0) for the API
- API Version 48.0+ (Winter '20)
Creating External Service
Via Setup UI
- Go to Setup → External Services
- Click New External Service
- Provide name and description
- Select Named Credential
- Upload or paste OpenAPI spec
- Review operations
- Save
Via Metadata API
<?xml version="1.0" encoding="UTF-8"?>
<ExternalServiceRegistration xmlns="http://soap.sforce.com/2006/04/metadata">
<label>Stripe API</label>
<description>Stripe payment processing API</description>
<namedCredential>Stripe_API</namedCredential>
<schemaType>OpenApi3</schemaType>
<schema>
{
"openapi": "3.0.0",
"info": { "title": "Stripe API", "version": "1.0" },
"paths": { ... }
}
</schema>
<status>Complete</status>
</ExternalServiceRegistration>
Generated Classes
For an External Service named "StripeAPI":
| Class | Purpose |
|---|---|
ExternalService.StripeAPI |
Main service class with operation methods |
ExternalService.StripeAPI_createCustomer_Request |
Request wrapper |
ExternalService.StripeAPI_createCustomer_Response |
Response wrapper |
ExternalService.StripeAPI_Customer |
DTO matching schema |
Usage Patterns
Basic GET Request
// Instantiate service
ExternalService.StripeAPI stripe = new ExternalService.StripeAPI();
// Call GET operation
ExternalService.StripeAPI_getCustomer_Response response =
stripe.getCustomer('cus_ABC123');
// Access response data
String email = response.email;
POST with Request Body
// Create request object
ExternalService.StripeAPI_createCustomer_Request request =
new ExternalService.StripeAPI_createCustomer_Request();
request.email = 'customer@example.com';
request.name = 'John Doe';
// Make call
ExternalService.StripeAPI_createCustomer_Response response =
stripe.createCustomer(request);
// Get created resource ID
String customerId = response.id;
Handling Nested Objects
// Access nested data
ExternalService.StripeAPI_Address address = response.address;
String city = address.city;
String postalCode = address.postalCode;
Error Handling
try {
ExternalService.StripeAPI stripe = new ExternalService.StripeAPI();
response = stripe.getCustomer('invalid_id');
} catch (ExternalService.ExternalServiceException e) {
// API returned error response
System.debug('Status Code: ' + e.getStatusCode());
System.debug('Error Body: ' + e.getBody());
System.debug('Error Message: ' + e.getMessage());
} catch (CalloutException e) {
// Network/connection error
System.debug('Connection failed: ' + e.getMessage());
}
Async Calls (Queueable)
Use Queueable for calls from triggers:
public class StripeCustomerSync implements Queueable, Database.AllowsCallouts {
private Account account;
public StripeCustomerSync(Account account) {
this.account = account;
}
public void execute(QueueableContext context) {
ExternalService.StripeAPI stripe = new ExternalService.StripeAPI();
ExternalService.StripeAPI_createCustomer_Request req =
new ExternalService.StripeAPI_createCustomer_Request();
req.email = account.Email__c;
req.name = account.Name;
try {
ExternalService.StripeAPI_createCustomer_Response resp =
stripe.createCustomer(req);
account.Stripe_Customer_Id__c = resp.id;
update account;
} catch (Exception e) {
System.debug('Sync failed: ' + e.getMessage());
}
}
}
OpenAPI Schema Tips
Supported Features
- GET, POST, PUT, PATCH, DELETE methods
- Path and query parameters
- Request and response bodies
- JSON schema types (string, number, boolean, object, array)
- References ($ref)
- Basic authentication headers
Limitations
- No file uploads (multipart/form-data limited)
- No WebSockets (HTTP only)
- No streaming responses
- Some complex schemas may not parse
- Maximum schema size limited
Schema Best Practices
{
"openapi": "3.0.0",
"info": {
"title": "My API",
"version": "1.0.0"
},
"paths": {
"/customers/{id}": {
"get": {
"operationId": "getCustomer",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Customer" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"Customer": {
"type": "object",
"properties": {
"id": { "type": "string" },
"email": { "type": "string" }
}
}
}
}
}
Updating External Service
When API changes:
- Get updated OpenAPI spec
- Go to Setup → External Services
- Edit service
- Upload new schema
- Review changes to operations
- Save and validate
- Update calling code if signatures changed
Testing
@isTest
private class StripeIntegrationTest {
@isTest
static void testCreateCustomer() {
// Set mock
Test.setMock(HttpCalloutMock.class, new StripeMock());
Test.startTest();
ExternalService.StripeAPI stripe = new ExternalService.StripeAPI();
ExternalService.StripeAPI_createCustomer_Request req =
new ExternalService.StripeAPI_createCustomer_Request();
req.email = 'test@example.com';
ExternalService.StripeAPI_createCustomer_Response resp =
stripe.createCustomer(req);
Test.stopTest();
System.assertEquals('cus_test123', resp.id);
}
private class StripeMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest request) {
HttpResponse response = new HttpResponse();
response.setStatusCode(201);
response.setBody('{"id": "cus_test123", "email": "test@example.com"}');
return response;
}
}
}
Use with Agentforce
External Services are ideal for Agent Actions:
- Create External Service from API spec
- Create Flow that calls External Service
- Reference Flow in Agent Script action
actions:
lookup_customer:
description: "Looks up customer in payment system"
inputs:
customer_email: string
outputs:
customer_id: string
target: "flow://Lookup_Stripe_Customer"