Coding the Web Service Call

Previous Step: Creating a Service Registry

When you have created a service registry, you need to create your web service script. This script:
  • gets Service objects from the registry
  • provides input parameters for the createRequest callback used to construct the URL to call the web service
  • provides error handling based on the Result of the web service call
  • writes output variables to the pipeline dictionary to be used by the rendering template

The general approach to coding web services in Salesforce B2C Commerce involves writing a script for a RESTful web service and storing credentials or certificates in the Business Manager.

However, if you are implementing a SOAP web service or require WS-Security features, you can also write a script that uses the dw.ws package to implement web services. In this case, certificates must be stored in your cartridge. See SOAP Web Services

Creating a Controller for Web Services

When creating a controller for web services, you usually need to include error handling and render the results of the web service call.

Understanding Pipelines for Web Services in Legacy Implementations

If you are maintaining an older cartridge that uses pipelines, web services are implemented in a pipeline with a script node for the web service script. The script node must have an error transition for error handling and a transition to a rendering template if you need to render the results of the web service call.

Creating a Web Service Script

Your web service script needs to import the dw.svc package.

Use ServiceRegistry.get to create a Service object from the ServiceRegistry. The Service object is basically a one-time use ServiceDefinition. Your script can modify or extend the Service object. You can also include business logic to control the parameters used to invoke the web service.

Use Service.call to invoke the web service.

When the service is invoked, B2C Commerce checks the circuit breaker and rate limiter. If either the circuit breaker or rate limiter are triggered, then no call is made and the appropriate errors are logged. If neither are triggered, then the callback methods in the ServiceDefinition are executed in logical order and the dw.svc.Result object is stored in the Service.

Example 1: Simple FTP Service Call

In this example, the operation invoked for the web service is determined by an input variable passed from the pipeline. In the dictionary bindings for the script node, the numCalls variable is bound to CurrentHttpParameterMap.numCalls.stringValue and the testType variable is bound to CurrentHttpParameterMap.testType.stringValue.

/**
*  FTPClient.ds
*
*   @input testType : String
*   @input numCalls : String
*   @output svcConfig : dw.svc.Service
*   @output ftpResult : dw.svc.Result
*
*/
importPackage( dw.util );
importPackage( dw.svc );
importPackage( dw.net );
importPackage( dw.io );

function execute( args : PipelineDictionary ) : Number
{	
	switch ( args.testType ) {   	
   	  case "LIST":
   	    args.svcConfig = ServiceRegistry.get("mycart.ftp");
        args.ftpResult = args.svcConfig.setOperation("list").call();
        break;
      case "CD":
   	    args.svcConfig = ServiceRegistry.get("mycart.ftp");
        args.ftpResult = args.svcConfig.setOperation("cd", "/").call();
        break;
      case "MKDIR": 
   	    args.svcConfig = ServiceRegistry.get("mycart.ftp");
        args.ftpResult = args.svcConfig.setOperation("mkdir", "test").call();
        break;
      case "DELETE":
   	    args.svcConfig = ServiceRegistry.get("mycart.ftp");
        args.ftpResult = args.svcConfig.setOperation("del", "test").call();
        break;
	}
	
	if (args.ftpResult == null || args.svcConfig == null ){
   	  return PIPELET_ERROR;
    }
    
    return PIPELET_NEXT;  
}

Example 2: Modifying the Service Object for an HTTP Service

This example demonstrates how you can use the methods on the Service object to modify the Service object before invoking the service. The ENCODING case in this example adds a header, URL parameters, and changes the encoding before invoking the service.

/**
* HTTPClient.ds
*
*	@input testType : String the type of test to run.
*	@input numCalls : String the number of calls to make.
*	@input returnCode : String the simulated http return code to return.
*	@output svcConfig : dw.svc.Service the service configuration object.
*	@output httpResult : dw.svc.Result the http result object.
*
*/
importPackage( dw.system );
importPackage( dw.svc );
importPackage( dw.net );
importPackage( dw.io );

function execute( args : PipelineDictionary ) : Number
{
	
   switch ( args.testType ) {   
	// do a POST request with additional headers, query param, json payload, using UTF-32 encoding
   case "ENCODING":
     args.svcConfig = ServiceRegistry.get("mycart.http.post");
      args.httpResult = args.svcConfig.addHeader('testHeader', 'testHeaderValue')
    			  .addParam('filter', true)
     			  .setEncoding('UTF-16')
     			  .call({'testString':'foo', 'testNum': 5, 'testBool': true});
      break;
   
   // do a PUT request with additional headers, query param, json payload
   case "PUT":
     args.svcConfig = ServiceRegistry.get("mycart.http.put");
     args.svcConfig.url += "/put";
     
      args.httpResult = args.svcConfig.addHeader('testHeader', 'testHeaderValue')
      			  .addParam('filter', true)
      			  .addParam('secondParamAmpersand', 1)
      			  .call({'testString':'foo', 'testNum': 5, 'testBool': true});
      break;
    
    // do a DELETE request with additional headers, query param, json payload
    case "DELETE":
     args.svcConfig = ServiceRegistry.get("mycart.http.delete");
     args.svcConfig.url += "/delete";
     
      args.httpResult = args.svcConfig.addHeader('testHeader', 'testHeaderValue')
      			  .addParam('filter', true)
      			  .call({'testString':'foo', 'testNum': 5, 'testBool': true});
      break;
    
    // do an HTTP post with BASIC auth.
    case "BASICAUTH":
      args.svcConfig = ServiceRegistry.get("mycart.http.basicauth");
      args.httpResult = args.svcConfig.addHeader('testHeader', 'testHeaderValue')
      			  .addParam('filter', true)
      			  .setEncoding('UTF-8')
      			  .call({'testString':'foo', 'testNum': 5, 'testBool': true});
      break;
   
    // do a GET request with timeout
    case "TIMEOUT":
      args.svcConfig = ServiceRegistry.get("mycart.http.timeout");
      args.httpResult = args.svcConfig.call();      
      break;
    
    // do a GET request and write result object to file
    case "OUTFILE":
      args.svcConfig = ServiceRegistry.get("mycart.http.get");
      args.httpResult = args.svcConfig.setOutFile(File("TEMP/"+ Math.random()))
      			  .call();      
      break;
      
    case "MOCKED":
      args.svcConfig = ServiceRegistry.get("mycart.http.get");
      args.httpResult = args.svcConfig.mock().call();      
      break;
   
   if (args.httpResult == null || args.svcConfig == null ){
   	  return PIPELET_ERROR;
   }

   return PIPELET_NEXT;

Error Handling

Information on the service status is stored in the Result object. Use methods on the object to determine the status of the service and any error it has returned:
  • result.error - service-type specific error code, such as a 404 for an HTTP service.
  • result.errorMessage - error message information, such as a Java exception

You can also call .setThrowOnError when calling the service to throw a JavaScript error if the result status is not OK:

result = ServiceRegistry.get("MyFTPService").setThrowOnError().call();

Next Step: Web Service Logging and Troubleshooting