SINGLE RESPONSIBILITY PRINCIPLE (SRP)
A class should have one, and exclusively one, reason to change.
In other words, we shouldn't have a single class doing everything: receiving data, validating requests, applying business rules, and accessing the database...
This class that “does it all” is the famous God Class, and it is an anti-pattern.
IS IT REALLY THAT IMPORTANT?
An analogy that explains its importance well is the comparison between a Swiss Army Knife vs. a Scalpel.
I know it might sound weird at first, but let's break it down. A pocket knife can be quite useful, carrying a bunch of functionalities at once, being practical in everyday life.
But what if one tool in it breaks?
POINT FOR SPECIALIZATION
You will have to buy another one, even if all the other tools are working well. A waste, right? This applied to software is even worse.
On the other hand, the scalpel, besides having fewer chances of breaking, is a specialist in its own function. You will only replace the scalpel if it actually has a problem.
CONNECTION TO ARCHITECTURE
In practice, SRP is the foundation for organizing the structure of your project.
A class that only serves a single responsibility helps in understanding architectures like MVC, Hexagonal, Clean Arch...
This principle determines that it makes no sense to have a class that defines attributes, validates fields, saves data to the database... In this scenario, the class would assume multiple responsibilities, which violates the precepts of any of these architectures.
A BAD EXAMPLE
This class below fulfills more than one responsibility. A class that deals with persistence, validation, business rules... violates the SRP.
Code Example (The Anti-pattern):
class UserService {
constructor(
private name: string,
private email: string,
) {}
// Responsibility 1: Data Validation
public validateEmail(): boolean {
return this.email.includes("@");
}
// Responsibility 2: Data Persistence (Database Access)
public saveToDatabase(): void {
console.log(`Saving user ${this.name} to MySQL database...`);
}
// Responsibility 3: Sending Notifications
public sendWelcomeEmail(): void {
console.log(`Sending welcome email to ${this.email}...`);
}
// Responsibility 4: Report Generation (Presentation/Visualization)
public generateUserReportPDF(): void {
console.log(`Generating PDF report for ${this.name}...`);
}
// Responsibility 5: Infrastructure (Error Logging)
public logSystemError(error: string): void {
console.log(`[LOG] ${new Date().toISOString()}: ${error}`);
}
}
AND WHAT WOULD BE A GOOD EXAMPLE?
It would be extracting all these responsibilities (Persistence, Business Rule, Validation, Boundary...) from a single class.
And exactly because of this, SRP communicates so well with architectures and their layers. Using the famous "Clean Arch" as an example, each of its layers will assume a well-defined responsibility.
Code Example (Applying SRP):
// ✅ SOLID: Each class has only one reason to change
class UserValidator {
public isValidEmail(email: string): boolean {
return email.includes("@");
}
}
class UserRepository {
public save(user: User): void {
console.log(`Saving user ${user.name} to database...`);
}
}
class EmailService {
public sendWelcomeEmail(email: string): void {
console.log(`Sending welcome email to ${email}...`);
}
}
class ReportGenerator {
public generateUserPDF(user: User): void {
console.log(`Generating PDF report for ${user.name}...`);
}
}
class Logger {
public logError(error: string): void {
console.log(`[LOG] ${new Date().toISOString()}: ${error}`);
}
}
SRP + CLEAN ARCH
Each layer fulfills only its responsibility and passes it on to the next layer, following the flow. The detail is that, depending on the size of the project, the team, and the needs, the layers can increase or decrease.
- ROUTE: Receives the HTTP request and directs the traffic.
- CONTROLLER: Validates the input data and formats the final response.
- SERVICE: Orchestrates the application rules and entities.
- REPOSITORY: Isolates the database and executes the persistence.
OTHER CASES
The previous example focused on Clean Arch, but other architectural approaches, like MVC and Hexagonal, also apply SRP to their core.
SOLID acts as a foundation for these patterns, not as a competitor.
In practice, the division of responsibilities ends up being implicit in the very definition of the layers of any good architecture:
⬇️ user.route.ts
⬇️ user.controller.ts
⬇️ user.service.ts
⬇️ user.repository.ts
My Links
Github: victor-lis-bronzo
Linkedin: victor-lis-bronzo
Portfolio: portfolio.victorlisbronzo.me
Coolest Portfolio: victorlisbronzo.me
Leave your reaction ❤️
Had you already realized the importance of SRP?
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.