SOLID PRINCIPLES
Alberto Romero

The best architecture is a plugin architecture

SOLID
- Single Responsibility Principle
- Open Close Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Single Responsibility Principle
A module, class, or a function MUST have only one reason to change.
What is a responsibility?
"A responsability is a family of functions that serves a specific actor in the system" - Robert C. Martin. aka Uncle Bob
Examples of actors
CEO
DBA
Marketing
Finances
Product Designers
//routes.js
const appRouter = {
'GET /currency_conversion/:from/:to/:amount': UtilController.convertMoney
};
//controllers.js
const API_URL = 'www.google.com/conversor';
const UtilController = {
convertMoney(req, res) {
const currencyFrom = req.params.from;
const currencyTo = req.params.to;
const moneyAmount = req.params.amount;
const requestURI = `${API_URL}?a=${moneyAmount}&from=${currencyFrom}&to=${currencyTo}`;
// The controller do the request to the API
request(requestURI, (error, result) => {
if (error) return callback(error);
// And also does the type of conversion!
const conversionResult = moneyAmount * result;
callback(null, conversionResult);
});
}
};SRP Violation?
const getMoneyConversion = require('./money-conversor');
const UtilController = {
convertMoney(req, res) {
const currencyFrom = req.params.from;
const currencyTo = req.params.to;
const moneyAmount = req.params.amount;
getMoneyConversion(moneyAmount, currencyFrom, currencyTo, (error, result) => {
res.send(error, result);
});
};
With a little bit of abstraction and separating responsibilities
what is a controller good for?
//money-conversor.js
const request = require('request');
const API_URL = 'www.google.com/conversor?q=${currencyFrom},${currencyTo},${moneyAmount}';
const getMoneyConversion = function(moneyAmount, currencyFrom, currencyTo, callback) {
// Example: currencyFrom: USD. currencyTo: MXN.
request(API_URL, (error, response) => {
if (error) return callback(error);
let body = JSON.parse(response.body);
const exchangeRate = body.rate;
const conversionResult = moneyAmount * exchangeRate;
callback(null, conversionResult);
});
};"Responsibilities":
Fetch the exchange rate to the cloud
Calculate the conversion
What did happen?
- API Request Isolated from the controller
- Money conversion isolated
//exchange-rate-fetcher.js
const request = require('request');
const UNIT = 1;
const API_URL = 'https://www.google.com/finance/converter'
const buildRequestURI = function(moneyAmount, currencyFrom, currencyTo) {
const requestURI = `${API_URL}?a=${moneyAmount}&from=${currencyFrom}&to=${currencyTo}`;
return requestURI;
};
const exchangeRateFetcher = function(moneyAmount, currencyFrom, currencyTo, callback) {
const requestURI = buildRequestURI(moneyAmount, currencyFrom, currencyTo);
request(requestURI, (error, response) => {
if (error) return callback(error);
let responseBody = JSON.parse(response.body);
const exchangeRate = responseBody.exchangeRate;
callback(null, exchangeRate;
});
};
//money-conversor.js
const exchangeRateFetcher = require('./exchange-rate-fetcher');
const getMoneyConversion(moneyAmount, currencyFrom, currencyTo, callback) {
exchangeRateFetcher(moneyAmount, currencyFrom, currencyTo, (error, exchangeRate) => {
const conversion = moneyAmount * exchangeRate;
callback(null, exchangeRate);
});
};Open Close Principle
Classes, modules or functions should be open for extension, but closed to modification
In other words
The functionality of a module could be extended without modifying the source code of the module.
Open close principle violation
'use strict'
class CraftBeer {
constructor(beer) {
this.beer = beer;
}
execute() {
switch(this.beer) {
case 'modelo especial':
console.log('easy beer crafting right here');
break;
case 'vicky':
console.log('umm some beer crafting right here');
break;
case 'minerva stout':
console.log('Really complex beer crafting right here');
break;
default:
throw new Error('beer is not supported');
}
console.log('The beer was successfully crafted');
}
}
const usecase = new CraftBeer('modelo especial');
usecase.execute();
"When building software, we have to listen to three main actors in the system: Don Objeto, Don Cliente and Don Diseño" - Carlos Ordoñez
Do we have to treat our business objects as strings or as objects?
Text
'use strict';
class CraftBeer {
constructor(beer) {
this.beer = beer;
}
execute() {
this.beer.craft();
}
}
class VictoriaBeer {
craft() {
console.log('easy beer crafting right here');
return 14;
}
}
class MinervaStoutBeer {
craft() {
console.log('easy beer crafting right here');
return 21.50;
}
}
class ModeloEspecialBeer {
craft() {
console.log('easy beer crafting right here');
return 4;
}
}
const usecase = new CraftBeer(new ModeloEspecialBeer());
usecase.execute();
This principle only protects us from change, if we can attempt to predict the future
Two approaches:
1. Try to anticipate everything and create abstractions for every single thing.
2. Don't solve a problem you still don't have.
Liskov Substitution Principle
If a module is using a Base class, then the reference to the Base class can be replaced with a derived class without screwing the functionality of the module.
class Rectangle {
setHeight(height) {
this.height = height;
}
setWidth(width) {
this.width = width;
}
getArea() {
return height * width;
}
}
class Square extends Rectangle {
setHeight(height) {
this.height = height;
this.width = height;
}
setWidth(width) {
this.width = width;
this.height = width;
}
}
Liskov's substitution principle violation
Liskov's Substitution Principle is about contracts and semantics.
A contract is a set of preconditions and postconditions that must be respected into the system
Solution to the shapes problem.
It depends.
Interface Segregation Principle
Clients should not be forced to depend upon interfaces that they don't use.
Interface Segregation Violation
// Service definition
class BotService {
createBot() {
// implementation for create a bot
}
updateBot() {
// implementation for update a bot
}
deleteBot() {
// implementation for delete a bot
}
deployBot() {
// implementation for deploy a bot
}
}
// Clients using it
app.post('/bot, (req, res) => {
const requestBody = buildRequestBody(req);
try {
BotService().createBot();
buildSuccessResponse();
} catch (error) {
buildErrorResponse(res);
}
});Why is it violating the principle?
Dependency Inversion Principle
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.

Abstraction Concept
What is it for?
- Hide complexity or low level details using higher level modules.
- The high level modules doesn't need to know about how the lower level modules do something.
Bad example
//database.js
const SQL = require('sqldriver');
class Database {
findUserById(id, callback) {
const query = `SELECT * FROM USERS WHERE id = ${id}`;
SQL.exec(query, callback);
}
findUserByUsername(username, callback) {
const query = `SELECT * FROM USERS WHERE username = ${username}`;
SQL.exec(query, callback);
}
saveUser(user, callback) {
const fields = Object.keys(user);
const values = fields.map((field) => {
return user[field];
});
const query = `INSERT INTO user (${Object.keys(user)}) values (${values.join(',')}`;
SQL.exec(query, callback);
}
}//database.js
const UserStore = require('./user-store');
const User = require('./entities/user');
class Database {
constructor(args) {
this.userStore = new UserStore();
}
findUserById(id, callback) {
this.userStore.findUserById(id, (error, user) => {
callback(error, new User(user));
});
}
findUserByUsername(id, callback) {
this.userStore.findUserById(id, (error, user) => {
callback(error, new User(user));
});
}
saveUser(user, callback) {
this.userStore.saveUser(user, callback);
}
}
class UserStore {
findUserById(id, callback) {
const query = `SELECT * FROM USERS WHERE id = ${id}`;
SQL.exec(query, callback);
}
...
}
Dependency inversion principle applied :D
Benefits:
1. Database driver swaping.
2. Business logic remains untouched.
Thanks!
References:
www.cleancoders.com
www.oodesign.com
www.lostechies.com
SOLID PRINCIPLES
By Alberto Romero
SOLID PRINCIPLES
A quick overview of the 5 SOLID principles of software development: Single Responsibility Principle, Open Close Principle, Liskov's Substitution Principle, Interface Segregation Principle and Dependency Inversion Principle
- 526