Read more at jamessimone.net, or return to the homepage





Extendable APIs

API development in Apex (and pretty much every other language) is mostly bespoke, requiring the duplication of rest resources. Let's look at how we can change that using inheritance and polymorphism to power our routes. But first ...

At a certain point in everybody’s object-oriented journey, they are forced to confront a brutal truth of the world — in order to accept the power of polymorphism, sacrifices (typically) must be made. Somewhere, in the grand orchestra of closures and their concomitantly produced methods, a conductor must exist. Somewhere, things must be coupled more tightly than we would like to admit. This necessary evil wears many names; one but suits it for a short time only, and over time its etymological roots grow deeper and wider. We hear it referred to in tongues both ancient and modern, a manic susurrus with many words all converging towards one meaning:

This conductor comes in many forms, but it is this entity that gives shape — gives meaning! — to the greater system it surrounds. Like some demented Rube Goldberg machine, it says “if this, do that.” Like the punch-card machine code that once defaced (or graced, opinion-dependent) the depths of this planet, the conductor knows exactly which instruments should be playing at any given moment. It observes; it coordinates; it flourishes. Deciding how and when to create polymorphic objects is its jam.

Let’s see what good old Uncle Bob has to say on the subject:

The solution to this problem is to bury the switch statement in the basement of an ABSTRACT FACTORY, and never let anyone see it.

Uncle Bob would have our conductor live in the BASEMENT. Otherwise though, his point stands (somewhat charmingly, considering how recently Apex even got switch statements).

He also says:

Duplication may be the root of all evil in software. Many principles and practices have been created for the purpose of controlling or eliminating it.

And what does all this have to do with APIs? Is it true that we'll always need a factory method to dynamically produce polymorphic objects? Read on, dear Reader ...


linkWhat A Typical Apex API Looks Like

classes/OrderService.cls
1link@RestResource(urlMapping='/orders/*')

2linkglobal class OrderService {

3link @HttpPost

4link global static String post(Order order) {

5link /*do something with the order

6link possibly by instantiating another class

7link we will just skip over this not being bulkified for now ...*/

8link return 'Success';

9link }

10link}

This is fine boilerplate (probably) in the event that you only have one or two APIs communicating with external services. For distributed systems with many needs to interact with your Salesforce instance, though, you're going to quickly find yourself saying things like: "what was the syntax for creating the API again? goes and looks it up. Oh, that's right, '@RestResource', and the class needs to be global ...".

Plus, every time some other team needs to interact with some of your objects, your best case scenario is that they agree to honor contracts (in this case, another word for interfaces ...) you put forth as to the shape of the Salesforce objects / classes that you already have in place ... and you'll still need to go ahead and create the extra routes / API methods.

Can we come up with something more dynamic that enables the creation and organization of new and existing routes without all this mess? Can we do so while avoiding the typical Factory switch statement? How might we go about doing so?

Looking at that typical example, several insights may immediately jump to mind about the steps necessary to tame the beast:

linkCreating Dynamic Extendable APIs

Let's write a few tests for an object that will be crucial in the success of implementing a more dynamic set of rest resources within Apex. We'll need a resolver of sorts to extract what some people like to call the "needful information" from incoming rest requests (tests first!):

classes/ApiRequestResolverTests.cls
1link@isTest

2linkprivate class ApiRequestResolverTests {

3link @isTest

4link static void it_should_resolve_api_route() {

5link String endpointName = 'orders';

6link RestRequest req = new RestRequest();

7link req.requestURI = '/services/apexrest/api/' + endpointName + '/';

8link

9link ApiRequestResolver resolver = new ApiRequestResolver(req);

10link

11link System.assertEquals(endpointName.capitalize(), resolver.ApiName);

12link }

13link

14link @isTest

15link static void it_should_handle_non_trailing_slash() {

16link String endpointName = 'orders';

17link RestRequest req = new RestRequest();

18link req.requestURI = '/services/apexrest/api/' + endpointName;

19link

20link ApiRequestResolver resolver = new ApiRequestResolver(req);

21link

22link System.assertEquals(endpointName.capitalize(), resolver.ApiName);

23link }

24link

25link @isTest

26link static void it_should_resolve_request_body() {

27link String body = '{}';

28link RestRequest req = new RestRequest();

29link req.requestBody = Blob.valueOf(body);

30link

31link ApiRequestResolver resolver = new ApiRequestResolver(req);

32link

33link System.assertEquals(body, resolver.RequestBody);

34link }

35link

36link @isTest

37link static void it_should_resolve_request_url_param() {

38link String fakeAccountId = '0016g00000EPjVcXXX';

39link RestRequest req = new RestRequest();

40link req.requestURI = '/services/apexrest/api/account/' + fakeAccountId;

41link

42link ApiRequestResolver resolver = new ApiRequestResolver(req);

43link

44link System.assertEquals(fakeAccountId, resolver.RequestUrlBody);

45link }

46link}

And the (admittedly basic) implementation:

classes/ApiRequestResolver.cls
1linkpublic class ApiRequestResolver {

2link private final String apiBase = '/api/';

3link

4link public String ApiName { get; private set; }

5link public String RequestBody { get; private set; }

6link public String RequestUrlBody { get; private set; }

7link

8link public ApiRequestResolver(RestRequest req) {

9link this.ApiName = this.getApiName(req.requestURI);

10link this.RequestBody = req.requestBody != null ?

11link req.requestBody.toString() :

12link '';

13link //taken straight outta the docs...

14link this.RequestUrlBody = req.requestURI != null ?

15link req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1)

16link : '';

17link }

18link

19link private String getApiName(String requestURI) {

20link if(requestURI == null) { return ''; }

21link Integer apiNameStart = requestURI.indexOf(apiBase) + apiBase.length();

22link Integer lastSlash = requestURI.lastIndexOf('/');

23link Integer apiNameEnd = lastSlash < apiNameStart ? requestURI.length() : lastSlash;

24link return requestURI.substring(apiNameStart, apiNameEnd).capitalize();

25link }

26link}

Our master API class is minimal. It merely defines the HTTP methods that are possible. It's route-agnostic. It creates the ApiRequestResolver, and delegates the rest downwards:

classes/ApiService.cls
1link@RestResource(urlMapping='/api/*')

2linkglobal class ApiService {

3link private static final ApiRequestResolver resolver =

4link new ApiRequestResolver(RestContext.request);

5link

6link @HttpDelete

7link global static Api.Response doDelete() {

8link return Api.Facade.doDelete(resolver);

9link }

10link

11link @HttpGet

12link global static Api.Response doGet() {

13link return Api.Facade.doGet(resolver);

14link }

15link

16link @HttpPatch

17link global static Api.Response patch() {

18link return Api.Facade.doPatch(resolver);

19link }

20link

21link @HttpPost

22link global static Api.Response post() {

23link return Api.Facade.doPost(resolver);

24link }

25link

26link @HttpPut

27link global static Api.Response put() {

28link return Api.Facade.doPut(resolver);

29link }

30link}

The tests are simple, and a pleasure (just one route shown for brevity's sake):

classes/ApiServiceTests.cls
1link@isTest

2linkprivate class ApiServiceTests {

3link @isTest

4link static void it_should_return_fail_for_not_existent_route() {

5link RestContext.request = new RestRequest();

6link RestContext.request.requestURI = '/api/fake/';

7link

8link Api.Response res = ApiService.doGet();

9link

10link System.assertEquals(false, res.Success);

11link System.assertEquals(Api.BASIC_RESPONSE, res.ResponseBody);

12link }

13link

14link @isTest

15link static void it_should_return_true_for_existing_route() {

16link RestContext.request = new RestRequest();

17link RestContext.request.requestURI = '/api/test/';

18link

19link //have to insert the "namespace" for the test class

20link //otherwise it will fail to dynamically build correctly

21link Api.HANDLER_NAME = 'ApiServiceTests.' + Api.HANDLER_NAME;

22link

23link Api.Response res = ApiService.doGet();

24link

25link System.assertEquals(true, res.Success);

26link System.assertEquals(TEST_RESPONSE, res.ResponseBody);

27link }

28link

29link static String TEST_RESPONSE = 'test';

30link public class ApiHandlerTest extends Api.Handler {

31link public override Api.Response doGet(ApiRequestResolver resolver) {

32link Api.Response res = this.getResponse(TEST_RESPONSE);

33link res.Success = true;

34link return res;

35link }

36link }

37link}

At a very high level, if you were to see only these tests, you would probably understand 95% of how to interact with a given API:

When I first wrote this article, I had the facade in a different, stand-alone, class ... but when I took a break from writing and thought more about it, I realized that for me, personally, encapsulating the different aspects of the API into one abstract class (a pseudo-namespace, in other words) was appealing. Your mileage may vary. I find things fit nicely into classes like this, provided they don't get too large; minimizing the surface area I need to grok when re-reading and re-remembering. Plus, the stand-alone facade class was still reaching into the Api class to make use of the constant HANDLER_NAME — a code smell that I wasn't pleased with.

It's possible you'll have strong feelings about this pseudo-namespace and want things separated. To be clear — I think that's perfectly fine. What works for me won't always work for you. This article is meant more as an exercise in the creation of dynamic APIs rather than an interjection on the best file structure:

classes/Api.cls
1linkglobal abstract class Api {

2link public static String HANDLER_NAME = 'ApiHandler';

3link public static final String BASIC_RESPONSE = 'HTTP method not yet implemented';

4link

5link global class Response {

6link global Response(String body) {

7link this.Success = false;

8link this.ResponseBody = body;

9link }

10link

11link public Boolean Success { get; set; }

12link public String ResponseBody { get; private set; }

13link }

14link

15link public static Facade Facade {

16link get {

17link if(Facade == null) {

18link Facade = new Facade();

19link }

20link return Facade;

21link }

22link private set;

23link }

24link

25link public class Facade {

26link private Facade() { }

27link

28link public Response doDelete(ApiRequestResolver resolver) {

29link return this.getHandler(resolver).doDelete(resolver);

30link }

31link

32link public Response doGet(ApiRequestResolver resolver) {

33link return this.getHandler(resolver).doGet(resolver);

34link }

35link

36link public Response doPatch(ApiRequestResolver resolver) {

37link return this.getHandler(resolver).doPatch(resolver);

38link }

39link

40link public Response doPost(ApiRequestResolver resolver) {

41link return this.getHandler(resolver).doPost(resolver);

42link }

43link

44link public Response doPut(ApiRequestResolver resolver) {

45link return this.getHandler(resolver).doPut(resolver);

46link }

47link

48link public Handler getHandler(ApiRequestResolver resolver) {

49link Type handlerType = Type.forName(Api.HANDLER_NAME + resolver.ApiName);

50link return handlerType != null ? (Api.Handler)handlerType.newInstance() : new Api.Handler();

51link }

52link }

53link

54link public virtual class Handler {

55link //enforce zero argument constructor

56link public Handler() { }

57link

58link public virtual Response doDelete(RequestResolver resolver) {

59link return getResponse(BASIC_RESPONSE);

60link }

61link public virtual Response doGet(RequestResolver resolver) {

62link return getResponse(BASIC_RESPONSE);

63link }

64link public virtual Response doPatch(RequestResolver resolver) {

65link return getResponse(BASIC_RESPONSE);

66link }

67link public virtual Response doPost(RequestResolver resolver) {

68link return getResponse(BASIC_RESPONSE);

69link }

70link public virtual Response doPut(RequestResolver resolver) {

71link return getResponse(BASIC_RESPONSE);

72link }

73link

74link protected Response getResponse(String responseBody) {

75link return new Response(responseBody);

76link }

77link }

78link}

So, in the end, our conductor has been delegated to Type.forName(String yourTypeName) — a coupling that I ordinarily tend to > > shy away from; within the system, it's easy to pass around Type references (for those following along, you may recall that that > > is precisely how the Callback interface works), but it's not as though we > > have that luxury when interacting with cross-system consumers. Furthermore, it makes sense that if a tight coupling should > > > > exist, that it be between the name of the API and the underlying objects.

Now, I'm not saying that I know the underlying specifics of how "Type.forName" is implemented ... but if I had to guess Salesforce just does a lookup to the ApexClass table based on the name you provide (and the namespace, if you're in the business of creating managed packages). Not quite an if/else or switch statement, but since we're delegating ... who knows!

I'll end this section by saying that I think there's plenty of room for improvement in what I'm showing off. In an actual implementation of this, for example, I would likely:

One thing I considered, but wouldn't actually do? Wrapping the virtual methods within the Handler in try/catch. If your operation has some chance of failing, it should be within the override that you try/catch.

linkImplementing New Routes

Implementing new APIs off of the base /api/ route is now as simple as adding classes that extend Api.Handler with names that start with "ApiHandler" (of course you could tweak "ApiHandler" if that naming convention doesn't appeal to you) — I'll show an example API designed to fetch accounts. It should be noted that this GET request can be formulated either with:

classes/ApiHandlerAccount.cls
1linkpublic class ApiHandlerAccount extends Api.Handler {

2link public final static String NOT_FOUND = 'Account Not Found';

3link

4link private final IRepository accountRepo;

5link

6link public ApiHandlerAccount() {

7link super();

8link //install your dependencies via the Factory

9link this.accountRepo = Factory.getFactory().RepoFactory.getAccountRepo();

10link }

11link

12link public override Api.Response doGet(ApiRequestResolver resolver) {

13link Id accountId = Id.valueOf(resolver.RequestUrlBody);

14link List<Account> accounts = this.accountRepo.get(

15link new Query(

16link Account.Id,

17link Query.Operator.EQUALS,

18link accountId

19link )

20link );

21link Account acc = accounts.size() > 0 ?

22link accounts[0] :

23link new Account(Name = NOT_FOUND);

24link

25link Api.Response res = this.getResponse(Json.serialize(acc));

26link res.Success = accounts.size() > 0;

27link return res;

28link }

29link}

You probably wouldn't be serializing the whole Account to return to your calling service — this is just an example!

If looking at that Factory statement has you going huh!?! then I'll kindly refer you to the Factory & Dependency Injection post. The whole downside of Type.forName — that your object is required to have a zero-argument constructor — can be recovered from if you have the ability to quickly swap out the objects of your choice when testing, plus you can actually assert that you're querying for the correct things, as I'll show with the test:

classes/ApiHandlerAccountTests.cls
1link@isTest

2linkprivate class ApiHandlerAccountTests {

3link @isTest

4link static void it_should_return_stringified_account_to_api() {

5link /*most of my objects have a one-argument

6link constructor that takes in the factory

7link and they use the following syntax:

8link Factory.withMocks.getClassHere.methodUnderTest

9link for zero-argument constructors, we have to explicitly call

10link useMocks prior to testing */

11link Factory.useMocks();

12link //arrange

13link Id accountId = TestingUtils.generateId(Account.SObjectType);

14link Account fakeAccount = new Account(Id = accountId, Name = 'ApiHandlerTest');

15link RepoFactoryMock.QueryResults.add(fakeAccount);

16link RestContext.request = getAccountRequest(accountId);

17link

18link //act

19link Api.Response res = ApiService.doGet();

20link Account deserializedAccount = (Account)Json.deserialize(

21link res.ResponseBody,

22link Schema.Account.class

23link );

24link

25link //assert

26link System.assertEquals(true, res.Success);

27link System.assertEquals(accountId, deserializedAccount.Id);

28link System.assertEquals(fakeAccount.Name, deserializedAccount.Name);

29link

30link Query performedQuery = RepoFactoryMock.QueriesMade[0];

31link System.assertEquals(Account.Id, performedQuery.field);

32link System.assertEquals(Query.Operator.EQUALS, performedQuery.operator);

33link System.assertEquals(accountId, performedQuery.predicates[0]);

34link }

35link

36link @isTest

37link static void it_should_return_account_not_found_for_no_results() {

38link //no need to mock the result

39link //we can go straight to the db here

40link //even though there's no result

41link // it's still 8x slower than mocking!!

42link RestContext.request = getAccountRequest(

43link TestingUtils.generateId(Account.SObjectType)

44link );

45link

46link Api.Response res = ApiService.doGet();

47link Account deserializedAccount = (Account)Json.deserialize(

48link res.ResponseBody,

49link Schema.Account.class

50link );

51link

52link System.assertEquals(false, res.Success);

53link System.assertEquals(ApiHandlerAccount.NOT_FOUND, deserializedAccount.Name);

54link }

55link

56link static RestRequest getAccountRequest(Id accountId) {

57link RestRequest req = new RestRequest();

58link req.requestURI = '/api/account/' + accountId;

59link return req;

60link }

61link}

And there you have it. Our first test runs in two-hundredths of a second on average, making the one-tenth of a second for the second test (the one that actually performs a SOQL query) seem positively pokey by comparison. Our first test is capable of verifying not only that the expected Account is returned to us, but also that the query that returns the Account is in fact exactly what we expect. That's huge.

linkWrapping Up

Thanks for reading this Joys Of Apex post! I sincerely hope you learned something and enjoyed the process. The full code from this example is available on my Github if you'd like to browse through the classes in a bit more detail. It's my hope that the code will prove thought-provoking when you consider your existing APIs, and that you would consider using it on a greenfield project; for the truly brave, there's little downside to gradually migrating your existing APIs to be housed under a single route.

Of course, as with any concept, what I've talked about here doesn't cover the full breadth of what you'll need to consider when using this pattern with SFDC. Just off the top of my head:

The original version of Extendable APIs can be read on my blog.

What A Typical Apex API Looks LikeCreating Dynamic Extendable APIsImplementing New RoutesWrapping Up

Home Apex Logging Service Apex Object-Oriented Basics Batchable And Queueable Apex Building A Better Singleton Continuous Integration With SFDX Dependency Injection & Factory Pattern Enum Apex Class Gotchas Extendable Apis Future Methods, Callouts & Callbacks Idiomatic Salesforce Apex Introduction & Testing Philosophy Lazy Iterators Lightweight Trigger Handler LWC Composable Modal LWC Composable Pagination LWC Custom Lead Path Mocking DML React Versus Lightning Web Components Refactoring Tips & Tricks Repository Pattern setTimeout & Implementing Delays Sorting And Performance In Apex Test Driven Development Example Testing Custom Permissions Writing Performant Apex Tests



Read more tech articles