Single responsibility principle on complex process

Viewed 144

I always had a question on how to assure the single responsibility principle when the process I have to assure is quite complex.

I work with a 3 layers architecture Backend: Controller (my API endpoints) | Service (single responsibility functions) | Data (access to the DB)

Let's say I have a process ProcessA that is composed by 4 tasks TasksA1, TasksA2, TasksA3, TasksA4.

If I have an endpoint exposed on my controller layer such as: POSTMethodProcessA

How should be composed my code in order to respect the single responsibility principle on my service layer?

The options I see:

Option 1 (the controller must know the process):

class MyController {
  exports.processA = functions.https.onRequest(req, res) => {
    myservice.doTaskA1(); // single responsability on task1
    myservice.doTaskA2(); // single responsability on task1
    myservice.doTaskA3(); // single responsability on task1
    myservice.doTaskA4(); // single responsability on task1
  });
}

Option 2 (the service know the process and loose the Single responsibility)

class MyController {
  exports.processA = functions.https.onRequest(req, res) => {
    myservice.doProcessA();
  });
}

//inside the service (the doProcessA method must be in charge of multiples tasks and loose the single responsability principle :
class MyService {
  function doProcessA() {
    this.doTasksA1();
    this.doTasksA2();
    this.doTasksA3();
    this.doTasksA4();
  }
}

This question is even more complicated to me if the tasks are composed themselves by multiple jobs: FirstJobA1, SecondJobA1, ThirdJobA1 ...

How those complexities layer should be handled on the code structure to respect the single responsibility principle is something that always blocked me.

1 Answers

A common misconception is that Single Responsibility Principle means that a class (or service or a system, etc.) should do only one thing. Instead, SRP means that the subject must have a single reason to change.

Thus it's fine to have separate "service" implementations for each task, and then an "aggregate" service that orchestrates the whole process:

service TaskA;
service TaskB;
service TaskN;

service Process{
    TaskA::run();
    TaskB::run();
    ...
    TaskN::run();
}

A change in a task should not affect unrelated tasks. Also, a change in the process should not affect the subtasks. This is related to the Common Closure Principle (CCP) which states:

The classes in a component should be closed together against the same kind of changes. A change that affects a component affects all the classes in that component and no other components.

or more informally:

gather into components those classes that change for the same reasons and at the same times

This means that if a change in TaskA will inevitably lead to a change in TaskB, then maybe those two should be a single task, instead of two.

Apply that recursively to your sub-tasks: FirstJobA1...FirstJobA-N

Related