Software EngineeringUnit 413 min read
Software Design Principles & Architectural Patterns
Unit 4 of Software Engineering covers fundamental design principles (SOLID, DRY, KISS) and architectural patterns (Layered, MVC, Repository) with real-world applications in Nepalese apps like eSewa and global systems like Google Cloud, including trade-offs, UML diagrams, and system decomposition techniques.
TAKEAWAYS:
- Design Principles (SOLID, DRY, KISS) ensure maintainable, scalable, and modular code by addressing coupling, cohesion, and reusability.
- Architectural Patterns (Layered, MVC, Repository) solve common design problems like separation of concerns, data access, and presentation logic.
- Trade-offs exist between patterns (e.g., MVC’s complexity vs. Layered’s simplicity) and must be evaluated based on project scope.
- UML Diagrams (Class, Component, Deployment) visually represent architectural decisions and system interactions.
- Real-world ties: eSewa uses Layered Architecture for security (API ↔ Service ↔ Database), while Pathao’s Event-Driven system handles ride requests via Kafka-like queues.
- Exam focus: Define principles/patterns, compare them, and justify choices for given scenarios (e.g., "Design a banking system").
Core Concepts: Software Design Principles
Software design principles are guidelines to create systems that are modular, reusable, and easy to maintain. Violating them leads to "spaghetti code"—tightly coupled, hard-to-extend systems.
1. SOLID Principles (Object-Oriented Design)
SOLID is an acronym for 5 principles that improve code structure and flexibility. Each principle targets a specific flaw:
classDiagram
class SingleResponsibility {
<<Principle>>
"A class should have only one reason to change."
}
class OpenClosed {
<<Principle>>
"Open for extension, closed for modification."
}
class LiskovSubstitution {
<<Principle>>
"Subtypes must be substitutable for their base types."
}
class InterfaceSegregation {
<<Principle>>
"Clients shouldn’t depend on interfaces they don’t use."
}
class DependencyInversion {
<<Principle>>
"Depend on abstractions, not concretions."
}
SingleResponsibility --> "Example: UserManager splits into UserAuth + UserLogger"
OpenClosed --> "Solution: Add new payment gateway via plugin (e.g., Daraz’s ‘pay_with_esewa’)"
LiskovSubstitution --> "Violation: Square cannot extend Rectangle (area() ≠ width*height)"
InterfaceSegregation --> "Bad: IWorker has ‘eat()’ for animals + ‘work()’ for humans"
DependencyInversion --> "Good: UserService depends on IAuthService, not AuthServiceImpl"Worked Example: eSewa’s SOLID Violation
- Problem: eSewa’s
TransactionServicehandles both payment processing and SMS notifications.class TransactionService { void processPayment() { ... } void sendSMS() { ... } // Violates Single Responsibility! } - Fix: Split into
PaymentProcessorandNotificationService, each with a single responsibility.
2. DRY (Don’t Repeat Yourself)
- Definition: Every piece of knowledge must have a single, unambiguous representation in the system.
- Why? Reduces bugs, eases maintenance, and saves development time.
- Example: Instead of copying the same validation logic in multiple forms:
# DRY Violation (copied in UserForm and AdminForm) def validate_email(email): if "@" not in email: raise ValueError("Invalid email") # DRY Solution: Reusable function def validate_email(email): ... UserForm.validate = validate_email AdminForm.validate = validate_email
3. KISS (Keep It Simple, Stupid)
- Definition: Systems should be as simple as possible. Complexity is the enemy of maintainability.
- Trade-off: Simplicity vs. flexibility. Over-engineering (e.g., using MVC for a 10-line script) harms performance.
- Real-world: Khalti’s checkout uses a single-page app (SPA) with minimal layers for speed, unlike Ncell’s legacy multi-page system.
Architectural Patterns: Solving System-Level Problems
Architectural patterns are proven solutions to recurring design challenges. They define how components interact at a high level.
1. Layered (N-Tier) Architecture
Structure: Components are divided into horizontal layers, each with a distinct role.
Layers:
| Layer | Responsibility | Example (eSewa) |
|---|---|---|
| Presentation | UI/UX, user interactions | Mobile app, web portal |
| Business Logic | Core rules, workflows | Payment validation, transaction logic |
| Data Access | Database interactions, CRUD | SQL queries, caching |
Advantages:
- Separation of concerns: Changes in one layer (e.g., switching from MySQL to MongoDB) don’t break others.
- Security: Sensitive logic (e.g., password hashing) stays in the Business Layer.
- Testability: Mock the Data Layer to test Business Logic.
Disadvantages:
- Performance overhead: Cross-layer calls (e.g., UI → DB) add latency.
- Complexity: Managing dependencies between layers (e.g., circular references).
Worked Example: NTC’s Traffic Management System
- Problem: NTC’s old system had monolithic code where traffic light logic was mixed with user reports.
- Solution: Layered Architecture:
- Presentation: Mobile app for reporting accidents.
- Business Logic: Rules to prioritize emergency routes.
- Data Access: Stores real-time traffic data in a NoSQL DB.
- Result: Faster updates and easier maintenance.
2. Model-View-Controller (MVC)
Structure: Separates data (Model), UI (View), and input handling (Controller).
Components:
| Component | Role | Example (Pathao) |
|---|---|---|
| Model | Data + business logic | Rider location, fare calculation |
| View | UI rendering | Map display, ride request screen |
| Controller | Handles user input → updates Model/View | "Book Ride" button triggers fare calc |
When to Use:
- Dynamic web apps (e.g., Daraz’s product pages).
- Single-page apps (SPAs) with frameworks like React (where "View" is a component).
Trade-offs:
| Pattern | Pros | Cons | Best For |
|---|---|---|---|
| Layered | Clear separation, secure | Slower cross-layer calls | Enterprise apps (banks, eSewa) |
| MVC | Great for UI-driven apps | Complex for non-CRUD systems | Web/mobile apps (Pathao, Daraz) |
3. Repository Pattern
Problem: Direct database access in business logic leads to tight coupling (e.g., UserService knows SQL queries).
Solution: Introduce a Repository as an abstraction layer.
Example: NEPSE’s Stock Data System
- Old Code (Tight Coupling):
class StockService { void getPrice(String symbol) { String sql = "SELECT price FROM stocks WHERE symbol='" + symbol + "'"; // SQL injection risk! } } - Repository Pattern:
interface StockRepository { String getPrice(String symbol); } class StockService { private final StockRepository repo; StockService(StockRepository repo) { this.repo = repo; } String getPrice(String symbol) { return repo.getPrice(symbol); } }- Benefits:
- Swap databases (e.g., from Oracle to PostgreSQL) without changing
StockService. - Add caching (e.g., Redis) transparently.
- Swap databases (e.g., from Oracle to PostgreSQL) without changing
- Benefits:
In the Real World
eSewa’s Layered Security
- Pattern: Layered Architecture
- How: User requests → API Gateway (Presentation) → Auth Service (Business) → Database (Data).
- Why: Isolates sensitive payment data from the UI. If the mobile app is hacked, attackers can’t access the database directly.
Pathao’s Event-Driven Ride Matching
- Pattern: Event-Driven + Repository
- How: When a rider requests a ride, an event (
RideRequested) is published to a queue (e.g., Kafka). Drivers subscribe to this event via a Repository that fetches nearby drivers. - Why: Decouples rider/driver apps; scales horizontally (add more driver nodes without rewriting logic).
Ncell’s Billing System (MVC)
- Pattern: MVC
- How:
- Model: Stores user plans, usage data.
- View: Shows bills via USSD or app.
- Controller: Handles "Generate Bill" requests.
- Challenge: Ncell’s legacy system mixes Model/View (e.g., SQL queries in the UI layer), making updates difficult.
UML Diagrams for Architectural Design
UML diagrams visualize architectural decisions. Two key diagrams for this unit:
1. Component Diagram (Layered Architecture)
Shows how components interact across layers.
2. Class Diagram (Repository Pattern)
Models classes and their relationships.
classDiagram
class StockRepository {
+getPrice(symbol: String) String
}
class StockService {
-repo: StockRepository
+getPrice(symbol: String) String
}
StockService --> StockRepository : "depends on"Design Trade-offs: When to Choose What?
| Scenario | Recommended Pattern | Why Not Others? |
|---|---|---|
| Banking system (security critical) | Layered + Repository | MVC’s Controller can’t enforce strict access rules. |
| Mobile app with real-time updates | MVC + Event-Driven | Layered adds latency; events handle dynamic data (e.g., Pathao’s live maps). |
| Monolithic legacy system | Refactor to Layered | MVC adds complexity; Repository helps decouple DB logic. |
Worked Example: Kathmandu Traffic Routes
- Problem: Traffic lights at Thapathali and Lakipat are controlled by a single script with hardcoded timings.
- Solution: Layered + Event-Driven
- Presentation: Sensors detect congestion.
- Business Logic: Rules to prioritize emergency vehicles.
- Data Access: Stores historical traffic patterns.
- Events:
CongestionDetectedtriggers a recalculation of light timings.
Exam Tip
- Definitions: Know the exact wording of principles/patterns (e.g., "Open/Closed Principle: open for extension, closed for modification").
- Diagrams: Always draw component/class diagrams for architectural questions. Label arrows with interactions (e.g., "HTTP Request").
- Comparisons: For questions like "Compare Layered and MVC", use a table with pros/cons/examples.
- Real-world ties: Relate to Nepali apps (eSewa, Pathao) or global systems (Google’s microservices). Example:
"Explain how Daraz could use the Repository Pattern to switch from MySQL to MongoDB without changing its product search logic."
- Trade-offs: Questions often ask "Which pattern would you choose for X system?". Answer with:
- Pattern name.
- Why it fits (e.g., "Layered for security").
- Alternative and its drawback (e.g., "MVC would work but adds complexity").
Common Pitfalls
- Overusing MVC: Not all systems need a View (e.g., a backend service). Use Layered instead.
- Ignoring SOLID: Violating Liskov Substitution (e.g., a
Squareclass that breaksRectanglemethods) causes runtime errors. - Tight Coupling: Direct database calls in business logic (anti-pattern). Always use Repository or Data Access Layer.
- Diagram Errors: Forgetting to label arrows or omitting components (e.g., missing the "Controller" in MVC diagrams).
Practice Questions (Self-Check)
- Define the Single Responsibility Principle with an example from Khalti’s checkout process.
- Draw a component diagram for a banking app using Layered Architecture. Label all interactions.
- Why might Pathao’s ride-matching system use an event-driven approach instead of Layered?
- How does the Repository Pattern help NEPSE avoid SQL injection?
- Compare MVC and Layered patterns for a traffic management system like NTC’s.
Key Formulas/Checklists
| Principle/Pattern | Checklist for Compliance |
|---|---|
| SOLID | 1. One class = one responsibility. |
| 2. Extend via interfaces, not code changes. | |
| DRY | 1. No duplicate code. |
| 2. Shared logic in reusable functions/classes. | |
| Layered | 1. Clear separation: UI, Business, Data. |
| 2. No circular dependencies between layers. | |
| MVC | 1. Model = data + logic. |
| 2. View = UI only. | |
| 3. Controller = input handler. |
Further Reading
- Books:
- Clean Architecture by Robert C. Martin (for SOLID and layered design).
- Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four).
- Nepali Context:
- Study how eSewa’s API uses Layered Architecture (public docs available).
- Analyze Pathao’s tech blog for event-driven examples.
- Tools:
- Draw.io (for UML diagrams).
- Postman (to test layered API interactions).
Based on the TU BIT syllabus for Software Engineering (BIT302), unit 4.
Discussion
Loading…