BIT302 Software Engineering

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:

AbstractionIAuthServiceConcretionAuthServiceImpl
Dependency Inversion: High-level modules depend on abstractions
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 TransactionService handles both payment processing and SMS notifications.
    class TransactionService {
        void processPayment() { ... }
        void sendSMS() { ... } // Violates Single Responsibility!
    }
    
  • Fix: Split into PaymentProcessor and NotificationService, 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.

In the Real World

  1. 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.
  2. 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).
  3. 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: CongestionDetected triggers a recalculation of light timings.

Exam Tip

  1. Definitions: Know the exact wording of principles/patterns (e.g., "Open/Closed Principle: open for extension, closed for modification").
  2. Diagrams: Always draw component/class diagrams for architectural questions. Label arrows with interactions (e.g., "HTTP Request").
  3. Comparisons: For questions like "Compare Layered and MVC", use a table with pros/cons/examples.
  4. 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."

  5. 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 Square class that breaks Rectangle methods) 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)

  1. Define the Single Responsibility Principle with an example from Khalti’s checkout process.
  2. Draw a component diagram for a banking app using Layered Architecture. Label all interactions.
  3. Why might Pathao’s ride-matching system use an event-driven approach instead of Layered?
  4. How does the Repository Pattern help NEPSE avoid SQL injection?
  5. 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…