WebService - wimvelzeboer/fflib-apex-extensions GitHub Wiki
For retrieving an entry
+-------------------------------------------------+ | Retrieve and sanitised request arguments / body | +-------------------------------------------------+ | V +-----------------------+ | Get the required data | +-----------------------+ | V +---------------------------------------+ | Translate, filter and return the data | +---------------------------------------+
In the following example you see a very basic GET request to retrieve the Account Rating value for the provided AccountId.
The request is send to Https://instanceURL.salesforce.com/services/apexrest/AccountRating/v1.0
and contains a body something like:
{
"AccountIds" :
[
"001000000000001AAA",
"001000000000002AAA"
]
}
{
"accountRatings" :
[
{ "AccountId" : "001000000000001AAA", "Rating" : "Warm" },
{ "AccountId" : "001000000000002AAA", "Rating" : "Hot" }
]
}
As we are dealing with a simple data retrieval from the database, a direct call to the domain is made to retrieve the records. If data comes from multiple objects, we can use multiple domains/selector classes.
When the webservice is supposed to invoke a business process, you should call a service method here.
@RestResource(UrlMapping='/AccountRating/v1.0')
global with sharing class AccountRatingWebService
{
private static final SObjectType ACCOUNT_SOBJECT_TYPE = Schema.Account.SObjectType;
private static final String CRUD_ERROR_MESSAGE = 'Not Authorised to access Accounts';
private static final String FLS_ERROR_MESSAGE = 'Not Authorised to access Rating data';
private static final String INVALID_FORMATTED_REQUEST = 'Invalid formatted request';
private static final String UNHANDLED_EXCEPTION_OCCURRED = 'Unhandled exception occurred';
/**
* Gets the account rating for the given account Id
*/
@HttpGet
global static void doGet()
{
try
{
// Retrieve sanitised request body
GetRequestBody request = getGetRequestBody();
// Validate that the user has the proper access
fflib_SecurityUtils.checkObjectIsReadable(ACCOUNT_SOBJECT_TYPE);
fflib_SecurityUtils.checkFieldIsReadable(ACCOUNT_SOBJECT_TYPE, Account.Rating);
// Get the required data
IAccounts accounts = Accounts.newInstance(request.AccountIds);
// Translate, filter and return the data
RestContext.response.responseBody = new GetResponseBody(accounts).toBlob();
RestContext.response.statusCode = 200;
}
// Unauthorised to SObject
catch (fflib_SecurityUtils.CrudException e)
{
RestContext.response.statusCode = 401;
RestContext.response.responseBody =
new ErrorResponse(CRUD_ERROR_MESSAGE).toBlob();
}
// Unauthorised to Field
catch (fflib_SecurityUtils.FlsException e)
{
RestContext.response.statusCode = 401;
RestContext.response.responseBody =
new ErrorResponse(FLS_ERROR_MESSAGE).toBlob();
}
// JSON formatting error
catch (System.JSONException e)
{
RestContext.response.statusCode = 400;
RestContext.response.responseBody =
new ErrorResponse(INVALID_FORMATTED_REQUEST).toBlob();
}
catch (Exception e)
{
RestContext.response.statusCode = 400;
RestContext.response.responseBody =
new ErrorResponse(UNHANDLED_EXCEPTION_OCCURRED).toBlob();
}
}
public class GetRequestBody
{
public Set<Id> AccountIds { get; set; }
}
global class GetResponseBody
{
public List<AccountRating> AccountRatings { get; set; }
public GetResponseBody(IAccounts accounts)
{
this.AccountRatings = new List<AccountRating>();
for (Account record : accounts.getAccounts())
{
this.AccountRatings.add( new AccountRating(record) );
}
}
public Blob toBlob()
{
return Blob.valueOf(JSON.serialize(this));
}
}
private class AccountRating
{
public Id AccountId { get; set; }
public String Rating { get; set; }
public AccountRating(Account record)
{
this.AccountId = record.Id;
this.Rating = record.Rating;
}
}
public class ErrorResponse
{
public String ErrorMessage { get; set; }
public ErrorResponse(String message)
{
this.ErrorMessage = message;
}
public Blob toBlob()
{
return Blob.valueOf(JSON.serialize(this));
}
}
public class DeveloperException extends Exception {}
}
For updating an exising entry.
In this example we see that the start is the same as the Get request,
but this time we use the domain setter method setRatingById
to modify the values.
Then we use the unitOfWork to write the changes to the database,
and finally we turn a success response code.
There is no real need in this example to return a body.
@HttpPut
global static void doPut()
{
try
{
// Retrieve sanitised request body
PutRequestBody request = getPutRequestBody();
// Validate that the user has the proper access
fflib_SecurityUtils.checkObjectIsReadable(ACCOUNT_SOBJECT_TYPE);
fflib_SecurityUtils.checkFieldIsReadable(ACCOUNT_SOBJECT_TYPE, Schema.Account.Rating);
// Retrieve the records and update the fields
IAccounts accounts = Accounts.newInstance(request.getAccountIds());
accounts.setRatingById(request.getRatingById());
// Send changes to the database
fflib_ISObjectUnitOfWork unitOfWork = Application.UnitOfWork.newInstance(
new List<SObjectType>{ Account.SObjectType}
);
unitOfWork.registerDirty(accounts.getRecords());
unitOfWork.commitWork();
// Return a success response
RestContext.response.statusCode = 200;
}
// Unauthorised to SObject
catch (fflib_SecurityUtils.CrudException e)
{
RestContext.response.statusCode = 401;
RestContext.response.responseBody =
new ErrorResponse(CRUD_ERROR_MESSAGE).toBlob();
}
// Unauthorised to Field
catch (fflib_SecurityUtils.FlsException e)
{
RestContext.response.statusCode = 401;
RestContext.response.responseBody =
new ErrorResponse(FLS_ERROR_MESSAGE).toBlob();
}
// JSON formatting error
catch (System.JSONException e)
{
RestContext.response.statusCode = 400;
RestContext.response.responseBody =
new ErrorResponse(INVALID_FORMATTED_REQUEST).toBlob();
}
catch (Exception e)
{
RestContext.response.statusCode = 400;
RestContext.response.responseBody =
new ErrorResponse(e.getMessage()).toBlob();
}
}
The delete request is very similar to the Put request, but in this case its
@HttpDelete
global static void doDelete()
{
try
{
// Retrieve sanitised request body
DeleteRequestBody request = getDeleteRequestBody();
// Validate that the user has the proper access
fflib_SecurityUtils.checkObjectIsReadable(ACCOUNT_SOBJECT_TYPE);
fflib_SecurityUtils.checkFieldIsReadable(ACCOUNT_SOBJECT_TYPE, Account.Rating);
// Retrieve the records and update the fields
IAccounts accounts = Accounts.newInstance(request.accountIds);
accounts.setRating(null);
// Send changes to the database
fflib_ISObjectUnitOfWork unitOfWork = Application.UnitOfWork.newInstance(
new List<SObjectType>{ Account.SObjectType}
);
unitOfWork.registerDirty(accounts.getRecords());
unitOfWork.commitWork();
// Return a success response
RestContext.response.statusCode = 200;
}
// Unauthorised to SObject
catch (fflib_SecurityUtils.CrudException e)
{
RestContext.response.statusCode = 401;
RestContext.response.responseBody =
new ErrorResponse(CRUD_ERROR_MESSAGE).toBlob();
}
// Unauthorised to Field
catch (fflib_SecurityUtils.FlsException e)
{
RestContext.response.statusCode = 401;
RestContext.response.responseBody =
new ErrorResponse(FLS_ERROR_MESSAGE).toBlob();
}
// JSON formatting error
catch (System.JSONException e)
{
RestContext.response.statusCode = 400;
RestContext.response.responseBody =
new ErrorResponse(INVALID_FORMATTED_REQUEST).toBlob();
}
catch (Exception e)
{
RestContext.response.statusCode = 400;
RestContext.response.responseBody =
new ErrorResponse(e.getMessage()).toBlob();
}
}
Used for retrieving data which is the result of a complex business operation. Typically, a complex getter which retrieves data from multiple objects and/or processes.
Always include versioning when developing webservices. Webservices can be used by multiple applications intergrating into your application,
they cannot upgrade all at once when you release a new update of the REST WebService.
As you can see these examples, they contain a version number in the UrlMapping:
@RestResource(UrlMapping='/AccountRating/v1.0')
global with sharing class AccountRatingWebService { ... }
@RestResource(UrlMapping='/AccountRating/v2.0')
global with sharing class AccountRatingWebService2 { ... }