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

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-এর মধ্যে পার্থক্য।


