Best practice for creating a service in SFCC

Let's say we want to query an external REST API, and will use LocalServiceRegistry for this. What is the best approach for creating our service with a proper encapsulation?

With our service implementation we want to archieve the following goals:

  • don't throw an error in case the service isn't created in the Business Manager yet
  • keep any errors within the service, so the success status is available on the dw.svc.Result object
  • read mock responses from an ISML template
  • have the communication log available on production if required
  • have a flexible way for calling multiple endpoints of the same service

Our actual implementation could now look like this.

let LocalServiceRegistry = require('dw/svc/LocalServiceRegistry');
let myQueryService;

function getDetails(operation) {
    switch (operation) {
    case module.exports.OPERATION_QUERY:
        return {
            method: 'GET',
            resourceURL: '/query',
            mockType: 'query'
        };
    default:
        throw new Error('Operation unknown: ' + operation);
    }
}

function getMyQueryService() {
    return LocalServiceRegistry.createService('my.query.service', {

        mockFull : function(service, operation) {
            log.info('Doing mock call for: {0}', operation);

            let Template = require('dw/util/Template');
            let details = getDetails(operation);
            let template = new Template('query/mock_' + details.mockType + '.isml');
            return template.render();
        },

        createRequest: function(service, operation, params) {
            const details = getDetails(operation);

            service.setRequestMethod(details.method);
            let baseUrl = service.configuration.credential.URL;
            let resourceUrl = operationDetails.resourceURL;
            if (operation === module.exports.OPERATION_QUERY) {
                resourceUrl += '?param1=' + encodeURIComponent(params.param1);
            }
            service.setURL(baseUrl + resourceUrl);

            return null;
        },

        parseResponse: function(service, responseObject) {
            if (responseObject.statusCode !== 200) {
                throw new Error('Invalid service response: ' + responseObject.statusCode);
            }
            return responseObject.text;
        },

        filterLogMessage: function(msg) {
            return msg;
        }

    });
};

module.exports.get = function get() {
    if (empty(myQueryService)) {
        myQueryService = getMyQueryService();
    }
    return myQueryService;
};

module.exports.OPERATION_QUERY = 'OPERATION_QUERY';

With this service in place, we can use it and call our query endpoint with the following snippet:

let MyQueryService = require('*/.../MyQueryService');
let myQueryService = MyQueryService.get();
let queryResultStatus = myQueryService.call(
    MyQueryService.OPERATION_QUERY,
    {
        'param1': one
    }
);

If queryResultStatus.ok is true, our call was successful. This will archieve our defined goals, keeps the overall architecture fairly simple and makes our service maintainable in the long run!

Contact