সরাসরি পর্বে যান
পর্ব ৩৮ · ৭০-এর মধ্যে · মডিউল ৭

Software architecture — layered, client-server, pipe-filter, event-driven আর ADR

Software Architecture — Drivers, Styles, Views, Decision Records & Fitness Functions

Taught in Bangla, this lesson defines software architecture and its drivers, writes quality attribute scenarios, compares layered, client-server, pipe-and-filter, microkernel and event-driven styles, chooses between monolith, modular monolith and microservices with a weighted trade-off matrix, draws architecture with 4+1 views and the C4 model, records decisions as ADRs in Git, and protects them with ATAM, fitness functions and Conway's law.

এই পর্বে যা শিখবেন

  • Architecture আর design-এর পার্থক্য বলা, আর একটি system-এর architectural driver খুঁজে বের করা
  • একটি অস্পষ্ট চাওয়াকে source-stimulus-artifact-environment-response-response measure দিয়ে মাপা যায় এমন scenario-তে রূপ দেওয়া
  • ছয়-সাতটি প্রধান architectural style-এর গঠন, কখন ব্যবহার আর trade-off তুলনা করা, আর কোডে layer-এর নিয়মভঙ্গ ধরা
  • Weighted trade-off matrix আর sensitivity দিয়ে style বাছাই, 4+1 ও C4 দিয়ে architecture আঁকা
  • একটি পূর্ণ ADR লিখে Git-এ রাখা, আর fitness function দিয়ে সিদ্ধান্তটিকে test-এ বেঁধে দেওয়া

১ · Software architecture কী — বাড়ির নকশা থেকে system-এর কাঠামো

মডিউল ৫ আর ৬-এ আপনি ছোট পরিসরে নকশা দেখেছেন: একটি class কতটা দায়িত্ব নেবে (পর্ব ২৭), দুটি object কীভাবে কথা বলবে (পর্ব ৩৫), dependency কোথা থেকে আসবে (পর্ব ৩৭)। এবার ক্যামেরা পিছিয়ে নিচ্ছি। মডিউল ৭ শুরু হচ্ছে পুরো system-এর কাঠামো দিয়ে — এই পর্বে software architecture, তারপর UI-র কোড সাজানো (পর্ব ৩৯), domain-কে কেন্দ্রে রাখা clean ও hexagonal architecture (পর্ব ৪০)। এরপর আবার কাছে আসব — clean code (পর্ব ৪১), code smell আর refactoring (পর্ব ৪২), technical debt আর legacy code (পর্ব ৪৩), আর code review (পর্ব ৪৪)। পুরো মডিউলের প্রশ্ন একটাই: software বছরের পর বছর বদলানো যায় কীভাবে?

একটি ছয়তলা বাড়ির কথা ভাবুন। কোন ঘরে কোন রঙ, কোন দরজায় কোন হাতল — রাজমিস্ত্রি আর মালিক পরে বদলাতে পারেন, খরচ সামান্য। কিন্তু pillar কোথায়, সিঁড়ি কোন দিকে, ভিত কত গভীর, পানির লাইন কোন দেয়ালের ভেতর দিয়ে — এগুলো একবার ঢালাই হয়ে গেলে বদলানো মানে বাড়ি ভাঙা। স্থপতির নকশা মূলত এই দ্বিতীয় ধরনের সিদ্ধান্ত। Software-এও একই ভাগ আছে: একটি function-এর নাম বদলানো এক মিনিটের কাজ; কিন্তু “সব ডেটা একটি database-এ থাকবে” বা “mobile app সরাসরি database-এ কথা বলবে” — এই সিদ্ধান্ত বদলাতে মাসের পর মাস লাগে।

তিনটি পরিচিত সংজ্ঞা

Architecture-এর কোনো একক সর্বসম্মত সংজ্ঞা নেই — আর এটা লুকানোর কিছু নয়। তিনটি বহুল উদ্ধৃত দৃষ্টিভঙ্গি পাশাপাশি রাখলে ছবি পরিষ্কার হয়:

কাঠামো (structures)

SEI-র Bass, Clements ও Kazman-এর Software Architecture in Practice বইয়ের পরিচিত সংজ্ঞাটি মোটামুটি এরকম: architecture হলো system নিয়ে যুক্তি করতে যেসব কাঠামো লাগে তার সমষ্টি — software-এর উপাদান (element), তাদের সম্পর্ক (relation) আর দুইয়েরই গুণ (property). ভাষা সংস্করণভেদে সামান্য আলাদা।

উপাদান · সম্পর্ক · গুণ

কঠিন-বদল সিদ্ধান্ত

বহু লেখক architecture-কে বলেন সেই সিদ্ধান্তগুলো, যেগুলো পরে বদলানো কঠিন — অথবা যেগুলোকে মানুষ বদলানো কঠিন বলে মনে করে। এখান থেকে একটি কাজের প্রশ্ন আসে: “এই সিদ্ধান্ত ভুল হলে ফেরাতে কত খরচ?”

reversibility-র প্রশ্ন

“The important stuff”

Martin Fowler-এর ২০০৩ সালের প্রবন্ধ “Who Needs an Architect?”-এ Ralph Johnson-এর একটি কথা উদ্ধৃত হয়, প্রায় এভাবে: architecture হলো গুরুত্বপূর্ণ জিনিস — সেটা যা-ই হোক। অর্থাৎ কোনটি architecture, তা অভিজ্ঞ দল প্রেক্ষাপট দেখে ঠিক করে।

প্রেক্ষাপটই নির্ধারক

“Software architecture” আলাদা শাস্ত্র হিসেবে দাঁড়ায় ১৯৯০-এর দশকে। ১৯৯২ সালে Dewayne Perry ও Alexander Wolf-এর প্রবন্ধ architecture-কে উপাদান, রূপ আর যুক্তি (rationale) দিয়ে বর্ণনা করে। ১৯৯৬ সালে Mary Shaw ও David Garlan-এর বই Software Architecture: Perspectives on an Emerging Discipline pipe-and-filter, layered, event-based-এর মতো architectural style-কে নাম দিয়ে তুলনা করে। একই বছর Buschmann ও সহলেখকদের Pattern-Oriented Software Architecture (খণ্ড ১) Layers, Pipes and Filters, Broker-কে pattern আকারে লেখে — পর্ব ৩২-এ যার উল্লেখ ছিল। তবে ভাবনাটি আরও পুরোনো: ১৯৬৮ সালে Dijkstra তাঁর THE operating system-কে স্তরে স্তরে সাজিয়েছিলেন।

Architecture বনাম design

একটি প্রচলিত বাক্য আছে: সব architecture-ই design, কিন্তু সব design architecture নয়। পার্থক্যটা মূলত পরিসর আর বদলের দামে। Software design-এর একটি বড় অংশ এক module বা কয়েকটি class-এর ভেতরে থাকে; architecture সেই সিদ্ধান্তগুলো, যেগুলো অনেক module, অনেক দল বা পুরো system-এর গুণকে একসাথে প্রভাবিত করে।

প্রশ্নসাধারণত architectureসাধারণত design
পরিসরপুরো system, একাধিক module বা serviceএকটি module, class বা function
উদাহরণএকটি deploy-যোগ্য app না দশটি service; event দিয়ে কথা না সরাসরি callএখানে strategy না সাধারণ if; method-এর parameter কী হবে
ভুলের দামসপ্তাহ বা মাস, অনেক দলের কাজঘণ্টা বা দিন, একজনের refactoring
কে প্রভাবিতসব developer, ops, ব্যবসাসেই module-এর developer
লিখে রাখার জায়গাADR, architecture diagram (অংশ ৭–৮)কোড, comment, PR-এর বিবরণ

সীমারেখাটা ঝাপসা, আর সেটাই স্বাভাবিক। ছোট app-এ “কোন ORM ব্যবহার করব” একটি সাধারণ design প্রশ্ন; লাখো ব্যবহারকারীর payment system-এ একই প্রশ্ন architecture হয়ে ওঠে, কারণ তার ওপর performance, নিরাপত্তা আর দশটি দলের কোড নির্ভর করে। এই পর্বের পুরো উদাহরণ একটি কাল্পনিক system: “টিকিটঘর” — আন্তঃজেলা বাসের টিকিট কেনার একটি কাল্পনিক app, যার সবচেয়ে কঠিন সময় ঈদের আগের দুই সপ্তাহ, যখন ভিড় স্বাভাবিকের বহু গুণ।

২ · Architecture-কে কী চালায় — driver আর quality attribute scenario

একই functional requirement-এর জন্য দশটি ভিন্ন architecture বানানো যায়। “যাত্রী টিকিট খুঁজবেন, আসন বাছবেন, পেমেন্ট করবেন, SMS পাবেন” — এটুকু একটি PHP script-এও হয়, পঞ্চাশটি microservice-এও হয়। তাহলে কোনটা বেছে নেবেন, তা ঠিক করে কে? উত্তর: architectural driver — যে requirement আর শর্তগুলো কাঠামোর আকার ঠিক করে দেয়। তিন ধরনের driver আছে:

১. Quality attribute

Quality attribute হলো system কতটা ভালোভাবে কাজ করে: availability, performance, modifiability, security, testability. এগুলোই সবচেয়ে বেশি কাঠামো বদলায়। ISO/IEC 25010-এর পুরো তালিকা পর্ব ৩-এ দেখেছেন।

টিকিটঘর: ঈদের ভিড়ে সচল থাকা

২. Constraint

Constraint সমাধানের পথ বেঁধে দেয়: দলের আকার ও দক্ষতা, সময়সীমা, বাজেট, বাধ্যতামূলক প্রযুক্তি, আইন। এগুলো নিয়ে দর-কষাকষি সাধারণত চলে না।

টিকিটঘর: ৫ developer, ৬ মাস

৩. Functional requirement

Functional requirement ঠিক করে কোন দায়িত্ব কোথায় যাবে — booking, payment, fare, notification. Style-এর চেয়ে module-এর ভাগে এর প্রভাব বেশি।

টিকিটঘর: খোঁজা, বুকিং, পেমেন্ট, SMS

Functional আর non-functional requirement-এর পার্থক্য, আর NFR-কে সংখ্যায় বাঁধার কৌশল (fit criterion, Planguage) পর্ব ২১-এ এসেছে। Architecture-এর কাজে সেই সংখ্যাগুলো আরও একটি কাঠামোয় লেখা হয়: quality attribute scenario. SEI-র লেখকেরা (Bass, Clements ও Kazman) এর ছয়টি অংশ ব্যবহার করেন। পর্ব ৩-এ একটি ফলাফল পোর্টালের performance scenario লিখেছিলেন; এখানে নিচ্ছি architecture-এর জন্য সবচেয়ে গুরুত্বপূর্ণ কিন্তু সবচেয়ে অবহেলিত গুণটি — modifiability, অর্থাৎ বদলানোর সহজতা।

Quality attribute scenario কার্ড
মূল চাওয়া
Source
Stimulus
Artifact
Environment
Response
Response measure
  1. অস্পষ্ট চাওয়া। “সহজে যোগ করা যাবে” — কতটা সহজ? কার জন্য? কখন? এই বাক্য দিয়ে দুটি architecture তুলনা করা যায় না, কারণ দুই পক্ষই বলবে “আমারটায় সহজ”। নিচের ছয়টি ঘর একে একে ভরে দেখি।
  2. Source — উদ্দীপনা কোথা থেকে আসে। Modifiability-র ক্ষেত্রে উৎস সাধারণত মানুষ: developer, পণ্য-দল, নিয়ন্ত্রক সংস্থা। Availability-র ক্ষেত্রে উৎস হতো একটি server বা network.
  3. Stimulus — ঠিক কী ঘটে। এখানে একটি নির্দিষ্ট বদলের অনুরোধ: নতুন একটি mobile wallet. “যেকোনো বদল” লিখলে scenario আবার অস্পষ্ট হয়ে যায় — একটি বাস্তব, সম্ভাব্য বদল বেছে নিন।
  4. Artifact — কোন অংশে আঘাত লাগে। Payment module-এর কোড আর config. পুরো system নয়; artifact যত নির্দিষ্ট, তত ভালো যাচাই।
  5. Environment — কোন অবস্থায়। Design time, অর্থাৎ স্বাভাবিক উন্নয়নের সময়। একই বদল ঈদের আগের code freeze-এ চাওয়া হলে প্রত্যাশিত সাড়া অন্য হতো — তাই environment লেখা জরুরি।
  6. Response — system (আর দল) কী করে। বদল করা, test করা, deploy করা — আর খেয়াল করুন শর্তটি: booking আর fare module-এ হাত দিতে হবে না। এই শর্তটিই architecture-কে চ্যালেঞ্জ করে।
  7. Response measure — সংখ্যায় মাপকাঠি। ৩ person-day-এর মধ্যে, payment-এর বাইরে একটিও file বদল নয়, পুরোনো সব test পাস। এখন দুটি প্রস্তাবিত architecture-কে এই scenario দিয়ে পরীক্ষা করা যায় — যেখানে payment-এর কোড চারটি স্তরে ছড়ানো, সেখানে “০টি file” মানা প্রায় অসম্ভব।

টিকিটঘর-এর জন্য আরও দুটি scenario সংক্ষেপে — পুরো কার্ড না লিখে এক লাইনে, যেমনটি অনেক দল তাদের তালিকায় রাখে:

গুণSource → stimulusEnvironmentResponse আর measure
Availabilityএকটি app server → হঠাৎ বন্ধঈদের আগের peak, স্বাভাবিকের প্রায় ২০ গুণ ভিড় (অনুমান)বাকি server অনুরোধ নেয়; ৩০ সেকেন্ডের মধ্যে স্বাভাবিক; নিশ্চিত একটি booking-ও হারায় না
Performanceযাত্রী → আসনের তালিকা চানpeak সময়p95 latency ৮০০ ms-এর কম
মূল ধারণা

Architecture-এর আলোচনা “কোনটা ভালো” দিয়ে শুরু হলে তর্ক শেষ হয় না। “কোন scenario-তে কোনটা পাস করে” দিয়ে শুরু হলে আলোচনা পরীক্ষাযোগ্য হয়। Scenario-গুলো সাধারণত অগ্রাধিকার দিয়ে সাজানো হয় — সব গুণ একসাথে সর্বোচ্চ হয় না (পর্ব ৩-এর trade-off অংশ)। Distributed system-এর পরিসরে requirement থেকে trade-off-এর ধাপগুলো System Design কোর্সের Requirement আর trade-off পর্বে আরও বিস্তারিত।

৩ · Layered architecture — strict, relaxed আর sinkhole

প্রথমে চারটি প্রধান style একসাথে এক নজরে দেখে নিন; তারপর একে একে ভেতরে ঢুকব।

চারটি style-এর কাঠামো — এক নজরে

তীর = নির্ভরতা বা ডেটার প্রবাহ; সব উদাহরণ কাল্পনিক টিকিটঘর app থেকে

Layered, pipe-and-filter, event-driven আর microkernel style-এর কাঠামো Layered · স্তর 4 · presentation 3 · application 2 · domain 1 · data ড্যাশ: স্তর ডিঙানো Pipe-and-filter read · উৎস clean · filter tokenize · filter count · sink মোটা তীর = pipe Event-driven booking · প্রকাশক event broker BookingPaid seat SMS points প্রকাশক গ্রাহকদের চেনে না Microkernel · plugin core চুক্তি + registry card wallet report export নতুন ক্ষমতা = নতুন plugin
ছবি ৩৮.১ — চারটি style-এর কঙ্কাল। Layered-এ নির্ভরতা শুধু নিচের দিকে; pipe-and-filter-এ ডেটা এক দিকে বয়ে চলে; event-driven-এ প্রকাশক আর গ্রাহকের মাঝে broker; microkernel-এ ছোট core আর তার চুক্তি মেনে যুক্ত plugin.

Layered architecture সবচেয়ে পরিচিত, আর অনেক দলের “ডিফল্ট” পছন্দ। কোড ওপর-নিচে ভাগ হয়: ওপরে presentation (web page, mobile screen, API controller), তার নিচে application (একটি use case-এর ধাপ সমন্বয়: “একটি বুকিং” মানে আসন যাচাই, ভাড়া হিসাব, পেমেন্ট শুরু), তার নিচে domain (ব্যবসার নিয়ম: একই আসন দুজনকে নয়), আর একদম নিচে data (database-এ পড়া-লেখা)। মূল নিয়ম একটাই: নির্ভরতা শুধু নিচের দিকে। Domain কখনো জানবে না কোন screen তাকে ডাকছে। এটি পর্ব ২৬-এর separation of concerns-এর বড় আকার। আরেকটি পরিচিত উদাহরণ network-এর protocol stack — TCP/IP-এ application, transport, network, link — যা Computer Networks কোর্সে বিস্তারিত।

Strict বনাম relaxed

Strict (closed) layering-এ একটি স্তর শুধু ঠিক নিচের স্তরটিকে ডাকতে পারে — presentation সরাসরি data-তে যেতে পারে না, application আর domain পেরিয়েই যেতে হবে। Relaxed (open) layering-এ নিচের যেকোনো স্তর ডাকা যায়। Strict-এর লাভ: data স্তর বদলালে শুধু domain-কে মানিয়ে নিতে হয়, বাকিরা নিরাপদ। দাম: অনেক “শুধু পার করে দেওয়া” কোড। বাস্তবে অনেক দল মিশ্র নিয়ম রাখে — কিছু স্তর (যেমন একটি shared utility স্তর) “open” ঘোষণা করা, বাকিগুলো “closed”. কোন নিয়ম, তা লিখে রাখাই আসল কথা — নইলে প্রত্যেকে নিজের মতো ধরে নেন। নিয়ম লেখা থাকলে যন্ত্র দিয়ে যাচাইও করা যায়:

// Layer-violation checker: strict vs relaxed layering (fictional bus-ticket app).
const layer = {                       // 4 = top, 1 = bottom
  BookingPage: 4, AdminPage: 4,       // presentation
  BookingService: 3, FareService: 3,  // application
  Seat: 2, Fare: 2,                   // domain
  TicketRepo: 1, Db: 1                // data
};
const imports = [                     // [from, to]
  ["BookingPage", "BookingService"], ["BookingService", "FareService"],
  ["BookingService", "Seat"],        ["BookingService", "TicketRepo"],
  ["FareService", "Fare"],           ["Fare", "FareService"],
  ["AdminPage", "TicketRepo"],       ["Seat", "TicketRepo"],
  ["TicketRepo", "Db"]
];
function check(mode) {
  const found = [];
  for (const [from, to] of imports) {
    const gap = layer[from] - layer[to];        // > 0 means "goes down"
    const edge = `${from}(${layer[from]}) -> ${to}(${layer[to]})`;
    if (gap < 0) found.push("UP   " + edge);    // never allowed
    else if (mode === "strict" && gap > 1) found.push("SKIP " + edge);
  }
  return found;
}
for (const mode of ["strict", "relaxed"]) {
  const v = check(mode);
  console.log(`${mode}: ${v.length} violation(s) in ${imports.length} imports`);
  v.forEach(line => console.log("  " + line));
}

আউটপুট:

strict: 3 violation(s) in 9 imports
  SKIP BookingService(3) -> TicketRepo(1)
  UP   Fare(2) -> FareService(3)
  SKIP AdminPage(4) -> TicketRepo(1)
relaxed: 1 violation(s) in 9 imports
  UP   Fare(2) -> FareService(3)

নিয়মটি সংখ্যায় এক লাইনের: gap = ওপরের স্তর − নিচের স্তর. ঋণাত্মক gap মানে ওপরের দিকে নির্ভরতা — যেকোনো নিয়মেই অপরাধ। Fare (domain) যদি FareService (application)-কে import করে, তবে domain আর পুনর্ব্যবহারযোগ্য থাকে না; আর FareService → Fare তীরের সাথে মিলে একটি dependency cycle-ও তৈরি হয়েছে (পর্ব ২৫)। gap ১-এর বেশি মানে স্তর ডিঙানো — strict-এ নিষেধ, relaxed-এ চলে; তাই strict-এ ৩টি, relaxed-এ ১টি। একই স্তরের ভেতরের import (BookingService → FareService, gap ০) দুই নিয়মেই অনুমোদিত। কোনটি গ্রহণযোগ্য, তা দলের ঘোষিত নিয়ম ঠিক করে; এই খেলনাটির কাজ শুধু নিয়মটিকে মাপা যায় এমন করা। বাস্তব project-এ import-এর তালিকা আসে কোড পড়ে — অংশ ৯-এ Python-এর নিজের parser দিয়ে সেটি দেখব।

Sinkhole anti-pattern আর layered-এর দাম

ধরুন যাত্রী শুধু “আমার পুরোনো টিকিটগুলো দেখান” চাইলেন। Controller ডাকল TicketService.list(), যে ডাকল TicketDomain.list(), যে ডাকল TicketRepo.list() — মাঝের দুটি স্তর কিছুই করল না, শুধু পার করে দিল। Mark Richards এমন অবস্থার নাম দিয়েছেন architecture sinkhole anti-pattern. কিছু অনুরোধ এমন হবেই; Richards মোটা দাগে একটি ৮০-২০ নিয়মের কথা বলেন — প্রায় ২০ শতাংশ অনুরোধ “শুধু পার” হলে স্বাভাবিক, কিন্তু অনুপাত উল্টে গেলে স্তরগুলো খরচ ছাড়া কিছু দিচ্ছে না। তখন কিছু স্তর open করা বা অন্য style ভাবা যায়।

কখন layered

ছোট-মাঝারি business app, নতুন দল, সময় কম; দলের সবাই কাঠামোটি চেনেন; বেশিরভাগ use case-এ সত্যিকার ব্যবসার নিয়ম আছে।

বোঝা ও test করা সহজ

দাম

একটি নতুন field যোগ করতে প্রায়ই চারটি স্তরই বদলাতে হয় — বদল “আড়াআড়ি” কাটে। পুরো app একসাথে deploy হয়। আর নিচের data স্তরের ওপর সবার নির্ভরতা domain-কে database-এর আকারে বেঁধে ফেলে — এই সমস্যার সমাধান পর্ব ৪০-এ (dependency-কে উল্টে domain-কে কেন্দ্রে রাখা)।

sinkhole, আড়াআড়ি বদল

৪ · Client-server, pipe-and-filter আর microkernel

Client-server

Client-server-এ এক পক্ষ অনুরোধ পাঠায় (client), আরেক পক্ষ সেবা ও ডেটার মালিক হয়ে উত্তর দেয় (server). টিকিটঘর-এর mobile app আর web page হলো client; API server আর database হলো server-এর দিক। রেলস্টেশনের টিকিট কাউন্টারের মতো — যাত্রী অনেক, কাউন্টারের ওপারে খাতা একটাই, আর খাতায় শুধু কাউন্টারের লোক লেখেন। মূল সিদ্ধান্তগুলো:

  • Client কতটা “বুদ্ধিমান”: thin client শুধু দেখায়; rich client নিজেও হিসাব করে (যেমন offline-এ আসনের তালিকা রাখা)। নিয়ম client-এ থাকলে প্রতিটি app version আলাদা নিয়ম চালাতে পারে — পুরোনো app ফোনে বছরখানেক থেকে যায়। তাই বিশ্বাসযোগ্য নিয়ম, যেমন ভাড়া আর আসন বরাদ্দ, server-এ; client-এর যাচাই শুধু সুবিধার জন্য (পর্ব ৩১-এর trust boundary).
  • Layer বনাম tier: layer কোডের ভাগ, tier চালানোর জায়গার ভাগ। Browser, application server আর database server — তিনটি tier; একে n-tier বা three-tier বলা হয়। একটি tier-এর ভেতরে কয়েকটি layer থাকতে পারে।
  • বিকল্প: peer-to-peer, যেখানে প্রতিটি অংশ client-ও, server-ও (যেমন BitTorrent) — কেন্দ্রীয় খাতা নেই, তাই নিয়ন্ত্রণ আর সামঞ্জস্য কঠিন।

HTTP, TLS আর socket-এর স্তরে client-server কীভাবে কথা বলে, তা Computer Networks কোর্সে; API-র নকশা System Design কোর্সের REST পর্বে।

Pipe-and-filter

Pipe-and-filter-এ কাজটি কয়েকটি স্বাধীন ধাপে ভাগ: প্রতিটি filter ইনপুট নেয়, রূপান্তর করে, আউটপুট দেয়; pipe এক filter-এর আউটপুটকে পরের ইনপুটে পৌঁছায়। Filter জানে না তার আগে বা পরে কে — শুধু ডেটার আকার মানে। গার্মেন্টস কারখানার সেলাই লাইনের মতো: কাটিং, সেলাই, বোতাম, ইস্ত্রি — প্রতিটি টেবিল নিজের কাজ জানে, আর একটি টেবিল বদলালে বাকিদের কিছু শিখতে হয় না। Unix-এর | এর বিখ্যাত রূপ; compiler-এর ধাপ, ছবি প্রক্রিয়াকরণ আর ডেটা প্রক্রিয়াকরণের ETL-ও তাই। পর্ব ৩৬-এর chain of responsibility আর middleware এর ঘনিষ্ঠ আত্মীয়।

নিচের কোডে টিকিটঘর-এর যাত্রী-মতামত থেকে সবচেয়ে বেশি আসা শব্দ বের করা হচ্ছে। Python-এর generator দিয়ে প্রতিটি filter লেখা (generator-এর বিস্তারিত Python কোর্সে), তাই ডেটা এক লাইন করে বয়ে যায়, পুরোটা memory-তে জমে না:

import re
from collections import Counter

FEEDBACK = """Bus late again! The AC bus was LATE.
Seat 7 broken; bus staff polite, bus late.
"""

def read(text):                      # source: one line at a time
    for n, line in enumerate(text.splitlines(), 1):
        print(f"  [read line {n}]")
        yield line

def clean(lines):                    # filter: lower case, letters only
    for line in lines:
        yield re.sub(r"[^a-z ]", " ", line.lower())

def tokenize(lines):                 # filter: line -> words
    for line in lines:
        yield from line.split()

def drop_stopwords(words, stop=frozenset({"the", "was", "again", "ac"})):
    for w in words:                  # an extra filter with the same shape
        if w not in stop:
            yield w

def run(source, *filters):           # the pipes: connect filters in order
    for f in filters:
        source = f(source)
    return source

words = run(read(FEEDBACK), clean, tokenize)
print("first word:", next(words))    # only line 1 has been read so far
print("count A:", Counter(words).most_common(3))
words = run(read(FEEDBACK), clean, tokenize, drop_stopwords)
print("count B:", Counter(words).most_common(3))

আউটপুট:

  [read line 1]
first word: bus
  [read line 2]
count A: [('late', 3), ('bus', 3), ('again', 1)]
  [read line 1]
  [read line 2]
count B: [('bus', 4), ('late', 3), ('seat', 1)]

তিনটি বিষয় খেয়াল করুন। (১) প্রবাহ অলস: next() একটি শব্দ চাইতেই শুধু প্রথম লাইন পড়া হলো (lazy evaluation); দ্বিতীয় লাইন পড়া হলো তখনই, যখন Counter বাকিটা চাইল। লাখ লাইনের ফাইলেও memory-তে থাকে এক লাইন। (২) stream একবারই খাওয়া যায়: count A-তে bus ৩ বার, অথচ লেখায় আছে ৪ বার — প্রথমটি next() আগেই নিয়ে গেছে। Pipe-এর ডেটা জমা থাকে না; দুবার লাগলে উৎস থেকে আবার চালাতে হয়, যেমন count B-তে। (৩) filter যোগ বা বদল এক জায়গায়: drop_stopwords একই আকারের (শব্দ ঢোকে, শব্দ বের হয়), তাই run()-এর তালিকায় একটি নাম যোগ করলেই হলো; অন্য কোনো filter বদলাতে হয়নি। এখন “again” বাদ, আর তিন নম্বরে এসেছে “seat”. দাম: প্রতিটি filter-কে একই ডেটার আকার মানতে হয় (এখানে “এক লাইন লেখা” বা “একটি শব্দ”), আর একটি ধাপের ভুলের খবর পুরো লাইনে জানানো কঠিন। আর যে কাজে পুরো ডেটা একসাথে দেখতে হয় — যেমন sort — সেই filter আর প্রবাহ ধরে রাখতে পারে না।

Microkernel বা plugin architecture

Microkernel architecture-এ একটি ছোট core থাকে, যা একটি প্রকাশিত চুক্তি (interface) আর নিবন্ধনের ব্যবস্থা (registry) দেয়; নতুন ক্ষমতা আসে plugin হিসেবে। পর্ব ২৭-এ open-closed principle-এর registry আর plugin architecture এই ধরনের class-স্তরের রূপ ছিল। বড় পরিসরে উদাহরণ: অনেক code editor ও IDE-র extension, browser-এর extension, আর টিকিটঘর-এ প্রতিটি payment পদ্ধতিকে plugin বানানো — তাহলে অংশ ২-এর modifiability scenario (“payment-এর বাইরে ০টি file”) প্রায় আপনাআপনি পাস করে।

Microkernel-এর আসল কঠিন কাজ চুক্তিটি। Plugin-এর interface একবার প্রকাশ হলে বদলানো মানে সব plugin ভাঙা — তাই version, backward compatibility (পর্ব ৩১) আর deprecation-এর নিয়ম লাগে। বাইরের লোকের plugin হলে আরও প্রশ্ন: plugin crash করলে core কি বাঁচবে? Plugin কী কী পড়তে পারবে (least privilege)? অনেক plugin-এর সম্ভাব্য জোড়া test করা যায় কীভাবে?

৫ · Event-driven architecture — broker বনাম mediator

Event-driven architecture-এ অংশগুলো একে অপরকে সরাসরি “এই কাজটি এখনই করতে হবে” বলে না; বরং ঘোষণা করে “এটা ঘটেছে” — BookingPaid, SeatReleased. যারা আগ্রহী, তারা শোনে আর নিজের কাজ করে। মাঝখানে থাকে একটি event broker (message broker বা queue). পর্ব ৩৫-এর observer pattern এক process-এর ভেতরে এই কাজটি করত; এখানে একই ধারণা process আর service-এর সীমা পেরিয়ে, সাধারণত asynchronous ভাবে (publish-subscribe). লাভ: প্রকাশক গ্রাহকদের চেনে না, তাই নতুন গ্রাহক (যেমন loyalty point) যোগ করতে booking-এর কোড বদলায় না; আর SMS ধীর হলেও booking অপেক্ষা করে না।

Mark Richards ও Neal Ford-এর Fundamentals of Software Architecture (২০২০) বইয়ে এই style-এর দুটি রূপ (topology) আলাদা করা হয়। নিচের stepper-এ একই কাজ — টিকিট বুকিং — দুই রূপে দেখুন, বিশেষ করে ভুল হলে কী হয়:

টিকিটঘর (কাল্পনিক) — event-এর খাতা
Topology
এই ধাপে
Broker-এ event
পুরো প্রবাহ কে জানে
  1. Broker topology: শুরু। Booking শুধু একটি event প্রকাশ করল। সে জানে না কে শুনবে — কোনো কেন্দ্রীয় নিয়ন্ত্রক নেই (broker topology).
  2. শিকলের দ্বিতীয় কড়া। Seat service event শুনে আসন আটকাল, আর নিজের ফল নতুন event হিসেবে প্রকাশ করল। প্রতিটি অংশ শুধু নিজের অংশটুকু জানে।
  3. Fan-out. Payment টাকা নিল; PaymentTaken একসাথে SMS আর loyalty point — দুজনেই শুনল, সমান্তরালে। নতুন গ্রাহক যোগ করতে কাউকে জানাতে হলো না — এটাই broker-এর শক্তি: কম coupling, বেশি scalability.
  4. ভুল হলে দুর্বলতা। Payment ব্যর্থ হলো। আটকানো আসন ছাড়ার দায়িত্ব কার? Seat service-কে নিজেই PaymentFailed শুনতে হবে; কেউ ভুলে গেলে আসন আটকে থাকবে, আর কোনো এক জায়গায় দেখে বলা যাবে না “এই বুকিং এখন কোন অবস্থায়।” প্রবাহ বোঝা, debug আর পরিবর্তন কঠিন।
  5. Mediator topology: একজন সমন্বয়ক। এবার শুরুর event নেয় একটি mediator (orchestrator). কোন ধাপ কোন ক্রমে, তা তার জানা: আগে আসন, তারপর পেমেন্ট। এটি পর্ব ৩৬-এর mediator pattern-এর বড় রূপ।
  6. ভুল সামলানো এক জায়গায়। Payment ব্যর্থ হতেই mediator ধাপ ১-এর compensating action চালাল — আসন ছেড়ে দিল, যাত্রীকে জানাল। প্রবাহ পরিষ্কার, কিন্তু দাম আছে: mediator নিজে একটি কেন্দ্রীয় অংশ, যা সব ধাপের সাথে যুক্ত (বেশি coupling), আর তার availability-ও এখন জরুরি।

সংক্ষেপে: সহজ, স্বাধীন প্রতিক্রিয়ার জন্য (SMS, log, point) broker; ক্রম মেনে চলা, ব্যর্থ হলে ফেরাতে হয় এমন ব্যবসায়িক প্রবাহের জন্য (বুকিং-পেমেন্ট) mediator. বাস্তবে একই system-এ দুটোই থাকে। Event-driven-এর সাধারণ দাম মনে রাখুন: “কাজ হয়ে গেছে” কথাটি দেরিতে সত্য হয় (eventual consistency), একই event দুবার আসতে পারে বলে গ্রাহককে idempotent হতে হয়, আর ক্রম নিশ্চিত নয়। Fan-out, delivery guarantee, choreography বনাম orchestration আর saga-র distributed গভীরতা System Design কোর্সের Pub/sub আর event-driven architecture পর্বে — এখানে আর পুনরাবৃত্তি করছি না।

Space-based — চরম ভিড়ের জন্য, সংক্ষেপে

ঈদের আগে টিকিট ছাড়ার মুহূর্তে হঠাৎ লাখো মানুষ একসাথে ঢুকলে একটি database-ই বোতলের গলা হয়ে দাঁড়ায়। Space-based architecture (নামটি “tuple space” ধারণা থেকে; Richards ও Ford-এর বইয়ে বিস্তারিত) এর উত্তর: অনেকগুলো processing unit, প্রতিটির memory-তে ডেটার কপি (in-memory data grid), কপিগুলোর মধ্যে সমন্বয়, আর database-এ লেখা পরে asynchronous ভাবে। ভিড় বাড়লে unit বাড়ে। দাম বড়: জটিলতা, খরচ, কপির মধ্যে অসামঞ্জস্যের ঝুঁকি, test করা কঠিন। তাই এটি ব্যতিক্রমী ভিড়ের জন্য — সাধারণ business app-এর ডিফল্ট নয়।

৬ · Monolith, modular monolith আর microservices — driver দিয়ে বাছাই

Style-এর আরেকটি অক্ষ হলো deploy-এর একক। Monolith মানে পুরো app একসাথে build ও deploy হয় — এটি নিজে খারাপ কিছু নয়। খারাপ হলো ভেতরে সীমাহীন জট (“big ball of mud”). Modular monolith-এ deploy একটিই, কিন্তু ভেতরে module-এর কঠোর সীমা: প্রতিটি module নিজের ডেটার মালিক, অন্যরা শুধু তার public interface ডাকে। Microservice-এ প্রতিটি ভাগ আলাদা process, আলাদা deploy, network-এ কথা বলে।

বিষয়Monolith (জড়ানো)Modular monolithMicroservices
Deployএকটিএকটিপ্রতিটি service আলাদা
Module-এর সীমাদুর্বল, নিয়ম লেখা নেইকোডে ও test-এ বাঁধাnetwork আর API দিয়ে বাঁধা
Module-এর মধ্যে callfunction callfunction call (interface দিয়ে)network call: ধীর, ব্যর্থ হতে পারে
আলাদা scaleনানা, পুরো app-এর কপি বাড়েহ্যাঁ
চালানোর খরচকমকমবেশি: monitoring, tracing, অনেক pipeline
কখনখুব ছোট, স্বল্পায়ু appছোট-মাঝারি দল, সীমা এখনো শিখছেঅনেক দল, স্বাধীন release ও scale জরুরি

সবচেয়ে বিপজ্জনক অবস্থা হলো distributed monolith: দেখতে আলাদা service, কিন্তু সব একসাথে বদলায় আর deploy হয় — microservice-এর খরচ, monolith-এর জট (পর্ব ২৫-এর অংশ ৮)। Service-এর সীমা, discovery, service mesh আর distributed খরচের বিস্তারিত System Design কোর্সের Monolith বনাম microservices পর্বে।

তাহলে টিকিটঘর কোনটি নেবে? নিচের stepper-এ driver থেকে সিদ্ধান্ত পর্যন্ত একটি পূর্ণ যুক্তির পথ:

সিদ্ধান্তের খাতা
এই ধাপের প্রশ্ন
প্রার্থী
ভিত্তি
  1. Driver লিখুন। প্রথমে তালিকা — তিন ধরনের driver আলাদা করে। ছয়টি style-ই এখনো খোলা। Style-এর নাম দিয়ে শুরু করলে (“আমরা microservices করব”) বাকি আলোচনা সেটিকে সমর্থনের যুক্তি খোঁজায় পরিণত হয়।
  2. Constraint দিয়ে ছাঁটাই। পাঁচজনের দল আর ছয় মাসে space-based-এর জটিলতা সামলানো বাস্তবসম্মত নয়। Microkernel আর event-driven পুরো app-এর কাঠামো হিসেবে নয়, ভেতরের অংশে কাজে লাগবে (payment plugin, SMS event). থাকল তিনটি।
  3. গুণের অগ্রাধিকার। Stakeholder-দের সাথে বসে ওজন: ঈদের ভিড়ে সচল থাকা সবচেয়ে জরুরি (৫), payment বদলানো আর একজন ops দিয়ে চালানো (৪ করে), আলাদা module-কে আলাদা scale (২)।
  4. Scenario দিয়ে যাচাই। অংশ ২-এর scenario-গুলো প্রতিটি প্রার্থীর ওপর চালানো হলো। Peak-এর জন্য পুরো app-এর কয়েকটি কপি load balancer-এর পেছনে রাখাই যথেষ্ট — microservices জরুরি নয়। কিন্তু layered monolith-এ payment-এর কোড চার স্তরে ছড়ানো, তাই “payment-এর বাইরে ০টি file” টেকে না।
  5. Weighted matrix আর sensitivity. নিচের কোডে হিসাব: ৫১ বনাম ৫০ বনাম ৪০। ব্যবধান এত কম যে এটি কার্যত সমান; sensitivity দেখায় “সহজ চালানো”-র ওজন ৩ হলে বা “আলাদা scale”-এর ওজন ৩ হলে microservices জেতে। অর্থাৎ আসল প্রশ্ন সংখ্যা নয় — “একজন ops দিয়ে কি দশটি service চালানো যাবে?”
  6. সিদ্ধান্ত, লেখা আর রক্ষা। Modular monolith বেছে নেওয়া হলো, কারণসহ ADR-এ (অংশ ৮), module-এর সীমা CI-র test-এ (অংশ ৯), আর লেখা থাকল কখন আবার ভাবতে হবে: কোনো module-এর স্বাধীন scale বা release দরকার হলে। পরে notify module সত্যিই আলাদা হলো — ADR 0002.
// Weighted trade-off matrix: 3 candidate styles for a fictional bus-ticket app.
const attrs = [                      // [quality attribute, weight 1..5]
  ["peak availability", 5], ["modifiability", 4],
  ["operational simplicity", 4], ["independent scaling", 2]
];
const styles = {                     // score 1..5 per attribute, same order
  "layered monolith":  [2, 2, 5, 1],
  "modular monolith":  [3, 4, 4, 2],
  "microservices":     [4, 4, 1, 5]
};
function rank(weights) {
  return Object.entries(styles)
    .map(([name, s]) => [name, s.reduce((t, x, i) => t + x * weights[i], 0)])
    .sort((a, b) => b[1] - a[1]);
}
const base = attrs.map(a => a[1]);
const max = base.reduce((t, w) => t + 5 * w, 0);
rank(base).forEach(([n, t], i) => console.log(`${i + 1}. ${n.padEnd(17)} ${t} / ${max}`));
console.log("sensitivity (change one weight, others fixed):");
attrs.forEach(([name], i) => {
  const winners = [];
  for (let w = 1; w <= 5; w++) {
    const ws = base.slice(); ws[i] = w;
    const r = rank(ws);
    winners.push(w + ":" + (r[0][1] === r[1][1] ? "tie" : r[0][0].split(" ")[0]));
  }
  console.log(`  ${name.padEnd(23)} ${winners.join("  ")}`);
});

আউটপুট:

1. modular monolith  51 / 75
2. microservices     50 / 75
3. layered monolith  40 / 75
sensitivity (change one weight, others fixed):
  peak availability       1:modular  2:modular  3:modular  4:modular  5:modular
  modifiability           1:modular  2:modular  3:modular  4:modular  5:modular
  operational simplicity  1:microservices  2:microservices  3:microservices  4:modular  5:modular
  independent scaling     1:modular  2:modular  3:microservices  4:microservices  5:microservices

হাতে মিলিয়ে নিন: modular monolith = ৩×৫ + ৪×৪ + ৪×৪ + ২×২ = ১৫ + ১৬ + ১৬ + ৪ = ৫১; microservices = ২০ + ১৬ + ৪ + ১০ = ৫০; layered = ১০ + ৮ + ২০ + ২ = ৪০; সর্বোচ্চ সম্ভব ৫ × (৫+৪+৪+২) = ৭৫. Sensitivity-র লাইনগুলোই আসল শিক্ষা। “সহজ চালানো”-তে modular monolith প্রতি ওজন-এককে ৩ নম্বর এগিয়ে (৪ বনাম ১), তাই ওজন ৪ থেকে ৩-এ নামলেই সে ৩ হারায় আর microservices ১ হারায় — ৪৭ বনাম ৪৯, বিজয়ী বদল। একইভাবে “আলাদা scale”-এর ওজন ২ থেকে ৩ হলেই উল্টে যায়। অর্থাৎ এই দুটি ওজন এই সিদ্ধান্তের sensitivity point (অংশ ৯-এ ATAM-এর ভাষায়)। Availability-র ওজন ১ থেকে ৫ যা-ই হোক, ফল একই — সেখানে তর্ক করে সময় নষ্ট করার দরকার নেই। মনে রাখুন, নম্বরগুলো মানুষের বিচার; matrix সেই বিচারকে প্রকাশ্য আর আলোচনাযোগ্য করে, নিজে সত্য তৈরি করে না (weighted scoring-এর একই সীমা পর্ব ২৪-এ দেখেছেন)।

৭ · Architecture-এর ছবি — 4+1 view আর C4 model

একটি বাড়ির একটি নকশায় সব ধরে না: স্থপতির floor plan, বৈদ্যুতিক লাইনের নকশা, পানির পাইপের নকশা, আর বাইরে থেকে দেখতে কেমন — চারটি আলাদা কাগজ, চারজন আলাদা পাঠক। Software-এও একই কথা। প্রতিটি দৃষ্টিকোণ থেকে আঁকা বর্ণনাকে বলে architectural view.

Kruchten-এর 4+1 view model (১৯৯৫)

Philippe Kruchten ১৯৯৫ সালে IEEE Software-এ “The 4+1 View Model of Architecture” প্রবন্ধে চারটি view আর সেগুলোকে বাঁধা পঞ্চম একটি view প্রস্তাব করেন — 4+1 view model. পরে এটি Rational Unified Process-এর অংশ হয়ে বহুল পরিচিত হয়।

Viewকী দেখায়প্রধান পাঠকপ্রচলিত ছবি
LogicalSystem কী কী দায়িত্ব পালন করে — মূল ধারণা আর তাদের সম্পর্কবিশ্লেষক, ব্যবহারকারী-পক্ষclass diagram (পর্ব ২৯)
Processচলার সময়: process, thread, concurrency, কে কাকে কখন বার্তা পাঠায়integrator, performance-এর দায়িত্বে যাঁরাsequence, activity diagram (পর্ব ৩০)
Developmentকোডের সংগঠন: package, module, layer, কোন দল কোন অংশের মালিকprogrammerpackage বা component diagram
Physicalকোন software কোন যন্ত্রে বা node-এ চলে, network-এ কীভাবে যুক্তsystem engineer, opsdeployment diagram
+1 Scenariosকয়েকটি গুরুত্বপূর্ণ use case, যা চারটি view-কে জুড়ে দেখায় ও যাচাই করেসবাইuse case, sequence (পর্ব ২২)

C4 model — মানচিত্রের মতো zoom

Simon Brown-এর প্রস্তাবিত C4 model ২০১০-এর দশকে বিশেষ জনপ্রিয় হয়েছে; এর বর্ণনা তাঁর লেখা ও c4model.com-এ। চিন্তাটি Google Maps-এর মতো — দেশ, শহর, রাস্তা, বাড়ি:

  1. Context: পুরো system একটি বাক্স; চারপাশে মানুষ (ব্যবহারকারী) আর বাইরের system. প্রযুক্তির নাম প্রায় থাকেই না — ব্যবসার লোকও পড়তে পারেন।
  2. Container: System-এর ভেতরের আলাদাভাবে চালানো বা deploy করা যায় এমন একক — web app, mobile app, API, database, queue. (এখানে “container” মানে Docker container নয়, যদিও প্রায়ই মিলে যায়।)
  3. Component: একটি container-এর ভেতরের বড় অংশগুলো — যেমন API-র ভেতরে booking, payment, fare module.
  4. Code: class diagram-এর স্তর; প্রায়ই আঁকা হয় না, বা tool দিয়ে কোড থেকে তৈরি হয়।

C4 context diagram — টিকিটঘর (কাল্পনিক)

স্তর ১: system একটি বাক্স, চারপাশে মানুষ আর বাইরের system

টিকিটঘর-এর C4 context diagram যাত্রী [Person] কাউন্টার কর্মী [Person] টিকিটঘর [Software System] খোঁজা, বুকিং, পেমেন্ট, টিকিট টিকিট খোঁজে ও কেনে কাউন্টারে বিক্রি করে Payment gateway [External] SMS gateway [External] বাস অপারেটরের সময়সূচি [External] পেমেন্ট নেয় SMS পাঠায় সময়সূচি পড়ে এই system ও তার মানুষ বাইরের system
ছবি ৩৮.২ — Context স্তরে প্রশ্ন শুধু “কে ব্যবহার করে, কার সাথে কথা বলে”। প্রতিটি তীরে একটি ক্রিয়া লেখা থাকে, আর প্রতিটি বাক্সে তার ধরন ([Person], [Software System], [External]). পরের zoom — container স্তরে — টিকিটঘর-এর বাক্সটি খুলে mobile app, web app, API আর database দেখা যেত।

ছবির সাধারণ ভুল: লেবেলহীন তীর (কে কাকে কী দেয়?), একই ছবিতে দুই স্তর মেশানো (context-এ হঠাৎ একটি class), আর “বুঝিয়ে বলার” ছবিতে ব্যাখ্যা (legend) না থাকা। আর ছবি যত সুন্দরই হোক, কোড বদলালে পুরোনো হয়ে যায় — তাই অনেক দল diagram-as-code (PlantUML, Mermaid বা Structurizr-এর মতো tool) দিয়ে ছবিকেও Git-এ রাখে।

৮ · ADR — সিদ্ধান্ত লিখে রাখা, Git-এ রাখা

দুই বছর পর একজন নতুন developer জিজ্ঞেস করবেন: “এত বড় app, অথচ microservices নয় কেন?” যিনি সিদ্ধান্ত নিয়েছিলেন, তিনি হয়তো চলে গেছেন। Wiki-র ৮০ পাতার architecture document কেউ হালনাগাদ করেনি। এই সমস্যার হালকা সমাধান Architecture Decision Record (ADR) — প্রতিটি গুরুত্বপূর্ণ সিদ্ধান্তের জন্য এক-দুই পাতার একটি ফাইল। Michael Nygard ২০১১ সালের নভেম্বরে “Documenting Architecture Decisions” নামের একটি blog লেখায় যে ছাঁচ দেন, তাতে পাঁচটি অংশ: title, status, context, decision, consequences. পরে MADR-এর মতো আরও বিস্তারিত ছাঁচ এসেছে (বিকল্পগুলোর তালিকাসহ), কিন্তু মূল চিন্তা একই।

docs/adr/0001-use-a-modular-monolith.mdTitle
# 1. Use a modular monolith

Date: 2026-01-15
docs/adr/0001-…mdStatus
## Status

Accepted
docs/adr/0001-…mdContext
## Context

Ticketghor (fictional) sells inter-district bus tickets. Five developers,
one operations person, launch before Eid in six months. Traffic is about
20 times normal in the two weeks before Eid. Booking, payment, fares and
notifications change at different speeds.
docs/adr/0001-…mdDecision
## Decision

We will build one deployable application split into modules
(booking, payment, fares, notify). A module may call another only through
its public interface. No module reads another module's tables.
An automated architecture test in CI enforces these rules.
docs/adr/0001-…mdConsequences
## Consequences

- One deployment, one database, simple operations for a small team.
- Peak load is handled by running more copies of the whole application;
  we cannot scale one module alone.
- If a module later needs independent scaling or release, its clean
  boundary makes extracting it as a service possible (new ADR needed).
TerminalGit-এ রাখা আর পরে সংশোধন
$ git add docs/adr/0001-use-a-modular-monolith.md
$ git commit -m "docs(adr): 0001 use a modular monolith"
…
# তিন মাস পরে: নতুন ADR, পুরোনোটির শুধু status বদল
-Accepted
+Accepted. Amended by ADR 0002.
  1. Title — নম্বর আর একটি ক্রিয়াবাক্য। “Use a modular monolith” — সিদ্ধান্তটিই শিরোনাম, “Architecture discussion” নয়। নম্বর কখনো আবার ব্যবহার হয় না, আর ফাইলের নামেও থাকে (0001-…), যাতে ক্রমে সাজানো যায়। তারিখ পরে প্রেক্ষাপট বুঝতে সাহায্য করে।
  2. Status — সিদ্ধান্তটি এখন কোথায়। সাধারণত Proposed, Accepted, Deprecated বা Superseded. Proposed অবস্থায় ADR একটি pull request হিসেবে আলোচনায় যেতে পারে (পর্ব ১৬); merge হওয়া মানে Accepted.
  3. Context — কোন বাস্তবতায়। সিদ্ধান্তের সময়ের সত্যগুলো, নিরপেক্ষ ভাষায়: দলের আকার, সময়, ভিড়ের অনুমান, কোন অংশ কত দ্রুত বদলায়। পাঁচ বছর পর context পড়েই বোঝা যাবে, সিদ্ধান্তটি তখন কেন যুক্তিসংগত ছিল — আর এখন সেই শর্ত বদলেছে কি না।
  4. Decision — সক্রিয় কণ্ঠে, স্পষ্ট নিয়ম। “We will …”. শুধু style-এর নাম নয়, মানার মতো নিয়ম: অন্য module-এর table পড়া নিষেধ, শুধু public interface দিয়ে call. শেষ বাক্যটি খেয়াল করুন — নিয়মটি test-এ বাঁধা হবে (অংশ ৯)।
  5. Consequences — ভালো, খারাপ, দুটোই। যে ADR-এ শুধু সুবিধা লেখা, সেটি বিজ্ঞাপন, সিদ্ধান্তের রেকর্ড নয়। এখানে দাম স্পষ্ট: একটি module আলাদা scale করা যাবে না। আর কখন আবার ভাবতে হবে, তাও লেখা।
  6. Git-এ রাখা আর পরিবর্তন। ADR কোডের পাশে, একই repository-তে — docs as code. Commit message-এ Conventional Commits-এর docs(adr): (পর্ব ১৮)। সিদ্ধান্ত বদলালে পুরোনো ADR মুছে বা নতুন করে লেখা হয় না; নতুন ADR আসে, আর পুরোনোটির শুধু status বদলায় — history অক্ষত থাকে।

পুরো ADR ফাইলটি একসাথে (ইংরেজিতে লেখা, কারণ অনেক দলে কোডের দলিল ইংরেজিতে থাকে; বাংলায় লিখলেও কাঠামো একই):

# 1. Use a modular monolith

Date: 2026-01-15

## Status

Accepted

## Context

Ticketghor (fictional) sells inter-district bus tickets. Five developers,
one operations person, launch before Eid in six months. Traffic is about
20 times normal in the two weeks before Eid. Booking, payment, fares and
notifications change at different speeds.

## Decision

We will build one deployable application split into modules
(booking, payment, fares, notify). A module may call another only through
its public interface. No module reads another module's tables.
An automated architecture test in CI enforces these rules.

## Consequences

- One deployment, one database, simple operations for a small team.
- Peak load is handled by running more copies of the whole application;
  we cannot scale one module alone.
- If a module later needs independent scaling or release, its clean
  boundary makes extracting it as a service possible (new ADR needed).

এবার একটি scratch repository-তে আসল Git-এ (Git ২.৫৩-এ, লেখক Rahim, নির্দিষ্ট তারিখে) দুটি ADR যোগ করা হলো। প্রথম commit-টি project শুরুর (README.md), সেটি বাদ দিয়ে দেখানো হচ্ছে। Repository শুরু আর প্রথম commit-এর পূর্ণ ধাপ পর্ব ৮-এ:

$ git add docs/adr/0001-use-a-modular-monolith.md
$ git commit -m "docs(adr): 0001 use a modular monolith"
[main c6435dc] docs(adr): 0001 use a modular monolith
 1 file changed, 29 insertions(+)
 create mode 100644 docs/adr/0001-use-a-modular-monolith.md
# (three months later: 0002 written, 0001's status line edited)
$ git add docs/adr
$ git commit -m "docs(adr): 0002 extract notify as a service"
[main e9ed096] docs(adr): 0002 extract notify as a service
 2 files changed, 22 insertions(+), 1 deletion(-)
 create mode 100644 docs/adr/0002-extract-notify-as-a-service.md
$ git log --oneline -- docs/adr
e9ed096 docs(adr): 0002 extract notify as a service
c6435dc docs(adr): 0001 use a modular monolith
$ git diff HEAD~1 -- docs/adr/0001-use-a-modular-monolith.md
…
@@ -4,7 +4,7 @@ Date: 2026-01-15

 ## Status

-Accepted
+Accepted. Amended by ADR 0002.

 ## Context
 

এখানে Git ঠিক সেই কাজটি করছে, যা wiki পারে না: git log -- docs/adr সিদ্ধান্তের পূর্ণ ইতিহাস দেখায়, কে কখন বদলেছে (পর্ব ৯-এর log আর diff). ADR 0001-এর diff-এ মাত্র একটি লাইন বদলেছে — status; প্রেক্ষাপট আর যুক্তি অক্ষত। আর সিদ্ধান্তটি যে commit-এ এসেছে, সেই একই commit বা PR-এ কোডের বদলও থাকতে পারে — তখন review-এর সময় reviewer কারণ আর কোড পাশাপাশি দেখেন।

পরীক্ষা ও ইন্টারভিউতে: (১) Architecture আর design-এর পার্থক্য? — পরিসর আর বদলের দাম; architecture অনেক অংশের গুণকে প্রভাবিত করা, কঠিন-বদল সিদ্ধান্ত। (২) Quality attribute scenario-র ছয় অংশ? — source, stimulus, artifact, environment, response, response measure. (৩) Strict আর relaxed layering? — strict-এ শুধু ঠিক নিচের স্তর, relaxed-এ নিচের যেকোনো স্তর; ওপরের দিকে কোনোটিতেই নয়। (৪) Event-driven-এ broker বনাম mediator? — broker-এ কেন্দ্রীয় নিয়ন্ত্রক নেই, mediator ক্রম আর ভুল-সামলানো চালায়। (৫) ADR-এ কী থাকে? — Nygard-এর ছাঁচে title, status, context, decision, consequences.

৯ · Architecture যাচাই আর রক্ষা — ATAM, fitness function আর Conway's law

ATAM — সংক্ষেপে

বড় সিদ্ধান্তের আগে architecture-কে আনুষ্ঠানিকভাবে যাচাই করার একটি পরিচিত পদ্ধতি SEI-র ATAM (Architecture Tradeoff Analysis Method), যা ২০০০ সালের দিকে Kazman, Klein ও Clements-এর কাজে প্রকাশিত। মোটা দাগে: ব্যবসার লক্ষ্য আর architecture উপস্থাপন, quality attribute থেকে শুরু করে পাতায় অগ্রাধিকারসহ scenario — একটি utility tree — বানানো, তারপর গুরুত্বপূর্ণ scenario ধরে architecture-এর পদ্ধতিগুলো বিশ্লেষণ। ফলাফল চার ধরনের: ঝুঁকি (risk), ঝুঁকি-নয় (non-risk), sensitivity point আর trade-off point. অংশ ৬-এর কোডে “সহজ চালানো”-র ওজন ছিল একটি sensitivity point; আর mediator — যে প্রবাহ পরিষ্কার করে কিন্তু coupling বাড়ায় — একটি trade-off point. পূর্ণ ATAM কয়েক দিনের, অনেক stakeholder-এর অনুষ্ঠান; ছোট দলে এর হালকা রূপ — এক বিকেলে scenario ধরে প্রস্তাব যাচাই — প্রায়ই যথেষ্ট।

Fitness function — architecture-কে test-এ বাঁধা

ADR-এ “কোনো module অন্যের table পড়বে না” লেখা হলো। ছয় মাস পর, এক ব্যস্ত রাতে, কেউ admin page থেকে সরাসরি database ডেকে ফেললেন — “শুধু এবারই”। এভাবে ছোট ছোট শর্টকাটে কোড পরিকল্পনা থেকে সরে যাওয়াকে বলে architecture erosion; technical debt-এর একটি বড় উৎস (পর্ব ৪৩)। প্রতিরোধ: নিয়মটিকে যন্ত্রে যাচাইযোগ্য করা। Neal Ford, Rebecca Parsons ও Patrick Kua-র Building Evolutionary Architectures (২০১৭; পরের সংস্করণে Pramod Sadalage-ও যুক্ত) বইয়ে এর নাম architectural fitness function — architecture-এর কোনো গুণের বস্তুনিষ্ঠ, সম্ভব হলে স্বয়ংক্রিয়, মাপ। উদাহরণ: নিষিদ্ধ import খোঁজার test, load test-এ p95 latency-র সীমা, cyclomatic complexity-র সীমা (পর্ব ৫০)। নিচে প্রথমটি — Python-এর নিজের ast parser দিয়ে import পড়া, আর unittest-এ নিয়ম:

import ast, unittest

SOURCES = {                          # fictional bus-ticket app: file -> source
    "ui/booking_page.py": "from app.booking import book\n",
    "ui/admin_page.py":   "import db\nfrom app.reports import daily\n",
    "app/booking.py":     "from domain.seat import Seat\nfrom data.tickets import save\n",
    "app/reports.py":     "from data.tickets import all_tickets\n",
    "domain/seat.py":     "from dataclasses import dataclass\n",
    "data/tickets.py":    "import db\n",
}

def top_imports(src):                # {"db", "app", ...} read with Python's own parser
    names = set()
    for node in ast.walk(ast.parse(src)):
        if isinstance(node, ast.Import):
            names |= {a.name.split(".")[0] for a in node.names}
        elif isinstance(node, ast.ImportFrom) and node.module:
            names.add(node.module.split(".")[0])
    return names

class ArchitectureFitness(unittest.TestCase):
    def test_only_data_layer_imports_db(self):
        bad = [f for f, s in SOURCES.items()
               if "db" in top_imports(s) and not f.startswith("data/")]
        if bad: self.fail("db imported outside data/: " + ", ".join(bad))
    def test_domain_imports_no_outer_layer(self):
        bad = [f for f, s in SOURCES.items() if f.startswith("domain/")
               and top_imports(s) & {"ui", "app", "data", "db"}]
        self.assertEqual(bad, [])

result = unittest.TestResult()
unittest.defaultTestLoader.loadTestsFromTestCase(ArchitectureFitness).run(result)
for test, tb in result.failures:
    print("FAIL", test._testMethodName, "->", tb.strip().splitlines()[-1])
print(f"ran {result.testsRun}, failed {len(result.failures)}")

আউটপুট:

FAIL test_only_data_layer_imports_db -> AssertionError: db imported outside data/: ui/admin_page.py
ran 2, failed 1

দুটি নিয়মের একটি ভেঙেছে, আর test ঠিক কোন file-এ, তা নাম ধরে বলছে: ui/admin_page.py সরাসরি db import করেছে। data/tickets.py-এর একই import বৈধ, কারণ নিয়মটাই বলে “শুধু data স্তর”। Domain-এর নিয়মটি পাস — dataclasses standard library, কোনো বাইরের স্তর নয়। লক্ষ করুন, import খোঁজা হচ্ছে লেখা মিলিয়ে নয়, Python-এর নিজের ast দিয়ে — তাই comment বা string-এর ভেতরে “import db” লেখা থাকলে ভুল সংকেত আসবে না। এখানে কোড একটি dictionary-তে রাখা হয়েছে যাতে পাতায় চালানো যায়; বাস্তবে আপনি repository-র .py file-গুলো পড়বেন, আর test-টি CI-তে প্রতিটি PR-এ চলবে (পর্ব ৫৮)। ব্যর্থ test-টি পাতায় ইচ্ছা করে দেখানো, তাই ফল নিজে ছাপানো হয়েছে, program error দিয়ে থামেনি। তৈরি tool-ও আছে — যেমন Java-য় ArchUnit, Python-এ import-linter, JavaScript-এ dependency-cruiser — কিন্তু ধারণাটি এই ৩৫ লাইনেই।

Conway's law — দলের কাঠামোও architecture

Melvin Conway-এর ১৯৬৮ সালের পর্যবেক্ষণ — Conway's law — পর্ব ১ আর পর্ব ২৫-এ এসেছে: system-এর কাঠামো তার নির্মাতা প্রতিষ্ঠানের যোগাযোগের কাঠামোর প্রতিচ্ছবি হয়। Architecture-এর দিক থেকে এর অর্থ: দশটি microservice-এর পরিকল্পনা পাঁচজনের একটি দলে টেকসই হওয়া কঠিন, কারণ সবাই সব service-এ হাত দেবেন আর সীমাগুলো ঝাপসা হবে। উল্টো দিক থেকে ব্যবহার — আগে কাঙ্ক্ষিত কাঠামো ঠিক করে সেই অনুযায়ী দল সাজানো — কে বলে inverse Conway maneuver. তাই টিকিটঘর-এর ADR-এর context-এ দলের আকার লেখা ছিল: সেটি কোনো পার্শ্বতথ্য নয়, একটি driver.

আরও গভীরে: Architecture বর্ণনার আন্তর্জাতিক মানদণ্ড ISO/IEC/IEEE 42010 (২০১১; ২০২২ সালে হালনাগাদ) — stakeholder, concern, viewpoint আর view-এর ভাষা দেয়, কোনো নির্দিষ্ট ছবি বাধ্যতামূলক করে না। ক্লাসিক পাঠ্য Bass, Clements ও Kazman-এর Software Architecture in Practice (প্রথম সংস্করণ ১৯৯৮, চতুর্থ ২০২১) আর একই দলের Documenting Software Architectures: Views and Beyond. হালকা documentation কাঠামো হিসেবে arc42 template-ও বহু দলে প্রচলিত। Style-গুলোর আধুনিক তুলনা পাবেন Richards ও Ford-এর Fundamentals of Software Architecture (২০২০) বইয়ে।

১০ · AI-এর যুগে software architecture

AI-কে “আমার app-এর architecture বলে দাও” লিখে চাইলে কয়েক সেকেন্ডে একটি চকচকে উত্তর আসে — প্রায়ই দশটি service, একটি message broker, Kubernetes আর একটি সুন্দর ছবি। সমস্যা হলো, AI আপনার দলের আকার, বাজেট, ঈদের ভিড় বা একজন ops-এর কথা জানে না, যদি না আপনি বলেন; আর বললেও তার ঝোঁক থাকে জনপ্রিয় উত্তরের দিকে। Architecture-এ AI খুব ভালো প্রতিপক্ষ — বিকল্প খোঁজা, আপনার ADR-এর দুর্বলতা ধরা, scenario-র খসড়া — কিন্তু সিদ্ধান্তদাতা নয়।

AI প্রায়ই যা করেকেন বিপজ্জনককীভাবে যাচাই
প্রশ্ন না করেই microservices বা event-driven প্রস্তাবছোট দলের ওপর distributed system-এর পুরো খরচনিজের driver আর constraint আগে দিন; “কোন শর্তে এটি ভুল পছন্দ?” জিজ্ঞেস করুন
ADR-এ শুধু সুবিধা, consequence-এ কোনো দাম নেইপরে দাম এসে ধাক্কা দেয়, কেউ প্রস্তুত থাকে নাপ্রতিটি সিদ্ধান্তে অন্তত দুটি নেতিবাচক পরিণাম দাবি করুন
ভুল দিকের তীর, স্তর মেশানো C4 বা UML ছবিবিশ্বাসযোগ্য দেখায়, কিন্তু ভুল বোঝায়প্রতিটি তীর কোডের একটি আসল import বা call-এর সাথে মেলান
মানদণ্ড বা বই নিয়ে বানানো দাবি (“ISO 42010 ADR বাধ্যতামূলক করে”)ভুল তথ্য দলিলে ঢুকে যায়মূল উৎস বা সরকারি বিবরণ পড়ুন; সাল আর সংস্করণ মেলান
বিপজ্জনক Git পরামর্শ — যেমন পুরোনো ADR “পরিষ্কার” করতে reset --hard বা push --forceদলের history আর অন্যের কাজ হারায়ADR কখনো মুছবেন না, status বদলান; যেকোনো command আগে একটি throwaway clone-এ চালান
Prompt-এ আসল config, server-এর ঠিকানা বা key পেস্ট হয়ে যাওয়াগোপন তথ্য ফাঁসপাঠানোর আগে মুছে কাল্পনিক নাম দিন

AI-কে সিদ্ধান্তের সমালোচক হিসেবে ব্যবহারের একটি কাঠামো। শেষ দুটি শর্ত AI-কে আপনার পছন্দকেই সমর্থন করার ফাঁদ থেকে বাঁচায়:

আমি একটি কাল্পনিক বাসের টিকিট app-এর architecture ঠিক করছি। তথ্য (গোপন কিছু নেই): - দল: ৫ developer, ১ ops; সময় ৬ মাস; বাজেট ছোট - quality attribute (অগ্রাধিকারসহ): ঈদের আগে স্বাভাবিকের প্রায় ২০ গুণ ভিড়ে সচল থাকা (৫), নতুন payment পদ্ধতি যোগের সহজতা (৪), কম লোকে চালানো (৪), আলাদা module-এর আলাদা scale (২) - আমার খসড়া সিদ্ধান্ত: modular monolith (নিচে ADR পেস্ট করছি) [এখানে ADR পেস্ট করুন] ১. এই driver-গুলোর জন্য অন্তত তিনটি বিকল্প style তুলনা করো, প্রতিটির জন্য কোন quality attribute scenario-তে সেটি ব্যর্থ হতে পারে বলো। ২. আমার ADR-এর consequences অংশে কোন নেতিবাচক পরিণাম বাদ পড়েছে, তা তালিকা করো। ৩. কোন শর্ত বদলালে আমার সিদ্ধান্ত ভুল হয়ে যাবে, তা স্পষ্ট করে লেখো। ৪. কোনো বই, মানদণ্ড বা tool-এর নাম দিলে সাল আর উৎস দাও; নিশ্চিত না হলে "নিশ্চিত নই" লেখো। ৫. আমার পছন্দ সমর্থন করার দরকার নেই — দুর্বলতা খোঁজাই তোমার কাজ।

AI-এর architecture প্রস্তাব হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১Architecture যদি “বদলানো কঠিন সিদ্ধান্ত” হয়, তাহলে কি সব বড় সিদ্ধান্ত project-এর শুরুতেই নিয়ে ফেলা উচিত?

শুরুতে সব সিদ্ধান্ত নেওয়ার চেষ্টা — প্রায়ই “big design up front” বলা হয় — একটি সমস্যায় পড়ে: শুরুতেই আপনি সবচেয়ে কম জানেন। টিকিটঘর-এর দল জানুয়ারিতে জানত না যে ঈদের ভিড়ে আসল বোতলের গলা হবে SMS, booking নয়। শুরুতে দশটি service বানালে তারা ভুল সীমায় ভাগ করত, আর ভুল সীমার service ফেরানো monolith-এর ভেতরের module ফেরানোর চেয়ে অনেক কঠিন।

উল্টো চরম — “architecture নিজে থেকেই গড়ে উঠবে” — ও বিপজ্জনক। Database-এর ধরন, client কতটা বিশ্বাসযোগ্য, ডেটা কোথায় থাকবে, এমন কিছু সিদ্ধান্ত না নিলে কোডই লেখা যায় না, আর পরে বদলাতে বড় দাম। Lean software development-এর আলোচনায় (Mary ও Tom Poppendieck-এর লেখায় জনপ্রিয়) একটি কাজের ধারণা আছে: last responsible moment — সিদ্ধান্ত নিন তখন, যখন আরও দেরি করলে একটি গুরুত্বপূর্ণ বিকল্প হারিয়ে যাবে; তার আগে নয়, পরেও নয়।

দ্বিতীয় কৌশলটি আরও শক্তিশালী: কঠিন-বদল সিদ্ধান্তের সংখ্যাই কমানো। Fowler-এর ২০০৩-এর প্রবন্ধে এই চিন্তা আছে — architect-এর একটি বড় কাজ হলো অপরিবর্তনীয়তা কমানো। টিকিটঘর modular monolith নিয়েছিল ঠিক এই কারণে: module-এর পরিষ্কার সীমা থাকায় “কোনটি আলাদা service হবে” প্রশ্নটি পরে, তথ্য হাতে নিয়ে, তুলনামূলক কম দামে নেওয়া গেছে (ADR 0002). একইভাবে interface-এর পেছনে database রাখা, feature flag, আর event দিয়ে অংশ আলাদা রাখা — সবই সিদ্ধান্তকে কম অপরিবর্তনীয় করার উপায়।

আর যে সিদ্ধান্ত নিতেই হচ্ছে, তার context আর “কখন আবার ভাবব” ADR-এ লিখে রাখলে পরের দল জানে কোন শর্ত বদলালে ফেরার সময় এসেছে।

মূল উপলব্ধি: ভালো architect সব সিদ্ধান্ত আগে নেন না; তিনি জানেন কোনটি এখনই নিতে হবে, কোনটি পিছিয়ে রাখা যায়, আর কীভাবে আজকের সিদ্ধান্তকে কালকে বদলানো সস্তা করা যায়।

প্র ০২২০১০-এর দশকে microservices ছিল প্রায় ফ্যাশন; পরে অনেকে modular monolith-এর কথা বলছেন কেন?

Microservices-এর প্রতিশ্রুতি বাস্তব: স্বাধীন দল, স্বাধীন release, যে অংশে ভিড় শুধু তার scale, এক অংশের ব্যর্থতা অন্যকে কম ছোঁয়া। বড় কিছু প্রতিষ্ঠান প্রকাশ্যে লিখেছে, শত শত দলের কাজ এভাবে সমান্তরালে চলে। কিন্তু এর দামও বাস্তব, আর সেটা প্রায়ই প্রথম দিনেই দিতে হয়: প্রতিটি function call এখন network call — ধীর, ব্যর্থ হতে পারে, timeout আর retry লাগে; একটি transaction কয়েকটি database-এ ছড়ায়; একটি অনুরোধের পথ খুঁজতে distributed tracing লাগে; দশটি pipeline, দশটি deploy, দশটি monitoring dashboard.

Martin Fowler ২০১৫ সালে “MonolithFirst” লেখায় মত দেন যে সফল microservice-এর অনেক গল্প শুরু হয়েছে monolith থেকে, যা বড় হয়ে ভাগ হয়েছে; আর শুরু থেকে microservice বানানো অনেক project সমস্যায় পড়েছে — এটি তাঁর পর্যবেক্ষণ, পরিমাপিত পরিসংখ্যান নয়, এবং কেউ কেউ দ্বিমতও করেছেন। কিছু প্রতিষ্ঠান প্রকাশ্যে উল্টো পথে যাওয়ার কথাও লিখেছে; যেমন ২০২৩ সালে Amazon Prime Video-র একটি প্রযুক্তি-blog জানায়, একটি নির্দিষ্ট monitoring tool-কে বহু distributed অংশ থেকে এক process-এ এনে তাদের খরচ অনেক কমেছে। লক্ষ করুন — এটি একটি tool-এর গল্প, পুরো প্রতিষ্ঠানের architecture-এর রায় নয়।

Modular monolith দুই জগতের মাঝের জায়গা নেয়: সীমা টানার শৃঙ্খলা (প্রতিটি module নিজের ডেটার মালিক, public interface, fitness function দিয়ে রক্ষা) এখনই, কিন্তু network-এর খরচ পরে, যখন সত্যিই দরকার। সীমা ভুল হলে ঠিক করা মানে একটি refactoring, সিস্টেম-জোড়া migration নয়। আর Conway's law মনে করায় — ছোট দলের জন্য অনেক service মানে প্রত্যেকে সব service-এ, সীমা ঝাপসা, distributed monolith-এর ঝুঁকি।

মূল উপলব্ধি: প্রশ্ন “microservices ভালো না খারাপ” নয়; প্রশ্ন হলো আপনার driver-এর কোনটি — স্বাধীন release, আলাদা scale, অনেক দল — এর দাম দেওয়ার মতো যথেষ্ট জরুরি। ফ্যাশন driver নয়।

প্র ০৩Layered architecture-এর ইতিহাস ১৯৬৮ পর্যন্ত — তাহলে আজ অনেকে এর সমালোচনা করেন কেন?

১৯৬৮ সালে Edsger Dijkstra Communications of the ACM-এ “The structure of the ‘THE’-multiprogramming system” প্রবন্ধে তাঁর দলের operating system-এর বর্ণনা দেন: কয়েকটি স্তর, নিচে processor ভাগ করা, তার ওপরে memory, তারপর operator-এর console, input-output, আর সবার ওপরে ব্যবহারকারীর program. প্রতিটি স্তর শুধু নিচেরটির ওপর নির্ভর করত, তাই প্রতিটিকে আলাদাভাবে যাচাই করা যেত — Dijkstra-র কাছে এটাই ছিল মূল লাভ। পরে network-এর OSI model আর TCP/IP stack একই চিন্তার সবচেয়ে সফল উদাহরণ হয়ে ওঠে: Wi-Fi থেকে fiber-এ গেলে web browser বদলাতে হয় না।

Business app-এ এসে layered কয়েকটি বাস্তব সমস্যায় পড়ে। প্রথমত, বদল সাধারণত স্তর ধরে নয়, feature ধরে আসে: “আসনের সাথে জানালার পাশ কি না” যোগ করতে screen, service, domain, database — চারটিই বদলায়। দ্বিতীয়ত, প্রচলিত layered-এ domain নিচের data স্তরের ওপর নির্ভর করে — তাই ব্যবসার নিয়ম database-এর আকারে বাঁধা পড়ে, আর database ছাড়া domain test করা কঠিন। তৃতীয়ত, sinkhole: স্তরগুলো যখন শুধু পার করে দেয়, তখন খরচ বাড়ে, লাভ আসে না।

উত্তরগুলো layered-কে বাতিল করেনি, বদলেছে। দ্বিতীয় সমস্যার উত্তর dependency-কে উল্টে দেওয়া: domain কেন্দ্রে, database একটি বাইরের adapter — Alistair Cockburn-এর hexagonal (ports and adapters) আর Robert Martin-এর clean architecture, যা পর্ব ৪০-এর বিষয়। প্রথম সমস্যার উত্তর feature বা module ধরে ভাগ (vertical slice, modular monolith), যার ভেতরে দরকার হলে ছোট আকারে স্তর।

মূল উপলব্ধি: Layered টিকে আছে কারণ “প্রতিটি অংশ শুধু নিচেরটিকে জানে” — এই নিয়ম যুক্তি আর test সহজ করে। ভুলটা হয় যখন স্তরগুলো ভুল অক্ষে টানা হয়, বা নির্ভরতার তীর ভুল দিকে যায়। সমালোচনা আসলে তীরের দিক নিয়ে, স্তরের ধারণা নিয়ে নয়।

প্র ০৪১৯৮৬ সালের একটি word-count প্রতিযোগিতা pipe-and-filter সম্পর্কে কী শেখায়?

১৯৮৬ সালে Communications of the ACM-এ Jon Bentley-র “Programming Pearls” কলামে একটি ঘটনা প্রকাশিত হয়। Bentley Donald Knuth-কে একটি কাজ দেন: একটি লেখা থেকে সবচেয়ে বেশি আসা k-টি শব্দ, তাদের সংখ্যাসহ, ছাপানো। Knuth তাঁর literate programming পদ্ধতিতে কয়েক পাতার একটি যত্নশীল program লেখেন, বিশেষ data structure-সহ। তারপর Doug McIlroy — যিনি ১৯৬০-এর দশকেই program-কে বাগানের পাইপের মতো জোড়ার কথা লিখেছিলেন, আর যাঁর চাপে ১৯৭৩ সালে Unix-এ pipe এসেছিল — সমালোচনায় ছয়টি ধাপের একটি shell pipeline দেন, মোটামুটি: অক্ষর-নয় এমন সব চিহ্নকে নতুন লাইন বানানো (tr), ছোট হাতের অক্ষর (tr), সাজানো (sort), একই শব্দ গোনা (uniq -c), সংখ্যা ধরে উল্টো সাজানো (sort -rn), প্রথম k লাইন (sed).

শিক্ষাটি এই পর্বের Python উদাহরণের সাথে মেলে। প্রতিটি ধাপ আলাদা, পরিচিত, আগেই test করা tool; জোড়ার নিয়ম একটাই — লেখার লাইন ঢোকে, লেখার লাইন বের হয়। একটি ধাপ বদলানো (যেমন বাংলা শব্দ ধরার জন্য প্রথম ধাপ) বাকিদের ছোঁয় না। McIlroy-এর যুক্তি ছিল, বেশিরভাগ বাস্তব প্রয়োজনে এটি যথেষ্ট, আর লিখতে মিনিট লাগে।

কিন্তু সীমাও স্পষ্ট। sort পুরো ডেটা দেখে তবেই আউটপুট দেয় — প্রবাহ সেখানে থেমে যায়, ঠিক যেমন Python-এর Counter শেষে সব শব্দ চায়। বিশাল ডেটায় Knuth-এর বিশেষ data structure অনেক কম সময় ও memory নিতে পারে। আর প্রতিটি ধাপ লেখা পড়ে-লিখে ডেটার আকার আবার তৈরি করে — সেটিও খরচ। এজন্য বাস্তবে pipe-and-filter জেতে যখন ধাপগুলো স্বাধীন আর বদলযোগ্য হওয়াটা গতির চেয়ে জরুরি; গতি চরম জরুরি হলে ধাপগুলো এক জায়গায় মিশিয়ে বিশেষ সমাধান লাগে।

মূল উপলব্ধি: দুজনেই ঠিক ছিলেন — ভিন্ন quality attribute-এর জন্য। একটি modifiability আর লেখার গতিতে জেতে, অন্যটি performance-এ। Architecture-এর প্রশ্ন প্রায় সবসময় এরকম: কোন গুণের জন্য কোন দাম।

প্র ০৫বড় architecture document প্রায়ই কেউ পড়ে না, হালনাগাদও হয় না। তাহলে architecture-এর জ্ঞান দলে বাঁচিয়ে রাখা যায় কীভাবে?

Waterfall-এর যুগে architecture document প্রায়ই ছিল শুরুতে লেখা একটি বিশাল দলিল, যা অনুমোদনের পর তাকে উঠে যেত। কোড বদলাত, দলিল বদলাত না; এক বছর পর দলিলটি ভুল তথ্যের উৎস। Agile-এর প্রতিক্রিয়ায় অনেক দল উল্টো চরমে গেল — “কোডই দলিল”। কিন্তু কোড বলে কী করা হয়েছে, বলে না কেন, আর কোন বিকল্প বাদ দেওয়া হয়েছিল। “কেন” হারালে পরের দল হয় ভয়ে কিছু বদলায় না, নয়তো না জেনে আগের ভুলটি আবার করে।

আজকের প্রচলিত উত্তর কয়েকটি হালকা অভ্যাসের জোড়া। প্রথমত, ADR: প্রতিটি সিদ্ধান্ত ছোট, অপরিবর্তনীয় (শুধু status বদলায়), তারিখসহ — তাই পুরোনো হলেও মিথ্যা হয় না, শুধু “ইতিহাস” হয়। ThoughtWorks-এর Technology Radar কয়েক বছর আগে হালকা ADR-কে গ্রহণের পরামর্শ দিয়েছিল বলে উল্লেখ পাওয়া যায়। দ্বিতীয়ত, docs as code: দলিল কোডের repository-তে, PR-এ review হয়, তাই কোডের বদলের সাথে একই PR-এ দলিলও বদলানো যায় (পর্ব ১৬)। তৃতীয়ত, কয়েকটি ছবি, একটি নির্দিষ্ট পদ্ধতিতে — C4-এর context আর container স্তর প্রায়ই যথেষ্ট — আর সম্ভব হলে diagram as code. চতুর্থত, fitness function: নিয়মটি test হলে দলিল পুরোনো হলেও CI ভুল ধরে ফেলে।

আর মানুষের দিকটা: architecture কি একজন “architect”-এর কাজ? Fowler-এর “Who Needs an Architect?” প্রবন্ধের একটি মূল কথা — ভালো architect দলের সবার বোঝাপড়া বাড়ান, একা সিদ্ধান্তের দেবতা হন না। ADR-এর review, pair programming আর code review (পর্ব ৪৪) সেই বোঝাপড়া ছড়ানোর পথ।

মূল উপলব্ধি: Architecture-এর জ্ঞান বাঁচে তিন জায়গায় একসাথে — ছোট, তারিখসহ সিদ্ধান্তের রেকর্ডে, কোডের পাশে চলা ছবি আর test-এ, আর দলের মানুষের মাথায়। যেকোনো একটির ওপর একা ভরসা করলে জ্ঞান হারায়।

অনুশীলন

একটি ADR লেখা, আউটপুট অনুমান, একটি হিসাব আর একটি সমালোচনা — চারটিই পরীক্ষা ও ইন্টারভিউর ধরনের।

  1. একটি ADR লিখুন: একটি বিশ্ববিদ্যালয়ের ফলাফল পোর্টাল (কাল্পনিক) প্রতি পরীক্ষার পর একবার ফল প্রকাশ করে: শিক্ষকদের দেওয়া নম্বরের CSV ফাইল আমদানি → যাচাই (অনুপস্থিত নম্বর, সীমার বাইরে) → GPA হিসাব → প্রকাশ। এখন পুরো কাজটি ১,২০০ লাইনের একটি script, যেখানে GPA-র নিয়ম বদলালে যাচাইয়ের কোডও ভাঙে। দলে ৩ জন; নতুন নিয়ম আসছে (যেমন grace mark). Nygard-এর পাঁচ অংশে একটি ADR লিখুন।

    একটি সম্ভাব্য উত্তর (অন্য যুক্তিসংগত সিদ্ধান্তও গ্রহণযোগ্য, যদি context আর consequences সৎ হয়):

    # 3. Split result processing into a pipe-and-filter batch
    
    Date: 2026-03-01
    
    ## Status
    Accepted
    
    ## Context
    Results are published once per exam. Today one 1,200-line script imports
    marks, validates them, computes GPA and publishes. A change to GPA rules
    broke validation twice. New rules (e.g. grace marks) are coming. Team: 3.
    Volume is small (tens of thousands of rows); speed is not a concern.
    
    ## Decision
    We will split the script into independent filters with one record format
    between them: import -> validate -> apply_rules -> compute_gpa -> publish.
    Each filter has its own unit tests. Rule changes go into apply_rules only.
    Invalid rows go to a rejects file with a reason; they never stop the run.
    
    ## Consequences
    + A rule change touches one filter; each filter is tested alone.
    + Grace marks become a new filter, not an edit across the script.
    - All filters must agree on the record format; changing it touches all.
    - A record moving through five steps is harder to trace; we will log
      the row id at every filter.
    - Reconsider if publishing must become real-time instead of once per exam.

    খেয়াল করুন: context-এ “গতি সমস্যা নয়” লেখা, তাই pipe-and-filter-এর ধীরতা এখানে দাম নয়। Consequences-এ দুটি নেতিবাচক পরিণাম আর ফিরে ভাবার শর্ত আছে। ভুল ADR-এর লক্ষণ হতো শুধু “+” চিহ্ন, বা “আমরা best practice অনুসরণ করব” ধরনের অস্পষ্ট decision.

  2. আউটপুট অনুমান করুন: অংশ ৩-এর check() function অপরিবর্তিত রেখে শুধু ডেটা বদলানো হলো। চালানোর আগে দুই লাইন সারাংশ আর প্রতিটি violation লিখুন। এই import-গুলোর ভেতরে কি কোনো cycle আছে?
    const layer = { OrderPage: 3, ReportPage: 3, OrderService: 2, Repo: 1, Cache: 1 };
    const imports = [
      ["OrderPage", "OrderService"], ["ReportPage", "Repo"],
      ["OrderService", "Repo"],      ["Repo", "Cache"],
      ["Cache", "OrderService"],     ["OrderService", "OrderPage"]
    ];
    strict: 3 violation(s) in 6 imports
      SKIP ReportPage(3) -> Repo(1)
      UP   Cache(1) -> OrderService(2)
      UP   OrderService(2) -> OrderPage(3)
    relaxed: 2 violation(s) in 6 imports
      UP   Cache(1) -> OrderService(2)
      UP   OrderService(2) -> OrderPage(3)

    (এই আউটপুট আসল কোড চালিয়ে মেলানো।) ReportPage → Repo-র gap ২, তাই শুধু strict-এ ভাঙে। Repo → Cache একই স্তরে, gap ০, দুই নিয়মেই বৈধ। দুটি UP দুই নিয়মেই অপরাধ। Cycle আছে দুটি: OrderService → Repo → Cache → OrderService আর OrderPage → OrderService → OrderPage. Checker সরাসরি cycle খোঁজে না, কিন্তু স্তরের সংখ্যা ওপর থেকে নিচে সাজানো বলে যেকোনো cycle-এ অন্তত একটি UP তীর থাকতেই হবে — শুধু নিচে নামতে থাকলে শুরুর জায়গায় ফেরা যায় না। একই স্তরের ভেতরের cycle (gap ০) অবশ্য এই checker ধরবে না; তার জন্য পর্ব ২৫-এর মতো graph-এ cycle খোঁজা লাগে।

  3. হিসাব — trade-off matrix হাতে: তিনটি প্রার্থী A, B, C. ওজন: performance ৩, modifiability ৫, cost ২. নম্বর (১–৫, এই ক্রমে): A = (৪, ২, ৫), B = (৩, ৪, ৩), C = (৫, ৩, ১)। (ক) প্রতিটির মোট নম্বর আর বিজয়ী বের করুন। (খ) শুধু modifiability-র ওজন কমালে কোন ওজনে বিজয়ী প্রথম বদলায়, আর কে জেতে?

    (ক) A = ৪×৩ + ২×৫ + ৫×২ = ১২ + ১০ + ১০ = ৩২; B = ৯ + ২০ + ৬ = ৩৫; C = ১৫ + ১৫ + ২ = ৩২. বিজয়ী B (৩৫), A আর C সমান (৩২)।

    (খ) Modifiability-র ওজন m হলে: A = ২২ + ২m, B = ১৫ + ৪m, C = ১৭ + ৩m. B ঠিক তখনই হারায়, যখন ১৫ + ৪m < ২২ + ২m, অর্থাৎ m < ৩.৫. ওজন পূর্ণসংখ্যা হলে m = ৪-এ এখনো B (A ৩০, B ৩১, C ২৯), কিন্তু m = ৩-এ A জেতে (A ২৮, B ২৭, C ২৬)। m = ২-এ A ২৬, B আর C ২৩ করে; m = ১-এ A ২৪, C ২০, B ১৯. অর্থাৎ modifiability-র ওজন এই সিদ্ধান্তের sensitivity point: ৫ থেকে ৩-এ নামলেই সিদ্ধান্ত উল্টে যায়, তাই ওজনটি নিয়ে stakeholder-দের সাথে সবচেয়ে মনোযোগী আলোচনা দরকার। (সংখ্যাগুলো একটি ছোট script চালিয়ে মেলানো।)

  4. সমালোচনা করুন: একটি কোচিং সেন্টারের উপস্থিতি app (কাল্পনিক) — ৩০০ ছাত্র, ৩ জনের দল, একজনও ops-এর লোক নেই। একটি AI tool প্রস্তাব দিল: “১২টি microservice (প্রতিটির নিজস্ব database), সবার মাঝে একটি event broker, Kubernetes-এ চালানো। ADR-এর consequences: অসীম scalability, সর্বোচ্চ flexibility, industry best practice.” কী কী সমস্যা দেখছেন, আর আপনি কী প্রস্তাব করবেন?
    • Driver উপেক্ষিত: ৩০০ ছাত্রের উপস্থিতি একটি ছোট server-ই সামলাতে পারে; “অসীম scalability” এমন সমস্যার সমাধান, যা নেই (পর্ব ২৬-এর YAGNI). আসল driver সম্ভবত সহজ চালানো, কম খরচ আর দ্রুত বদল — যেগুলোতে প্রস্তাবটি সবচেয়ে খারাপ।
    • Constraint ও Conway's law: ৩ জনের দলে ১২টি service মানে প্রত্যেকে চারটি service, ১২টি database, broker আর Kubernetes-এর ভার — ops-এর লোক ছাড়াই। সীমা ঝাপসা হবে, distributed monolith-এর ঝুঁকি।
    • ADR অসৎ: consequences-এ একটিও দাম নেই; network ব্যর্থতা, distributed transaction, monitoring, খরচ — কিছুই নেই। “Industry best practice” কোনো context নয়।
    • প্রস্তাব: একটি modular monolith বা এমনকি একটি সাধারণ layered app, একটি database; ২–৩টি module (attendance, students, reports); report-এর ভারী কাজ দরকার হলে পরে একটি background job. একটি সৎ ADR, যাতে লেখা থাকবে কোন শর্তে (যেমন বহু শাখা, হাজারো ছাত্র) আবার ভাবতে হবে। আর মূল নিয়মগুলোর জন্য একটি fitness function test.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি architecture সিদ্ধান্তের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Quality attribute, constraint আর functional requirement — driver-এর তালিকা
  2. Constraint দিয়ে অবাস্তব style বাদ দেওয়া
  3. Quality attribute-এর অগ্রাধিকার আর মাপা যায় এমন scenario লেখা
  4. Scenario আর weighted matrix দিয়ে প্রার্থী style যাচাই, sensitivity দেখা
  5. ADR লিখে PR-এ review, তারপর Git-এ merge
  6. Fitness function দিয়ে সিদ্ধান্তটি CI-তে রক্ষা

একটি quality attribute scenario: “ঈদের আগের ভিড়ের সময় একটি app server হঠাৎ বন্ধ হয়ে গেল; বাকি server ৩০ সেকেন্ডের মধ্যে সব অনুরোধ নেবে।” এখানে “একটি app server হঠাৎ বন্ধ হয়ে গেল” কোন অংশ?

  • Environment
  • Stimulus
  • Response measure
  • Artifact

চার স্তরের একটি app: 4 presentation, 3 application, 2 domain, 1 data. Presentation-এর একটি module সরাসরি domain-এর (স্তর 2) একটি module import করে। কোন কথাটি ঠিক?

  • Strict আর relaxed — দুই নিয়মেই এটি নিষিদ্ধ
  • Strict-এ চলে, relaxed-এ নিষিদ্ধ
  • Strict-এ নিষিদ্ধ, relaxed-এ অনুমোদিত
  • দুই নিয়মেই অনুমোদিত, কারণ তীর নিচের দিকে

Layered architecture-এ “sinkhole anti-pattern” বলতে কী বোঝায়?

  • বেশিরভাগ অনুরোধ কোনো কাজ ছাড়াই স্তরের পর স্তর শুধু পার হয়ে database পর্যন্ত যায়
  • নিচের স্তর ওপরের স্তরকে import করে
  • Database-এ লেখা ডেটা মাঝপথে হারিয়ে যায়
  • একটি app-কে অপ্রয়োজনে অনেক microservice-এ ভাগ করা

একটি বুকিং প্রবাহে আসন আটকানো, পেমেন্ট নেওয়া আর SMS পাঠানো ক্রম মেনে হতে হবে, আর পেমেন্ট ব্যর্থ হলে আসন ছেড়ে দিতে হবে। এক জায়গা থেকে পুরো প্রবাহের অবস্থা দেখা আর ভুল সামলানো জরুরি। Event-driven architecture-এর কোন রূপ বেশি মানায়?

  • Broker topology, কারণ কেন্দ্রীয় অংশ নেই
  • Pipe-and-filter
  • Space-based architecture
  • Mediator topology, যেখানে একটি সমন্বয়ক ধাপ আর ফেরানোর কাজ চালায়

Michael Nygard-এর ২০১১ সালের ADR ছাঁচের অংশ নয় কোনটি?

  • Context
  • Cost estimate (খরচের হিসাব)
  • Consequences
  • Status

C4 model-এ “container” বলতে কী বোঝায়?

  • শুধু Docker বা অন্য কোনো container প্রযুক্তিতে চলা একক
  • একটি class আর তার method-গুলো
  • আলাদাভাবে চালানো বা deploy করা যায় এমন একক, যেমন web app, API বা database
  • System-এর বাইরের একজন ব্যবহারকারী বা system

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Architecture = system-এর বড় কাঠামো (উপাদান, সম্পর্ক, গুণ) আর কঠিন-বদল সিদ্ধান্ত; architecture-ও design, কিন্তু বড় পরিসরের আর বেশি দামের।
  2. Style বাছাই করে driver: quality attribute, constraint, functional requirement. অস্পষ্ট চাওয়াকে source-stimulus-artifact-environment-response-response measure-এ লিখুন।
  3. Layered (strict/relaxed, sinkhole), client-server, pipe-and-filter, event-driven (broker/mediator), microkernel, modular monolith/microservices, space-based — প্রতিটি একটি দাম দিয়ে একটি গুণ কেনে।
  4. এক ছবিতে architecture ধরে না: 4+1 (logical, process, development, physical + scenarios) আর C4 (context, container, component, code).
  5. সিদ্ধান্ত ADR-এ (title, status, context, decision, consequences) docs/adr/-এ Git-এ; বদলালে নতুন ADR, পুরোনোটির শুধু status. নিয়ম রক্ষা করে fitness function; দলের কাঠামো মনে করায় Conway's law.

পরের পর্বে: পুরো system ছেড়ে একটি অংশের ভেতরে — UI-র কোড কীভাবে সাজাবেন: MVC, MVP আর MVVM, আর screen-এর কোড থেকে ব্যবসার নিয়ম আলাদা রাখার কৌশল।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান