Skip to content

Design_basics

Chapter 2: System Design Basics & Principles

Section titled “Chapter 2: System Design Basics & Principles”

Core Principles for Building Scalable Systems

Section titled “Core Principles for Building Scalable Systems”

Building scalable systems requires adherence to fundamental principles that guide architectural decisions.

System Design Principles
=======================
+-------------+ +-------------+ +-------------+
| Simplicity | --> | Modularity | --> | Loose |
| | | | | Coupling |
+-------------+ +-------------+ +-------------+
| | |
v v v
+-------------+ +-------------+ +-------------+
| High | --> | Single | --> | Fail |
| Cohesion | | Responsibility| Safe |
+-------------+ +-------------+ +-------------+

2.2.1 Single Responsibility Principle (SRP)

Section titled “2.2.1 Single Responsibility Principle (SRP)”

Each component should have one reason to change:

Before: God Class
==================
+---------------------------------------+
| UserManager |
+---------------------------------------+
| - authenticate() |
| - sendEmail() |
| - processPayment() |
| - generateReport() |
+---------------------------------------+
|
v
After: Separated Responsibilities
==================================
+------------+ +------------+ +------------+
| Auth | | Email | | Payment |
| Service | | Service | | Service |
+------------+ +------------+ +------------+
| | |
+------------------+----------------+
v
+------------+
| Report |
| Service |
+------------+

Software entities should be open for extension, closed for modification:

Open/Closed Principle
=====================
Before: Modify existing code
+--------------------------+
| PaymentProcessor |
| - processCreditCard() | Adding new payment = editing this
| - processPayPal() |
+--------------------------+
After: Extend without modification
+--------------------------+
| <<interface>> |
| PaymentMethod |
+--------------------------+
^
|
+-------+-------+-------+
| | | |
+----+ +----+ +----+ +----+
|Credit| |Pay | |Bank| |... |
|Card | |Pal | |Transfer|
+----+ +----+ +----+ +----+

Objects of a superclass should be replaceable with subclass objects:

Liskov Substitution
==================
+------------------------+
| <<interface>> |
| Bird |
+------------------------+
| + fly() |
| + eat() |
+------------------------+
^
|
+--------+--------+
| |
+----+ +----+
|Duck | |Penguin|
+----+ +----+
| |
v v
Can fly Cannot fly!
(OK) (Violates LSP)
Fix: Break into interfaces
==========================
+------------+ +------------+
| Flying | | NonFlying |
| Bird | | Bird |
+------------+ +------------+

2.2.4 Interface Segregation Principle (ISP)

Section titled “2.2.4 Interface Segregation Principle (ISP)”

Prefer small, specific interfaces over large ones:

Interface Segregation
=====================
Fat Interface (Bad)
+---------------------------+
| Machine |
+---------------------------+
| + print() |
| + fax() |
| + scan() |
+---------------------------+
|
v
Segregated Interfaces (Good)
+------------+ +------------+ +------------+
| Printer | | Fax | | Scanner |
+------------+ +------------+ +------------+

2.2.5 Dependency Inversion Principle (DIP)

Section titled “2.2.5 Dependency Inversion Principle (DIP)”

Depend on abstractions, not concrete implementations:

Dependency Inversion
=====================
Before: Direct dependency
+--------+ +----------+
| Client | --------> | MySQL |
+--------+ | Database |
+----------+
After: Depend on abstraction
+--------+ +------------+ +----------+
| Client | --------> | Repository | --------> | MySQL |
+--------+ +----| Interface | | Database |
| +------------+ +----------+
| ^
| |
| +----------+
+-------- | PostgreSQL|
+----------+

Simplicity should be a design goal:

Complexity vs Simplicity
=========================
Complex Design Simple Design
+-------------+ +-------------+
| 15 layers | | 3 layers |
| 50+ classes| | 10 classes |
| Complex | | Clear |
| inheritance| | separation |
+-------------+ +-------------+
| |
v v
Hard to maintain Easy to understand
Hard to test Easy to test
Expensive to change Cheap to change

Every piece of knowledge should have a single representation:

DRY Principle
=============
Violation (WET - Write Everything Twice)
=========================================
UserService OrderService PaymentService
+----------+ +----------+ +----------+
| validate | | validate | | validate |
| email | | email | | email |
+----------+ +----------+ +----------+
| | |
+-----------------+-----------------+
|
v
Code is duplicated!
Following DRY
=============
+------------+ +------------+ +------------+
| User | | Order | | Payment |
| Service | | Service | | Service |
+------------+ +------------+ +------------+
| | |
+-------------------+-------------------+
|
v
+------------------+
| Validation |
| Helper |
+------------------+

Don’t build features until they’re necessary:

YAGNI Principle
===============
Before (Over-engineering)
+-------------------------+
| UserService |
| - createUser() |
| - updateUser() | <- Built "just in case"
| - deleteUser() |
| - exportToPDF() | <- Not needed yet!
| - importFromCSV() |
+-------------------------+
After (Build what you need)
+-------------------------+
| UserService |
| - createUser() |
| - getUser() |
+-------------------------+
Add features when actually needed

Different concerns should be in different modules:

Separation of Concerns
======================
+-----------------------------------------------------------+
| Application |
+-----------------------------------------------------------+
| +-----------+ +-----------+ +-----------+ |
| | UI | | Business | | Data | |
| | Layer | | Logic | | Access | |
| +-----------+ +-----------+ +-----------+ |
| | | | |
| v v v |
| +----------------------------------------------+ |
| | Infrastructure | |
| | +----------+ +----------+ +----------+ | |
| | | Web | | Database| | Cache | | |
| | | Server | | | | | | |
| | +----------+ +----------+ +----------+ | |
| +-----------------------------------------------------+|
+-----------------------------------------------------------+

Layered Architecture
===================
+-------------------------+
| Presentation Layer | (UI, Controllers)
+-------------------------+
|
v
+-------------------------+
| Application Layer | (Use Cases, Services)
+-------------------------+
|
v
+-------------------------+
| Domain Layer | (Business Logic, Entities)
+-------------------------+
|
v
+-------------------------+
| Infrastructure Layer | (Database, External APIs)
+-------------------------+
Communication Flow
=================
Request: UI -> Application -> Domain -> Infrastructure
Response: Infrastructure -> Domain -> Application -> UI
Client-Server Model
===================
Client(s) Server
+--------------+ +---------------+
| | Request | |
| Browser/ | ---------> | Application|
| Mobile App | | Server |
| | <--------- | |
+--------------+ Response+---------------+
|
v
Multi-Tier Architecture
======================
+-----------+ +-----------+ +-----------+
| Client | --> | Web/API | --> | Database |
| Tier | | Tier | | Tier |
+-----------+ +-----------+ +-----------+
Event-Driven Architecture
=========================
+--------+ +---------+ +-----------+ +----------+
| Event | | Event | | Event | | Event |
|Producer| --> | Bus | --> | Processor| --> | Consumer |
+--------+ +---------+ +-----------+ +----------+
Example Flow
============
User Signs Up Event Bus Handlers
+-----------+ +---------+ +------------+
| | New | | | |
| User | -----> | Kafka/ | -----> | Send Email |
| Service | User | RabbitMQ| | Create Pro |
+-----------+ +---------+ | file |
+----------+ |
| |
+---------+------------+
v
+--------------------+
| Welcome Email |
+--------------------+

Everything Fails
================
Hardware fails Software fails Network fails
+------------+ +------------+ +------------+
| Disk full | | Memory | | Timeout |
| RAM error | | leak | | Packet loss|
| Power down | | Deadlock | | DNS fail |
+------------+ +------------+ +------------+
Design Principles for Failure
=============================
1. Assume failure will happen
2. Build redundancy at every level
3. Design for graceful degradation
4. Implement proper monitoring
5. Plan for recovery
StrategyDescription
RedundancyMultiple copies of data/services
ReplicationCopy data across nodes
IsolationContain failure to one area
RecoveryAutomatic or manual recovery
MonitoringDetect failures quickly

Before finalizing your design, verify:

  • Have you clarified the scale?
  • Are functional requirements clear?
  • Are non-functional requirements defined?
  • Have you identified constraints?
  • Is the architecture appropriate for the use case?
  • Are components loosely coupled?
  • Is there clear separation of concerns?
  • Have you chosen the right data stores?
  • Can the system scale horizontally?
  • Is there a clear scaling strategy?
  • Are there any single points of failure?
  • Is the database properly scaled?
  • How does the system handle failures?
  • Is there proper error handling?
  • Are there retry mechanisms?
  • Is there proper logging?
  • Is data encrypted at rest and in transit?
  • Is there proper authentication/authorization?
  • Are there rate limiting mechanisms?
  • Is sensitive data properly protected?

Key principles to remember:

  1. Keep it simple - Complexity is the enemy
  2. Single responsibility - Do one thing well
  3. Design for failure - Expect things to break
  4. Separate concerns - Clear boundaries
  5. Program to interfaces - Depend on abstractions
  6. Don’t over-engineer - Build what you need

Next: Chapter 3: Scalability Fundamentals