7

We are building a product that will make use of multiple Lightning Web Components (LWCs), and some of those will make use of @AuraEnabled Apex methods. We also plan to provide an API that non-LWC UI can access. (In the past, we have provided such APIs by using @RestResource Apex classes receiving and sending JSON.)

Our present idea is to go ahead and build the @AuraEnabled Apex code methods/classes first, and then later wrap them in a single @RestResource Apex class that dispatches the requests according to the URL pattern to the relevant @AuraEnabled methods/classes. (I assume in this case the @AuraEnabled(cacheable=true) annotation will be ignored.)

Has anyone done this? Pros/cons?

  • 3
    From a separation of concerns perspective it is better to provide the business logic in one class and to provide appropriate (at)AuraEnabled and (at)RestResource facades in two separate classes that leverage the business logic class. – Phil W Feb 4 at 15:50
  • Yep @PhilW, Yeah fair point. Perhaps the separation should be at the method level e.g. the @AuraEnabled method calls a plain method (also called by the @RestResource code), but then that is close to what I am already proposing. The only extra concern for the @AuraEnabled case is the @AuraEnabled annotation... – Keith C Feb 4 at 16:16
  • 2
    Well, it rather depends on whether the business logic is 1:1 with any one of the methods exposed to the "APIs". I said "facade" because these frequently provide a way to view and interact with the business logic and related data is subtly different ways. One of the benefits of such an approach is allowing you to refactor the behind-the-scenes code without impacting any of your APIs (you can also think of these as the traditional layers in an application - front end<->API<->business logic<->persistence). The API layer might also encapsulate more of the security concerns too. – Phil W Feb 4 at 16:45
  • Hi @PhilW, thanks for your attention here! If a layer does something significant then +1; I'm allergic to thin layers that just cause method interfaces to be duplicated e.g. the ugly controller/helper setup in Aura. – Keith C Feb 4 at 16:49
  • 2
    @PhilW, In this case, I don't think we would want separate access rights for the two access mechanisms, but you are right that security needs to be factored into the approach (as always). – Keith C Feb 4 at 17:57
2

Initial coding of the KISS solution:

global class Aura {

    // Call this from the REST API
    public static Object execute(String operation, String requestJson) {
        switch on operation {
            when 'op1' {
                return op1((Op1Params) JSON.deserialize(requestJson, Op1Params.class));
            }
            when 'op2' {
                return op2((Op2Params) JSON.deserialize(requestJson, Op2Params.class));
            }
            ...
        }
    }

    // Method used by LWC
    @AuraEnabled
    global static Result__c op1(Op1Params params) {
        ...
    }

    // Method used by LWC
    @AuraEnabled
    global static Result__c op2(Op2Params params) {
        ...
    }

    ...
}

and:

@RestResource(urlMapping = '/xyz/...')
global inherited sharing class Rest {

    ....

    @HttpPost
    global static void doPost() {

        Savepoint sp = Database.setSavepoint();
        try {
            dispatch(RestContext.request, RestContext.response);
        } catch (AuraHandledException e) {
            res.responseBody = Blob.valueOf(e.getMessage());
            res.statusCode = BAD_REQUEST;
            Database.rollback(sp);
        } catch (Exception e) {
            res.responseBody = Blob.valueOf(sanitizedMessage(e));
            res.statusCode = INTERNAL_SERVER_ERROR;
            Database.rollback(sp);
        }
    }

    private static void dispatch(RestRequest req, RestResponse res) {

        String[] parts = req.requestURI.split('\\/');
        String operation = parts.size() > 1 ? parts[1] : null;
        String requestJson = req.requestBody.toString();

        Object result = Aura.execute(operation, requestJson);

        res.responseBody = toJsonBlob(result);
        res.statusCode = OK;
    }
}
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.