35 min read Core CS & Systems Part 1 · Lesson 4 (4 / 15)
Part 1 — Mindset & SDLC

Lesson 10.4 — Designing Software Before Coding

Architecture, Components, Modules, Interfaces, and Separation of Concerns

What We Will Master in This Lesson

By the end of this lesson, you will clearly understand with deep engineering intuition:

  • What Software Design is and why it functions like a 20-story building blueprint
  • The sharp distinction between Architecture and Implementation
  • Defining clear boundaries for Components and Modules
  • Separation of Concerns (SoC) as the primary defense against chaos
  • The golden architectural rule: High Cohesion and Low Coupling
  • Interfaces and Abstraction: Creating change-resilient boundaries
  • Layered Architecture, Modular Monolith vs. Microservices, and pluggable adapters

1 Critical Questions You Must Answer Before Writing Code

In the previous lesson, we resolved a fundamental question: "What must our software achieve?"

Now suppose we are setting out to construct a complete e-commerce platform. Our requirements specify:

  • • User accounts and customer authentication
  • • Product catalog browsing and full-text search
  • • Persistent shopping cart functionality
  • • Order placement workflows
  • • Online payment processing
  • • Administrative dashboards for inventory and order management

If we immediately begin coding, an existential set of structural dilemmas will paralyze development within weeks:

Where does each piece of source code belong?
Which component owns which specific business responsibility?
Who communicates directly with the database?
Where does the payment processing logic live?
When a business rule changes, which other modules will break?

To answer these questions authoritatively, we establish a structured Software Design before writing code.

2 What is Software Design?

Software Design is the engineering process of defining a system's structure, components, interfaces, responsibilities, and data communication paths.

"Requirements specify WHAT we must build.
Design defines HOW we structure and organize it."

Consider civil engineering: If you intend to erect a 20-story building, you never start by stacking bricks on day one.

Building Construction Pre-Planning: Where are the structural pillars? Where do stairs and elevators go? How will electrical wiring and plumbing route through floors? Which load-bearing beams support the roof?
Software System Pre-Planning: Which component holds domain business rules? How are database transactions isolated? How does the order service degrade gracefully when third-party payment APIs fail?

3 What is Architecture vs. Code Implementation?

Software Architecture is the highest-level structural blueprint of the system. At this altitude, we reason about:

  • • The core subsystems and domain boundaries
  • • The explicit responsibilities assigned to each component
  • • Network protocols and communication contracts between subsystems
  • • Data flow vectors and transaction boundaries
  • • Integration touchpoints with external services
🏛️ Architecture = The Big Picture

High-level structural decisions that are exceptionally expensive to undo later.

"Our application will consist of a Next.js Frontend, a Go REST API Backend, and a PostgreSQL Database."
🔨 Implementation / Code = Detailed Construction

Concrete internal syntax and logic that can be refactored daily.

"`createOrder()` accepts these parameters, validates tax amounts, and inserts records into the `orders` table."

A High-Level System Architecture View

USER (Web Browser / Mobile App)
↓ (HTTPS / REST API Calls)
APPLICATION BACKEND (Business Logic & Routing)
↙ ↓ ↘
Database
PostgreSQL
Payment Service
Stripe / SSLCommerz
Notification Service
Email / SMS Queue

4 Component vs. Module: Establishing Boundaries

The macro logical partitions within a system architecture are known as Components. A component is organized around a cohesive business or technical domain.

📦 Order Component

Its scope is strictly bounded to order lifecycle:

  • • Validating and creating order records
  • • Managing order state machine (Pending → Paid → Shipped)
  • • Handling order cancellation and order history retrieval
💳 Payment Component

Its scope is strictly bounded to monetary transactions:

  • • Initiating charges with external gateways
  • • Validating webhook signatures and callbacks
  • • Managing retries and refund transactions

A Module is a more granular logical packaging unit within code. A major component typically contains several focused modules:

Order Creation Module Order Pricing & Tax Module Order Validation Module Order History Module

5 Separation of Concerns (SoC)

The most foundational canon in all software design is Separation of Concerns.

"Never entangle distinct responsibilities and concerns into a single monolithic unit."

Consider an anti-pattern function authored without separation of concerns:

❌ function checkout() {
1. Parses raw HTTP request form payload
2. Executes direct SQL `SELECT * FROM products`
3. Calculates discounts and taxes
4. Invokes Stripe SDK to charge credit card
5. Opens raw socket to dispatch SMTP confirmation email
6. Concatenates HTML strings to render response
}

Maintaining this mega-function is a nightmare. Changing the email template risks breaking payment charging! Professional engineering decouples these concerns into distinct architectural tiers:

Presentation (UI) Input intake & view rendering
Business Logic Domain rules & policies
Data Access Database persistence
External Integration Payment & email adapters

6 High Cohesion and Low Coupling

The structural health of any software design is judged by two universal metrics:

Cohesion Goal: High

How closely related the internal responsibilities of a module are to one another.

High Cohesion (Good): Payment Module contains strictly charge, refund, and invoice logic.
Low Cohesion (Bad): Payment Module contains image resizing, password hashing, and PDF parsing.
Coupling Goal: Low

The degree of dependency and internal knowledge between distinct components.

Low Coupling (Good): Order Service interacts solely via an interface (`pay(amount)`).
High Coupling (Bad): Order Service directly reads and mutates private internal state of the Payment Service.

The Universal Law of Clean Design:
High Cohesion + Low Coupling = Maintainable, Evolvable Software

7 Interfaces & Abstraction: Shields Against Change

An Interface is an explicit contract specifying what methods a component exposes without detailing how they are executed.

Abstraction is the discipline of exposing only necessary capabilities while concealing intricate implementation complexity.

🚗
The Automobile Accelerator Analogy:

When you depress the accelerator pedal, you are engaging a simple interface. You do not need to calculate piston stroke timings or electronic fuel-injection ratios; that operational complexity is completely abstracted. Similarly, calling `sendEmail()` shields engineers from raw low-level SMTP handshakes.

Case Study: Pluggable Payment Gateways

Suppose our platform originally launched using Stripe. Later, leadership directs engineering to support bKash and PayPal. If the Order component was tightly coupled to Stripe's specific classes, adding bKash would require rewriting core order checkout logic.

By placing a clean interface between them, the Order domain remains completely shielded:

Order Component
↓ (depends only on contract)
interface PaymentGateway { process(amount): Result }
↙ ↘
StripeAdapter (implements)
BkashAdapter (implements)

The Order component has zero awareness whether Stripe or bKash is processing the charge. It relies exclusively on the contract, allowing new providers to be swapped in painlessly in hours.

8 🎮 Interactive Simulator: Pluggable Adapter Pattern

Switch payment providers below to see how interface abstraction keeps the core Order Service 100% untouched:

Select Payment Gateway:

9 Layered Architecture & Modular Monoliths

The most resilient, industry-standard pattern for web backend development is Layered Architecture:

Presentation Layer HTTP Requests, Controllers & JSON Serialization
↓
Application / Service Layer Use Cases, Workflows & Transaction Boundaries
↓
Domain / Business Layer Core Entities, Business Rules & Validations
↓
Data Access Layer (Repository) SQL Queries, ORM & Database Connections

Monolith vs. Microservices

Prematurely adopting microservices on small teams creates distributed system nightmares: network serialization latency, partial failure recovery, distributed tracing, and coordination overhead.

💡 Modern Industry Consensus: Begin with a Modular Monolith—a single deployable unit where components maintain strict internal boundaries. If a specific component requires independent scaling later, extracting it into a microservice is straightforward and safe.

10 💡 Concept Check (Interactive Quiz)

Test your comprehension of software architecture and modular design:

1. What is the overarching objective of clean software architecture?

2. What is the fundamental mandate of Separation of Concerns (SoC)?

3. What is the decisive advantage of relying on an Interface when integrating third-party gateways?

11 🧠 Key Takeaways

  • Requirements = WHAT, Design = HOW, Code = Implementation: Establish architectural boundaries before committing lines of code.
  • High Cohesion + Low Coupling: Group tightly related responsibilities together and decouple components using clean interfaces.
  • Separation of Concerns: Never pollute domain business rules with raw database queries or direct third-party SDK calls.
  • Design is not an inflexible prison: The hallmark of great architecture is its capacity to evolve with real-world business needs.
Next Lesson Preview

Lesson 10.5 — Code That Can Survive Change: Readability, Coupling & Cohesion

With system architecture defined, how do you author internal code so that future engineers can comprehend, refactor, and evolve it without fear of regression?

Start Lesson 10.5