loading...

Single responsibility methods in javascript

manuraj17 profile image Manu ・3 min read

When working with a single file module which exports many methods, it becomes a pretty complex issue to maintain it in the long run as well as add tests. You can never know how the file is gonna grow, how many methods are going to get added eventually and how are we going to add tests for each of this with it's own stubs and mocks.

Example for the type of situation I am talking about

// app/services/order-service.js

const orderService = {};

orderService.serviceMethodA = () => {};
orderService.serviceMethodB = () => {};
orderService.serviceMethodC = () => {};
orderService.serviceMethodD = () => {};
...
...

module.exports = orderService;
Enter fullscreen mode Exit fullscreen mode

I have actually stumbled upon such handles critical code and grew up to thousands of number of lines.

Now, even for a method of ten lines, you may end up writing three test cases considering the edge cases involved. Now, imagine a file with tens of such methods. It can also happen that they may come with their own dependencies and external calls. So even if we want to write a test for serviceMethodC, we will have to end up making sure the stubs and mocks are all in place for every other one so that the whole test doesn't fail.

To counter this, a pattern that I recently started following was something of a sort of "Single Responsibilty Methods". Like the name suggests I started breaking down this file into multiple files, one for each method. All of them then imported into a single entry point file.

Folder structure before change

app
└── services
   └── order-service.js
Enter fullscreen mode Exit fullscreen mode

Now, after splitting up the methods into seperate files and adding an entry point file, the structure would look like below

app
└── services
   └── order-service
      ├── index.js
      ├── service-method-a.js
      ├── service-method-b.js
      ├── service-method-c.js
      └── service-method-d.js
Enter fullscreen mode Exit fullscreen mode

The index.js file looks like below

const orderService = {};

orderService.serviceMethodA = require('./service-method-a.js');
orderService.serviceMethodB = require('./service-method-b.js');
orderService.serviceMethodC = require('./service-method-c.js');
orderService.serviceMethodD = require('./service-method-d.js');

module.exports = orderService;
Enter fullscreen mode Exit fullscreen mode

Now, to add tests, we can go ahead and add tests individually for each method, abstracted for it's own and not worried about the dependencies of other methods

tests
└── services
   └── order-service
      ├── index.js
      ├── service-method-a-test.js
      ├── service-method-b-test.js
      ├── service-method-c-test.js
      └── service-method-d-test.js
Enter fullscreen mode Exit fullscreen mode

Whole thing is much more clean, no more single large files and all mixed up dependencies etc. Also, collaboration is easier as if tasks are split each concerned person can work on their tests individually, no more merge conflicts!

NOTE:
One thing that I have personally noticed was that, when we write any service object, a repository object or a controller object, we append the object type to the end of the file name.
Eg: order-service, order-repository, orders-controller
Advantage being it clearly denotes what the file is and also aids in fuzzy searching it.
With the current approach, we are breaking down such a file. It reduces the search-ability and it also could happen that there can be two similarly named methods under different contexts. Such breaking down also could prevent editors to navigate via "intellisense" to the method definition.
Although, these are not big issues and can also be solved if we just be extra careful and look at the file path. In my case I could trade if for the advantages that I received.

Discussion

pic
Editor guide