Skip to main content

Command Palette

Search for a command to run...

Low Level Design: গভীর থেকে বোঝা এবং আয়ত্ত করা

Published
20 min read
Low Level Design: গভীর থেকে বোঝা এবং আয়ত্ত করা
I

A highly motivated and experienced full-stack developer with a proven track record of developing and deploying web applications. Skilled in a range of programming languages and frameworks, as well as database technologies. Comfortable working in a fast-paced environment and able to adapt to new technologies quickly. A team player who is also able to work independently when required.

ভূমিকা: কেন এই Article?

তুমি হয়তো programming শিখেছ। Variable, loop, function, data structure - সব জানো। কিন্তু যখন একটা বড় system বানাতে বসো, তখন মনে হয় কোথা থেকে শুরু করব? কীভাবে organize করব? Code লিখতে লিখতে হারিয়ে যাও একটা maze-এ।

এই feeling টা স্বাভাবিক। কারণ programming শেখা আর software design করা - এই দুটো আলাদা skill। Programming হলো tools জানা - hammer, screwdriver ইউজ করতে পারা। কিন্তু design হলো জানা কোথায় কোন tool ইউজ করতে হবে, কীভাবে একটা সুন্দর structure তৈরি করতে হবে।

আজকের এই article-এ আমরা গভীরভাবে জানব Low Level Design কী, কেন দরকার, এবং কীভাবে এটা তোমার software development skill-কে নিয়ে যাবে next level-এ।


Part 1: Low Level Design-এর Philosophy বোঝা

Design মানে আসলে কী?

চলো একটা উদাহরণ দিয়ে শুরু করি যেটা আমরা সবাই জানি - রান্না

তুমি যখন বিরিয়ানি রান্না করো, তখন কি randomly ingredients মিশাতে থাকো? না। তোমার একটা process আছে:

১. Planning পর্ব:

  • কত জনের জন্য রান্না করব?

  • কী কী উপাদান দরকার?

  • কোনটা আগে ready করব?

  • কতক্ষণ লাগবে?

২. Preparation পর্ব:

  • চাল ভিজিয়ে রাখো

  • মাংস মেরিনেট করো

  • পেঁয়াজ কাটো

  • মশলা ready রাখো

৩. Execution পর্ব:

  • একটা নির্দিষ্ট order-এ রান্না করো

  • প্রথমে কী, তারপর কী

  • কোন step-এ কতক্ষণ সময় দেবে

৪. Quality Check:

  • স্বাদ ঠিক আছে কিনা

  • Presentation কেমন

  • সময়মত ready হলো কিনা

Software design ঠিক এরকম। তুমি code লেখার আগে planning করবে, structure ঠিক করবে, তারপর implementation করবে।

কিন্তু Planning ছাড়া কী হয়?

ধরো তুমি planning ছাড়াই বিরিয়ানি রান্না শুরু করলে:

  • মাংস জ্বাল দিতে দিতে মনে পড়লো পেঁয়াজ কাটা নেই

  • পেঁয়াজ কাটতে কাটতে মাংস পুড়ে গেল

  • চাল ভেজানো নেই, তাড়াহুড়ো করে সেটা করতে গেলে

  • শেষে দেখা গেল কিছু মশলা কিনতে ভুলে গেছো

Result? একটা mess। সময় বেশি লাগলো, food quality খারাপ হলো, তুমিও frustrated।

Software-ও ঠিক এরকম। Design ছাড়া code করা মানে হলো:

  • একটা feature বানাতে গিয়ে অন্যটা ভেঙে যাওয়া

  • কোড লিখতে লিখতে realize করা যে approach টা ভুল

  • শেষে এমন একটা codebase যেটা maintain করা nightmare


Part 2: একটা Real Scenario গভীরভাবে বুঝি

চলো একটা practical example নিয়ে step by step বুঝি। আমরা বানাব একটা অনলাইন খাবার অর্ডার system

প্রথম দৃষ্টিতে Requirements

তোমার boss বললো: "আমাদের একটা food delivery app দরকার। Users restaurant থেকে খাবার order করবে।"

এই একটা লাইনের requirement শুনেই কি তুমি code লিখতে বসে যাবে? না। প্রথমে বুঝতে হবে আসলে কী কী লাগবে।

Step 1: Requirements খুলে বোঝা

তুমি বসে চিন্তা করবে (বা boss-কে জিজ্ঞেস করবে):

Users কারা?

  • Customer (যারা order করবে)

  • Restaurant owner (যারা খাবার বানাবে)

  • Delivery person (যারা deliver করবে)

  • Admin (যারা পুরো system manage করবে)

প্রতিটা user-এর আলাদা আলাদা কাজ। একজন customer যা করতে পারবে, একজন delivery person তা পারবে না।

Customer কী কী করবে?

  • নতুন account খুলবে

  • Login করবে

  • Restaurant browse করবে

  • Menu দেখবে

  • খাবার select করবে cart-এ

  • Address দেবে

  • Payment করবে

  • Order track করবে

  • Feedback দেবে

Restaurant owner কী করবে?

  • তাদের restaurant register করবে

  • Menu upload করবে

  • Order receive করবে

  • Order accept/reject করবে

  • খাবার ready হলে notify করবে

Delivery person কী করবে?

  • Available orders দেখবে

  • Order pick করবে

  • Restaurant থেকে খাবার নেবে

  • Customer-কে deliver করবে

  • Status update করবে

দেখো, এত কিছু! একটা simple "food delivery app" বলতে আসলে কত জটিল একটা system!

Step 2: Main Components চিহ্নিত করা

এবার চিন্তা করো - এই পুরো system-কে কীভাবে ভাগ করবে?

Component 1: User Management এটা handle করবে সব user-related কাজ:

  • নতুন user registration

  • Login/Logout

  • Password reset

  • Profile update

  • User types manage করা (customer, restaurant, delivery)

কেন এটা আলাদা component? কারণ user management একটা complete responsibility। এর কাজ শুধু users নিয়ে।

Component 2: Restaurant Management এটা handle করবে restaurant-related কাজ:

  • নতুন restaurant add করা

  • Restaurant info update করা

  • Menu management

  • Restaurant search

  • Restaurant ratings

কেন আলাদা? কারণ restaurant-এর logic completely আলাদা user management থেকে।

Component 3: Order Management এটা সবচেয়ে complex component:

  • Order creation

  • Order status tracking

  • Order history

  • Order cancellation

Component 4: Payment Management

  • Different payment methods handle করা

  • Payment verification

  • Refund processing

Component 5: Delivery Management

  • Delivery person assignment

  • Route optimization

  • Real-time tracking

Component 6: Notification Management

  • Email notifications

  • SMS notifications

  • Push notifications

  • In-app notifications

এই Components আলাদা করার Logic কী?

এই প্রশ্নটা খুব important। কেন আমরা এভাবে ভাগ করলাম?

কারণ ১: Single Responsibility

প্রতিটা component-এর একটা clear, focused job আছে। User Management শুধু users নিয়ে ভাবে। তার কাজ না restaurant manage করা, না payment process করা।

Analogy: একটা hospital-এ যেমন আলাদা আলাদা departments আছে - Cardiology, Neurology, Pediatrics। প্রতিটা department নিজের field-এ expert। Cardiologist cancer treatment করবে না।

কারণ ২: Independent Development

এভাবে আলাদা করলে তোমার team-এর different developers parallel-এ কাজ করতে পারবে। একজন User Management নিয়ে কাজ করছে, অন্যজন Payment নিয়ে। তারা একে অপরকে block করছে না।

কারণ ৩: Easy Testing

যখন সব আলাদা, তখন individual components test করা সহজ। Payment module test করতে গেলে পুরো User Management run করার দরকার নেই।

কারণ ৪: Scalability

ধরো তোমার app-এ খুব বেশি orders আসছে, কিন্তু user registration কম হচ্ছে। তাহলে তুমি শুধু Order Management component-কে বেশি resources দিতে পারবে। User Management-এ কম resources থাকলেও চলবে।

কারণ ৫: Maintainability

যদি Payment gateway change করতে হয়, তুমি শুধু Payment Management component-এ যাবে। Order Management, User Management - এসব touch করার দরকার নেই।


Part 3: একটা Component-এর ভিতরে কী থাকে?

এবার চলো একটা component-এর ভিতরে ঢুকি। ধরো আমরা Order Management component design করছি।

Order Management-এর Responsibilities

প্রথমে clearly define করতে হবে এটা কী কী করবে:

১. Order Creation:

  • Customer যখন "Place Order" বাটনে click করবে

  • Cart থেকে items নিয়ে একটা order create করতে হবে

  • Total price calculate করতে হবে

  • Delivery charges add করতে হবে

  • Discounts apply করতে হবে

২. Order Validation:

  • Restaurant open আছে কিনা?

  • Items available আছে কিনা?

  • Delivery address valid কিনা?

  • Payment method valid কিনা?

৩. Order Processing:

  • Restaurant-কে notify করা

  • Payment process করা

  • Delivery person assign করা

  • Customer-কে confirmation পাঠানো

৪. Order Tracking:

  • Current status কী?

  • কোন stage-এ আছে?

  • Estimated delivery time?

৫. Order Completion:

  • Delivered হলে status update

  • Payment finalize

  • Feedback request

এবার Design করি: কী কী Parts লাগবে?

একটা component-কে আমরা সাধারণত তিনটা layer-এ ভাগ করি:

Layer 1: Presentation/Interface Layer এটা বাইরের দুনিয়ার সাথে interact করে। যেমন:

  • HTTP requests receive করা

  • Response পাঠানো

  • Input validation করা

Layer 2: Business Logic Layer এখানে actual business rules থাকে। যেমন:

  • Order কীভাবে create হবে

  • Price কীভাবে calculate হবে

  • Discount কীভাবে apply হবে

  • Order cancellation-এর rules কী

Layer 3: Data Access Layer এটা database-এর সাথে কথা বলে:

  • Order save করা

  • Order fetch করা

  • Order update করা

এই তিনটা layer আলাদা রাখার কারণ কী? চলো বুঝি।

Three-Layer Architecture কেন?

কারণ ১: Flexibility

ধরো আজকে তুমি REST API দিয়ে requests receive করছ। কাল যদি GraphQL-এ switch করতে চাও, তাহলে শুধু Presentation Layer change করলেই হবে। Business Logic আর Data Access Layer একই থাকবে।

অথবা ধরো database PostgreSQL থেকে MongoDB-তে নিয়ে যেতে চাও। শুধু Data Access Layer change করো। Business Logic intact থাকবে।

কারণ ২: Testing

Business Logic test করার সময় database বা HTTP server লাগবে না। তুমি fake/mock data দিয়ে pure logic test করতে পারবে।

কারণ ৩: Clear Boundaries

প্রতিটা layer জানে তার responsibility কী। Presentation Layer business logic নিয়ে ভাবে না। Business Logic database নিয়ে চিন্তা করে না।

একটা Concrete Example

চলো একটা specific operation দিয়ে বুঝি: "Place Order"

Step 1: Presentation Layer receives request

Customer একটা button click করে। তখন একটা HTTP request আসে:

POST /orders
{
  "customer_id": "123",
  "restaurant_id": "456",
  "items": [
    {"menu_item_id": "1", "quantity": 2},
    {"menu_item_id": "2", "quantity": 1}
  ],
  "delivery_address": "123 Main St",
  "payment_method": "card"
}

Presentation Layer-এর কাজ:

  • এই data receive করা

  • Basic validation করা (required fields আছে কিনা)

  • Business Logic Layer-কে call করা

Step 2: Business Logic Layer processes

এখানে আসল magic হয়। এই layer-এ একটা process থাকবে:

PlaceOrder Process:

1. Validate customer exists
   - Data Layer থেকে customer info নিয়ে আসো
   - যদি না থাকে → Error return করো

2. Validate restaurant exists and is open
   - Restaurant info fetch করো
   - Operating hours check করো
   - যদি বন্ধ থাকে → Error return করো

3. Validate menu items
   - প্রতিটা item-এর জন্য:
     - Item এই restaurant-এ আছে কিনা check করো
     - Available কিনা check করো
     - Price fetch করো
   - কোনো item unavailable হলে → Error return করো

4. Calculate total price
   - Items-এর price sum করো
   - Delivery charges add করো (distance based)
   - Discount apply করো (if any)
   - Tax calculate করো

5. Create order object
   - একটা unique order ID generate করো
   - Current timestamp নাও
   - Initial status set করো: "pending"
   - All details organize করো

6. Process payment
   - Payment Service-কে call করো
   - Amount pass করো
   - Payment method pass করো
   - Response wait করো
   - যদি fail হয় → Error return করো, order cancel করো

7. Save order
   - Data Layer-এ order save করো
   - যদি save fail হয় → Payment refund করো

8. Notify stakeholders
   - Restaurant-কে notify করো: "নতুন order এসেছে"
   - Customer-কে confirmation পাঠাও
   - Delivery team-কে inform করো

9. Return order details
   - Order ID, estimated time সহ return করো

দেখো এই পুরো process-টা কতটা detailed! প্রতিটা step-এ decision নিতে হচ্ছে, error handle করতে হচ্ছে।

Step 3: Data Layer saves everything

Data Layer-এর responsibility simple:

  • Order table-এ insert করো

  • OrderItems table-এ items save করো

  • Transaction maintain করো (যাতে partial save না হয়)

এই Design-এর Advantages বিস্তারিত

Advantage 1: Error Handling হয় Gracefully

দেখো process-এর যেকোনো step-এ error হতে পারে:

  • Customer না থাকতে পারে

  • Restaurant বন্ধ থাকতে পারে

  • Item unavailable হতে পারে

  • Payment fail হতে পারে

  • Database save fail হতে পারে

প্রতিটা জায়গায় আমরা handle করছি কী করতে হবে। এবং যদি কোথাও error হয়, তাহলে previous steps rollback করছি।

উদাহরণ: Payment successful কিন্তু database save fail হলো। তাহলে আমরা payment refund করব। Customer-এর টাকা কেটে order create না হলে problem হবে।

Advantage 2: Business Rules একটা জায়গায়

সব business logic Business Layer-এ আছে।

ধরো তোমার boss বললো: "10PM-এর পর delivery charge double হবে।"

তুমি কোথায় change করবে? শুধু price calculation-এর logic-এ:

Calculate Delivery Charge:

IF current_time > 10 PM:
    delivery_charge = base_charge * 2
ELSE:
    delivery_charge = base_charge

এই change করলেই হবে। পুরো application-এ অন্য কোথাও touch করার দরকার নেই।

Advantage 3: Testing করা সহজ

তুমি Business Logic test করতে পারো এভাবে:

Test: Order placement with unavailable item

Given:
- একজন valid customer
- একটা open restaurant
- একটা unavailable item

When:
- PlaceOrder call করছি

Then:
- Error return করবে
- Error message বলবে: "Item unavailable"
- কোনো order create হবে না database-এ
- কোনো payment process হবে না

Real database বা payment gateway ছাড়াই এই test run করা যাবে। কারণ আমরা fake data দিয়ে simulate করতে পারি।


Part 4: Data Modeling - Information কীভাবে Structure করবে?

এবার আরেকটা critical aspect - Data Modeling। মানে তোমার information কীভাবে organize করবে?

ভুল Data Model-এর পরিণতি

চলো একটা bad example দিয়ে শুরু করি। ধরো তুমি order information এভাবে রাখলে:

Order Table:
- order_id
- customer_name
- customer_phone
- customer_email
- customer_address
- restaurant_name
- restaurant_address
- restaurant_phone
- item_1_name
- item_1_price
- item_1_quantity
- item_2_name
- item_2_price
- item_2_quantity
- ... (up to item_10)
- total_price
- delivery_person_name
- delivery_person_phone

এই design-এর সমস্যা কী?

সমস্যা ১: Fixed Item Limit

তুমি শুধু 10টা item পর্যন্ত রাখতে পারছ। কেউ যদি 11টা item order করতে চায়? তাহলে?

সমস্যা ২: Data Duplication

একই customer যদি 100 বার order করে, তাহলে তার name, phone, email 100 বার save হবে! এটা space waste এবং inconsistency create করে।

ধরো customer তার phone number change করলো। তাহলে 100টা order-এর phone number update করতে হবে? এটা impractical।

সমস্যা ৩: Null Values

যদি কেউ 3টা item order করে, তাহলে item_4 থেকে item_10 পর্যন্ত সব null থাকবে। Database-এ unnecessary space নিচ্ছে।

সমস্যা ৪: Query Difficulty

তুমি যদি জানতে চাও "কোন item সবচেয়ে বেশি order হয়েছে?" - এই query লেখা হবে nightmare। কারণ items 10টা আলাদা column-এ scattered।

সঠিক Data Model

এবার দেখো proper way:

Table 1: Customers

- customer_id (Primary Key)
- name
- email
- phone
- created_at

Table 2: Restaurants

- restaurant_id (Primary Key)
- name
- address
- phone
- operating_hours
- created_at

Table 3: MenuItems

- menu_item_id (Primary Key)
- restaurant_id (Foreign Key)
- name
- description
- price
- category
- is_available

Table 4: Orders

- order_id (Primary Key)
- customer_id (Foreign Key)
- restaurant_id (Foreign Key)
- delivery_address
- order_status
- total_amount
- created_at
- delivery_time

Table 5: OrderItems

- order_item_id (Primary Key)
- order_id (Foreign Key)
- menu_item_id (Foreign Key)
- quantity
- price_at_order_time

এই Design কেন Better?

Benefit 1: No Fixed Limits

যতগুলো item চাও order করতে পারবে। কারণ OrderItems একটা separate table। প্রতিটা item একটা row।

Benefit 2: No Duplication

Customer info একবারই save হচ্ছে Customers table-এ। Orders table-এ শুধু reference (customer_id) রাখছি।

Customer phone change করলে শুধু Customers table-এ একটা row update করলেই হবে। সব orders automatically updated customer info পাবে।

Benefit 3: Easy Queries

"সবচেয়ে জনপ্রিয় item কোনটা?" এই query এখন সহজ:

SELECT menu_item_id, SUM(quantity) as total_ordered
FROM OrderItems
GROUP BY menu_item_id
ORDER BY total_ordered DESC
LIMIT 1

Benefit 4: Flexibility

নতুন তথ্য add করা সহজ। যেমন তুমি যদি "order ratings" add করতে চাও, একটা নতুন table বানাবে:

OrderRatings:
- rating_id
- order_id (Foreign Key)
- food_rating
- delivery_rating
- comments

Existing tables touch করার দরকার নেই।

Relationships বোঝা

এই tables-গুলো কীভাবে connected?

One-to-Many Relationships:

১. একজন Customer-এর অনেক Orders হতে পারে

  • তাই Orders table-এ customer_id রাখছি

২. একটা Restaurant-এর অনেক MenuItems আছে

  • তাই MenuItems table-এ restaurant_id রাখছি

৩. একটা Order-এ অনেক OrderItems থাকতে পারে

  • তাই OrderItems table-এ order_id রাখছি

Many-to-Many Relationship:

Orders আর MenuItems-এর মধ্যে many-to-many relationship:

  • একটা Order-এ অনেক MenuItems থাকতে পারে

  • একটা MenuItem অনেক Orders-এ থাকতে পারে

এই many-to-many handle করার জন্য আমরা একটা junction table বানাই: OrderItems

একটা Complete Order Query

চলো দেখি যখন আমরা একটা order fetch করব, তখন কী কী লাগবে:

Get Order Details for order_id = "12345"

Query করতে হবে:

1. Orders table থেকে:
   - order_status
   - total_amount
   - delivery_address
   - created_at

2. Customers table থেকে (JOIN করে):
   - customer_name
   - customer_phone

3. Restaurants table থেকে (JOIN করে):
   - restaurant_name
   - restaurant_address

4. OrderItems table থেকে:
   - প্রতিটা item-এর quantity

5. MenuItems table থেকে (JOIN করে):
   - item_name
   - item_description

এই সব information আলাদা আলাদা tables-এ organized থাকার কারণে efficiently query করা যাচ্ছে।


Part 5: Interface vs Implementation - একটা Crucial Concept

এবার আসি একটা খুব important concept-এ যেটা LLD-এর heart: Interface vs Implementation

Interface কী?

Interface হলো একটা contract বা promise। এটা বলে দেয় "কী কাজ করা হবে" কিন্তু বলে না "কীভাবে করা হবে"।

একটা real-world analogy:

তুমি একটা restaurant-এ গেলে। Menu তে লেখা আছে: "Chicken Biryani - 300 টাকা"।

এটা একটা interface। তুমি জানো:

  • তুমি 300 টাকা দেবে

  • তুমি Chicken Biryani পাবে

কিন্তু তুমি জানো না:

  • Chef কীভাবে বানাবে

  • কোন মশলা ইউজ করবে

  • কত সময় লাগবে

  • কোন পাত্রে রান্না করবে

এই details implementation। তুমি শুধু interface (menu) দেখে order করছ।

Software-এ Interface

Software-এ interface মানে হলো একটা definition যেটা বলে:

"এই service এই কাজগুলো করতে পারে। তুমি এভাবে call করবে, এটা এভাবে respond করবে।"

একটা ছোট উদাহরণ:

// এটা একটা Interface - শুধু declaration
type PaymentService interface {
    ProcessPayment(amount float64, method string) (transactionID string, error)
    RefundPayment(transactionID string) error
    GetPaymentStatus(transactionID string) (status string, error)
}

এই interface বলছে:

  • একটা PaymentService তিনটা কাজ করতে পারবে

  • ProcessPayment call করলে amount আর method দিতে হবে, return পাবে transactionID বা error

  • RefundPayment call করলে transactionID দিতে হবে

  • GetPaymentStatus দিয়ে status check করা যাবে

কিন্তু কীভাবে এই কাজগুলো হবে? সেটা এখানে নেই।

Implementation

Implementation হলো actual কাজ করার code। একই interface-এর অনেকগুলো implementation হতে পারে:

Implementation 1: Stripe Payment

type StripePayment struct {
    apiKey string
}

func (s *StripePayment) ProcessPayment(amount float64, method string) (string, error) {
    // Stripe API call করবে
    // Stripe-এর specific logic
    // Return Stripe transaction ID
}

Implementation 2: PayPal Payment

type PayPalPayment struct {
    clientID string
    secret   string
}

func (p *PayPalPayment) ProcessPayment(amount float64, method string) (string, error) {
    // PayPal API call করবে
    // PayPal-এর specific logic
    // Return PayPal transaction ID
}

Implementation 3: Manual/Cash Payment

type ManualPayment struct {
    receiptBook string
}

func (m *ManualPayment) ProcessPayment(amount float64, method string) (string, error) {
    // কোনো external API নেই
    // শুধু একটা receipt ID generate করবে
    // Database-এ record রাখবে
}

এই Separation কেন Important?

এবার আসল magic। তোমার Order Management code যখন payment process করবে, সে এভাবে লিখবে:

type OrderService struct {
    paymentService PaymentService  // Interface type
}

func (o *OrderService) PlaceOrder(order Order) error {
    // ... অন্যান্য logic ...

    // Payment process
    transactionID, err := o.paymentService.ProcessPayment(order.TotalAmount, order.PaymentMethod)
    if err != nil {
        return err
    }

    // ... বাকি logic ...
}

দেখো, OrderService জানে না payment আসলে কীভাবে হচ্ছে। সে শুধু জানে একটা PaymentService আছে যেটা ProcessPayment করতে পারে।

এর Advantages কী?

Advantage 1: Easy to Switch

আজকে Stripe ইউজ করছ, কাল PayPal-এ switch করতে চাও? OrderService-এর একটা লাইনও change করার দরকার নেই!

// আজকে
orderService := OrderService{
    paymentService: &StripePayment{apiKey: "..."}
}

// কাল
orderService := OrderService{
    paymentService: &PayPalPayment{clientID: "...", secret: "..."}
}

Advantage 2: Testing সহজ

Test-এর সময় real payment gateway ইউজ করতে হবে না। একটা fake implementation বানাও:

type FakePayment struct {
    shouldSucceed bool
}

func (f *FakePayment) ProcessPayment(amount float64, method string) (string, error) {
    if f.shouldSucceed {
        return "fake-transaction-123", nil
    }
    return "", errors.New("payment failed")
}

এখন test করো:

// Success case test
fakePayment := &FakePayment{shouldSucceed: true}
orderService := OrderService{paymentService: fakePayment}
// Test order placement

// Failure case test
fakePayment := &FakePayment{shouldSucceed: false}
orderService := OrderService{paymentService: fakePayment}
// Test order placement failure handling

Advantage 3: Multiple Implementations একসাথে

তুমি চাইলে runtime-এ decide করতে পারো কোন implementation ইউজ করবে:

func GetPaymentService(country string) PaymentService {
    switch country {
    case "US":
        return &StripePayment{...}
    case "BD":
        return &BkashPayment{...}
    case "IN":
        return &PaytmPayment{...}
    default:
        return &ManualPayment{...}
    }
}

Advantage 4: Decorator Pattern

তুমি একটা payment service-কে আরেকটা দিয়ে wrap করতে পারো extra functionality-র জন্য:

type LoggingPayment struct {
    wrapped PaymentService
    logger  Logger
}

func (l *LoggingPayment) ProcessPayment(amount float64, method string) (string, error) {
    l.logger.Log("Payment started: amount =", amount)

    transactionID, err := l.wrapped.ProcessPayment(amount, method)

    if err != nil {
        l.logger.Log("Payment failed:", err)
    } else {
        l.logger.Log("Payment success: transactionID =", transactionID)
    }

    return transactionID, err
}

এখন তুমি যেকোনো payment service-কে logging দিয়ে wrap করতে পারো:

stripePayment := &StripePayment{...}
loggedPayment := &LoggingPayment{
    wrapped: stripePayment,
    logger:  myLogger,
}

orderService := OrderService{paymentService: loggedPayment}

Part 6: Error Handling - যখন জিনিস ভুল হয়

Software development-এ একটা সত্য: জিনিস ভুল হবেই

  • Database connection fail করতে পারে

  • Network timeout হতে পারে

  • External API down থাকতে পারে

  • Invalid input আসতে পারে

  • Resources শেষ হয়ে যেতে পারে

Good design মানে এই সব scenarios handle করা।

Error Handling Strategies

Strategy 1: Fail Fast

যত তাড়াতাড়ি সম্ভব error detect করো এবং stop করো।

উদাহরণ: Order placement-এ যদি customer ID invalid হয়, তাহলে আর এগোনোর দরকার নেই। সাথে সাথে error return করো।

PlaceOrder:
  IF customer_id is empty:
    RETURN error "Customer ID required"

  IF NOT customer exists in database:
    RETURN error "Customer not found"

  // এরপরের steps...

এতে কী লাভ? Resources waste হচ্ছে না। Database queries, API calls - এসব করার আগেই জানা যাচ্ছে যে এটা fail করবে।

Strategy 2: Graceful Degradation

কিছু features fail হলেও main functionality যেন কাজ করে।

উদাহরণ: Order placed হওয়ার পর email notification পাঠাতে গেলে email service down। তাহলে কি পুরো order cancel করবে? না!

PlaceOrder:
  // ... order create করা ...
  // ... payment process করা ...
  // Order successfully created!

  // এখন notification পাঠাও
  TRY:
    SendEmailNotification(order)
  CATCH error:
    LOG error "Email notification failed"
    // কিন্তু order তো হয়ে গেছে, তাই এটা critical error না

  RETURN success

Email fail হলেও order হয়ে গেছে। Customer পরে in-app notification দেখতে পারবে।

Strategy 3: Retry Logic

কিছু errors temporary। Network glitch, momentary server overload। এসব ক্ষেত্রে retry করলে হয়ে যেতে পারে।

ProcessPayment with Retry:
  maxRetries = 3
  delay = 1 second

  FOR attempt = 1 to maxRetries:
    TRY:
      result = CallPaymentAPI()
      RETURN result  // Success!

    CATCH error:
      IF attempt < maxRetries:
        LOG "Payment attempt failed, retrying..."
        WAIT for delay
        delay = delay * 2  // Exponential backoff
      ELSE:
        RETURN error "Payment failed after 3 attempts"

Strategy 4: Circuit Breaker

যদি একটা external service repeatedly fail করছে, তাহলে কিছুক্ষণ সেটা call করা বন্ধ করো। এতে resources waste হয় না।

Payment Circuit Breaker:
  state = CLOSED  // Normal operation
  failureCount = 0
  failureThreshold = 5
  resetTimeout = 30 seconds

  ProcessPayment:
    IF state == OPEN:
      RETURN error "Payment service temporarily unavailable"

    TRY:
      result = CallPaymentAPI()
      failureCount = 0  // Reset on success
      RETURN result

    CATCH error:
      failureCount++

      IF failureCount >= failureThreshold:
        state = OPEN
        START timer to reset after resetTimeout

      RETURN error

এতে কী হলো? যদি payment gateway down থাকে, তাহলে প্রতিবার timeout wait না করে সরাসরি error return করছি। এতে response time ভালো থাকছে।


Part 7: Scalability - যখন System বড় হয়

তুমি একটা app বানালে ১০০ users-এর জন্য। সব ঠিকঠাক চলছে। হঠাৎ viral হয়ে গেল, এখন ১০০,০০০ users!

Poor design থাকলে এখানে crash করবে। Good design থাকলে scale করা যাবে।

Scalability বলতে কী বুঝায়?

Scalability মানে system-এর capacity বাড়ানোর ability:

  • বেশি users handle করা

  • বেশি requests handle করা

  • বেশি data store করা

Vertical vs Horizontal Scaling

Vertical Scaling (Scale Up): বড় machine ইউজ করা।

  • 2 GB RAM থেকে 8 GB RAM

  • 2 core CPU থেকে 8 core CPU

সমস্যা: একটা limit আছে। তুমি infinitely বড় machine পাবে না।

Horizontal Scaling (Scale Out): বেশি machines add করা।

  • 1 server থেকে 10 servers

এটা better কারণ theoretically unlimited scaling possible।

Design for Horizontal Scaling

Horizontal scaling-এর জন্য design করতে হলে কিছু principles follow করতে হয়:

Principle 1: Stateless Services

Service-গুলো stateless হতে হবে। মানে একটা request-এর information অন্য request-এ লাগবে না।

উদাহরণ:

Bad design - Stateful:

Server 1:
  User logs in
  Session data stored in Server 1's memory

Next request from same user:
  IF request goes to Server 2:
    Server 2 doesn't know user is logged in
    User has to login again!

Good design - Stateless:

Server 1:
  User logs in
  Session data stored in Redis (shared storage)
  Return session token to user

Next request from same user:
  Request goes to Server 2
  Server 2 checks token in Redis
  Finds user session
  Request processed successfully!

Principle 2: Database Connection Pooling

প্রতিটা request-এ নতুন database connection খোলা expensive। Connection pool maintain করো:

Connection Pool:
  Initialize:
    Create 10 database connections
    Keep them ready in pool

  When request comes:
    Take a connection from pool
    Use it
    Return it back to pool

  Benefits:
    No connection creation overhead
    Connections reused
    Limited connections prevent database overload

Principle 3: Caching

যেসব data frequently access হয় কিন্তু rarely change হয়, সেগুলো cache করো।

উদাহরণ: Restaurant menu. এটা ঘন ঘন change হয় না কিন্তু প্রতিটা user browse করার সময় দেখে।

Get Restaurant Menu:
  CHECK cache:
    IF menu exists in cache:
      RETURN from cache  // Fast!
    ELSE:
      FETCH from database
      STORE in cache for 1 hour
      RETURN menu

Principle 4: Asynchronous Processing

যেসব কাজ immediately করার দরকার নেই, সেগুলো background-এ করো।

উদাহরণ: Order placed হওয়ার পর email পাঠানো। এটা order creation block করা উচিত না।

PlaceOrder:
  Validate order
  Process payment
  Save order to database

  ADD to queue: "Send email for order_id = 12345"

  RETURN success to user immediately

Background Worker:
  Continuously check queue
  IF job found:
    Process job (send email)
    Remove from queue

Part 8: Real World Trade-offs

Perfect design বলে কিছু নেই। সব decision-এর trade-offs আছে। Good designer জানে কখন কোন trade-off নেওয়া উচিত।

Trade-off 1: Performance vs Maintainability

Scenario: তোমার একটা query আছে যেটা ৩টা table JOIN করে। এটা slow চলছে।

Option A: Denormalize করো

  • Orders table-এ restaurant_name সরাসরি রাখো

  • JOIN করার দরকার নেই

  • Query fast হবে

  • কিন্তু: Restaurant name change করলে সব orders update করতে হবে

Option B: Caching করো

  • Query result cache করো

  • Fast access

  • Data consistency maintained

  • কিন্তু: Cache invalidation logic দরকার

কোনটা better? Depends on:

  • Restaurant name কত ঘন ঘন change হয়?

  • Query কত frequent?

  • Data accuracy কত critical?

Trade-off 2: Consistency vs Availability

Scenario: তোমার একটা inventory system আছে। কোনো product-এ 5টা stock আছে।

Strong Consistency:

  • প্রতিটা read latest data দেবে

  • কিন্তু slow হবে

  • সব replicas sync করতে হবে

Eventual Consistency:

  • Read fast হবে

  • কিন্তু momentarily outdated data দিতে পারে

  • সব replicas sync হতে কিছুক্ষণ লাগবে

E-commerce-এ কোনটা ভালো?

  • Stock count-এ eventual consistency নেওয়া যায়

  • কিন্তু payment-এ strong consistency লাগবে

Trade-off 3: Simplicity vs Flexibility

Scenario: Discount system design করছো।

Simple Approach:

  • Discount একটা percentage হবে

  • সহজে implement করা যাবে

  • কিন্তু: "Buy 2 Get 1 Free" type offers করা যাবে না

Flexible Approach:

  • একটা complex discount engine বানাও

  • যেকোনো type discount support করবে

  • কিন্তু: Complex, debug করা কঠিন, slow

কোনটা চাও? Depends on:

  • এখন কী কী discounts দরকার?

  • Future-এ কী লাগতে পারে?

  • Development time কত?


শেষ কথা: LLD একটা Journey

Low Level Design শেখা একদিনের কাজ না। এটা একটা continuous journey।

প্রথমে হয়তো overwhelmed লাগবে। এত কিছু মাথায় রাখতে হবে? এত চিন্তা করতে হবে?

কিন্তু gradually practice করতে করতে এগুলো automatic হয়ে যাবে। তুমি code লেখার আগে naturally think করবে:

  • এটা কীভাবে organize করব?

  • কোথায় কোন responsibility থাকবে?

  • Future-এ কীভাবে extend করা যাবে?

  • Test করা কত সহজ হবে?

আর যখন এই mindset তৈরি হবে, তখন তুমি শুধু code লিখছ না - craft করছ software। এটাই একজন junior developer আর senior developer-এর মধ্যে পার্থক্য।

More from this blog

Go-তে Interface কীভাবে Code Decouple করে?

একটা HTTP Server দিয়ে পুরো ব্যাপারটা বুঝে নেওয়া যাক আমরা সবাই জানি Go একটা সিম্পল ল্যাঙ্গুয়েজ, কিন্তু interface নিয়ে অনেকেরই confusion থাকে। আজকে আমরা দেখব কীভাবে interface আসলে তোমার code-কে flexible এবং maintainable বানায়। একটা real-world HTT...

Oct 14, 202520 min read5
Go-তে Interface কীভাবে Code Decouple করে?

তোমার Project-এ Coupled Code কীভাবে খুঁজে বের করবে?

Coupled code খোঁজা মানে হচ্ছে তোমার codebase-এ এমন জায়গা খুঁজে বের করা যেখানে একটা অংশ আরেকটার উপর বেশি depend করছে। এটা একটা detective work — তুমি clue খুঁজবে, pattern দেখবে, এবং সমস্যা চিহ্নিত করবে। চলো step by step শিখি কীভাবে এটা করতে হয়। কেন ...

Oct 14, 202510 min read2

Go-তে Object (Struct Instance) তৈরির সম্পূর্ণ গাইড

Go programming শেখার সময় একটা জিনিস খুব তাড়াতাড়ি বুঝতে হয় - কীভাবে object তৈরি করতে হয়। অন্য language যেমন Java বা Python এ class আছে, কিন্তু Go-তে আছে struct। আর struct এর instance বানানোই হলো object তৈরি করা। আজকের এই blog এ আমরা দেখব Go-তে ob...

Oct 13, 202524 min read3
Go-তে Object (Struct Instance) তৈরির সম্পূর্ণ গাইড
I

Imran Hasan

61 posts

Full-stack developer with experience in developing and managing web applications. Skilled in React, Node.js, HTML, CSS, and JavaScript. Experience in managing website hosting and security.