TIL Thursday: Find Out About the New Service Controllers

A fundamental architecture change occurred in Vinson: we changed the way we code services — moving from static service files to ones generated by service controllers. While we've documented these changes, I wanted to find out more and dig a little deeper.

With the help of Gerardo Rodriguez, a senior software engineer in our ecommerce team, we're going to take a closer look at the services refactor. Here's a summary of what's covered:

  • New architecture: the 'family tree' approach
  • Asynchronous module definition (AMD), like our other module code
  • Centralized logic so now it's not dispersed and repeated
  • Events and extensibility built in
  • Permissions validation
  • Backwards compatibility and whether it's worth converting old services

This is aimed at more advanced users who interested in finding out what our team's thought process was going into making this change, and who want to see what's under the hood.

The Family Tree

The change brings in a lot of new files. Previously, to get a service to work in a module, you only needed two files: a service and a model. Now, in Vinson and later, there are six files. While this is additional complexity, it actually makes thing easier when we consider that three of these files are as 'grandparents' and 'parents' to the other files. Gerardo says it's best to think of the new architecture like a family tree:

  1. SC.EventWrapper.js — the grandparent, which gives all of the family some capabilities
  2. ServiceController.js — the parent, which centralizes the logic for all service controllers
  3. ServiceController.Validations.js — a step-parent, which standardizes validation types
  4. <service>.ServiceController.js — the child which contains the logic specific to a module's service

From top to bottom, various bits of logic and capabilities are passed on until you get the final service (.ss) file, which is automatically generated by the developer tools when you run gulp, and you still need a model. Let's take a look at these new files now.

SC.EventWrapper.js — The Grandparent

SspLibraries > SuiteScript > SC.EventWrapper.js

As mentioned, this file acts as a grandparent, bestowing an important function onto all of its children and grandchildren: the ability to execute code before or after a service fires a method. Want something to happen before a GET? Done. How about after a POST? Easy. I'll show you how later.

ServiceController.js — The Parent

SspLibraries > SuiteScript > ServiceController.js

This is the controller used by all other controllers. It centralizes all of the logic that would have previously been contained in each of the individual .ss files.

It extends the grandparent file and prepares the way for its children.

The most important aspect of this is the handle function, which replaces the try/catch code in the individual files. This, overall, makes the whole process simpler for developers who want to write services, as service controllers now look a lot like a standard AMD file.

One important change you may have noticed in this function is that we now pass the request and response as parameters, which means they are now always available in this environment.

ServiceController.Validations.js — The Step-Parent

SspLibraries > SuiteScript > ServiceController.Validations.js

I call it a step-parent (Gerardo doesn't) because it's a dependency of ServiceController.js and thus it is inherited by all of the children. It standardizes a lot of validations that need to be performed when particular methods are called.

For example, before a service requests a list of items to be reordered, we want to check that the user is logged in first. But the service that does this isn't the only one that wants to check if the user is logged in, this happens across many services. This process is now simplified down to putting a property into your service controller that says requireLogin: true.

<service>.ServiceController.js — The Child

You create one of these per service. These are what most closely resemble the old service files of the past. You still put in the different HTTP methods that you need but now you extend ServiceController and set the methods as properties. These will then be used by Gulp to build the .ss files.

Examples of The Family Tree

To help put this altogether, it's best to look examples. look at ReorderItems > SuiteScript > ReorderItems.ServiceController.js. If you have access to Mont Blanc or older code, open up ReorderItems.Service.ss next to it and you'll get a greater sense of the changes.

The Basics with ReorderItems.ServiceController.js

The first thing that'll hit you is that it's AMD. We start with a define statement, name it, and list dependencies.

Next: no more try/catch. Now we're returning the service controller that we're extending and give it a name.

Now, if you have the old file at hand, you'll see that instead of having loads of code defining permissions, we have options. options lines up with the validateRequest function in ServiceController.js, which uses the code in ServiceController.Validations.js.

The common property is used to list all of the validation rules that apply to every method this service calls. So, for example, we no longer have to wrap all of the code in a massive conditional statement to see if the user is logged in, so we just use requireLogin: true. The code we use is from the step-parent, and is thus repeatable and easily reused by other services.

You'll also note that the next is requirePermissions, where then do list a number of permissions specific to the service. But it's far simpler, and follows a standard pattern.

Method-Specific Validation in ReturnAuthorization.ServiceController.js

Next, take a look at service controller we use for return authorization. It has very similar common properties to the service controller for ReorderItems, but you'll also see it has some post additional validation.

The extraList property allows you to specify validations that need to run only on specified methods. In this example, we're specifying that when a POST is made, transactions.tranRtnAuth.2 must be true in addition to all of the common permissions. I'll talk a little more about this later, but you should see how simple this is.

The Auto-Generated File: ReturnAuthorization.Service.ss

Auto-generated! Amazing!

Open up LocalDistribution > Services and take a look at the service files. They're all really short! All they do is require the module's service controller and its handle method that passes the request and response.

All the actual code lives in ssp_libraries.js after its been processed.

ns.package.json, distro.json and Gulp

I would be remiss if I didn't go over how the supporting files have changed, as well as a quick look at the dev tools.

ns.package.json

For this, we added an object for the auto-generated services called autogenerated-services:

We're mapping services to service controllers, this is how we let the developer tools know. The cool thing is that if you want to run an old-style service, then you can, and we'll talk about this in the backwards compatibility section later.

distro.json

Open it up and scroll down to the object for ssp-libraries. If you compare it to an older distro.json, you'll see that we now list all of the service controllers, rather than the models, and that are a lot more of them.

Gulp Generation Process

Let's take a look at the generation process. Run the following commands:

gulp clean
gulp services

This'll clear out the distribution folders and then create the services in the LocalDistribution folder. Take a look at the list, you'll see that they match up to the list service controllers in the ssp-libraries object.

If you want to deploy them, you can run:

gulp deploy --source services

This'll just deploy the service files. If we want to deploy the service controllers, then we need to deploy the SSP libraries too.

Extending the Event Wrapper Example

OK, you've been sitting patiently so let's do a little bit of coding.

This is only going to be temporary, so we're not going to bother with a custom module, we're going to put it straight into the source module.

I mentioned at the start that one of the things we've built into the new code is extensibility. What we're going to do is add in some arbitrary code that fires before and after ProductReviews.ServiceController.js fires. You'll obviously need this functionality enabled on your site, otherwise you'll need to modify a different one.

In ProductReviews > SuiteScript, create ProductReviews.Extended.ServiceController.js and in it put the following:

define ('ProductReviews.Extended.ServiceController'
, [
    'Application'
  , 'ServiceController'
  , 'ProductReviews.ServiceController'
  ]
, function (
    Application
  , ServiceController
  , ProductReviewsServiceController
  )
  {
    'use strict';

    Application.on('before:ProductReviews.ServiceController.get', function()
    {
      console.log('Before you get');
    });

    Application.on('after:ProductReviews.ServiceController.get', function()
    {
      console.log('After you get');
    });
  }
);

Here, we're extending the service controller. Remember how the best way to add our own customizations, is to extend the source code rather than replace it? This is another example of that philosophy.

What next? Well, we're not creating a new service, so we don't need to update ns.package.json. We do want it to be included in the code which is deployed, so we need to update distro.json. In the ssp-libraries object, add ProductReviews.Extended.ServiceController to the dependencies.

Run gulp to compile the code. When it's finished, take a look in LocalDistribution > ssp_libraries.js. Do a search for "Before you get" and you should see your code:

Push it up to your site with:

gulp deploy --source ssp-libraries

If you go to the file cabinet and view the file, you'll see it. Now let's test it.

You'll need to go a page on your site that uses the product reviews functionality; the most obvious example is a product detail page. Once the page loads, check that the service has been called in the Network tab of your browser's developer tools. Once it has, head to the backend via Setup > SuiteCommerce Advanced > SSP Applications > SuiteCommerce Advanced - Dev Vinson, and then check the Execution Log tab. You should see your console.log messages:

From here you get the idea. You can execute whatever code you like, if it needs to be run before or after a particular service does a particular method. This way you don't need to edit or replace the source file.

When you're done, don't forget to undo this change to your site (including removing the entry in distro.json).

Permissions: Extra Information

I have to admit, when I was first learning this stuff I was a little confused about permissions. It wasn't obvious to me what transactions.tranRtnAuth.1 meant, and so Gerardo gave me some information, which I'm going to pass on to you guys.

The first key lies in SspLibraries > SuiteScript > Application.js. Take a look at the getPermissions function, and you'll see two big objects, mapping the permissions used by transactions and lists. This is where the transactions part comes from.

The second part references the specific permission being pulled from the context. These map up to the permissions the user has, or, perhaps more specifically, the role the user has. If you customize the Customer Center role (ie the one used by shoppers on your site) then you'll see the Transactions and Lists subtabs under the Permissions tabs. For tranRtnAuth we can see this is mapped to TRAN_RTNAUTH which, according to the permission names and IDs docs, maps to the Return Authorization field.

The final part, the number (1) refers to the value of the permission in the following manner:

  1. NONE
  2. VIEW
  3. CREATE
  4. EDIT
  5. FULL

Thus, with transactions.tranRtnAuth.1 we are saying, "Check the user can view return authorizations", which is the permission we need whenever a user does anything to do with the returns functionality. When we move on to the POST method, we run an additional permissions check — that is, we check for the ability to submit a new return authorization request.

One last bit of advice from Gerardo is the super-useful &XML=T parameter that you can append to the end of a URL in the NetSuite backend. When viewing a role, add it to the end of the URL and the page will reload to show all of the XML used to build the page. You can then do a search in the page for the name of the permission you're searching for, which'll show the numerical representation of its value.

Backwards Compatibility

If you've migrated your site to Vinson, you'll notice that your modules that use the old-style services still work. This is because we haven't removed the ability to use manually created service files. The thinking behind this whole change to service controllers is that it's smarter and easier way to do it — if you want to continue using the old way then we'll support that, at least for a while.

But that's just in modules that already use manually generated services, so what about using them in modules that also have service controllers? This is also possible and basically requires you to make a modification to your ns.package.json file.

As an experiment, what you can do is put a pre-Vinson service file among the service controllers of a Vinson module. For example, I'm going to put my Mont Blanc version of ReturnAuthorization.Service.ss into the SuiteScript folder of the ReturnAuthorization module. Then I'm going to modify the module's ns.package.json file to include the following:

"services": [
    "SuiteScript/*.Service.ss"
],

So we're telling Gulp to include any .ss files it finds in the SuiteScript folder as services. When I run gulp services, I'll see the following message:

If you remember, we have a part of our ns.package.json file which maps the service controller to the outputted service file. If, however, we specify a service which matches the name of a manually generated service we're including in the module, then that service will not be generated by the dev tools and the existing service will be used instead.

I should point out that I can't think of a scenario where you'd want to do this, other than for some basic testing.

Should I Convert My Old Modules?

You may be thinking this and let me say that at this point we have no strong reason to say that you should, other than to say that the new way of doing is more modern. Unnecessarily rewriting code is time-consuming and your code will still function exactly the same as the new way.

With that said, you may want to take on the task simply as an exercise, to show you understand what I just talked about. If you did the testimonials tutorial, then you could try converting that to a service controller; if that goes well, then consider the others.

Summary

In Vinson we introduced a new way of writing services: service controllers. These extend a series of core files, adding in functionality and reproducibility as if it were along a family tree. This lineage adds in additional files, but has the overall impact of reducing complexity.

With these additional files, a lot of code that you'd normally have to reproduce each time is standardized, making it easier to do things like validating permissions.

And, when it comes to actually creating the files, we leave that work up to the developer tools. The generated files are then pushed up when you run a deploy. Neat.

More Information