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

Requirement modeling — domain model, use case diagram, activity আর state diagram

Requirements Modeling — Use Case Diagrams, Domain Models, DFDs, Activity and State Machine Diagrams

Taught in Bangla, this lesson explains why to model requirements and how UML unified three methods. It draws use case diagrams with actors and system boundaries, domain models, context diagrams and data flow diagrams, activity diagrams with decisions and swimlanes, and a state machine diagram for an order lifecycle, then cross-checks models with a CRUD matrix and discusses tools and just-enough modeling.

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

  • Requirement model কেন বানানো হয়, আর UML কীভাবে তিনটি পদ্ধতি মিলে একটি মানদণ্ড হলো
  • সঠিক notation-এ use case diagram: actor, system boundary, «include», «extend», extension point আর generalisation — আর সাধারণ ভুলগুলো
  • Noun analysis থেকে domain model: concept, attribute, association আর multiplicity
  • Structured analysis-এর context diagram আর DFD; activity diagram-এ fork, join আর swimlane; state machine-এ event, guard আর effect
  • CRUD matrix দিয়ে model-গুলো মিলিয়ে দেখা, test-এর path গোনা আর “just enough” modeling

১ · কেন requirement-এর ছবি আঁকবেন

একটি কাল্পনিক অনলাইন ফার্মেসি অ্যাপের কথা ভাবুন। পর্ব ২০-এ (Requirement engineering) দল stakeholder-দের সাথে কথা বলেছে, পর্ব ২১-এ (Functional আর non-functional requirement) requirement-গুলো মাপা যায় এমনভাবে লিখেছে, আর পর্ব ২২-এ (Use case আর user story) প্রতিটি লক্ষ্যের ধাপগুলো লেখা use case আর user story হয়েছে। এখন হাতে প্রায় চল্লিশ পাতার লেখা।

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

বাড়ি বানানোর আগে রাজমিস্ত্রিকে শুধু একটি চিঠি দিলে কী হয়? “তিনটি শোবার ঘর, একটি বড় বসার ঘর, রান্নাঘর যেন বারান্দার পাশে হয়।” প্রত্যেক মিস্ত্রি নিজের মতো বুঝবেন। কিন্তু কাগজে নকশা আঁকলে সঙ্গে সঙ্গে প্রশ্ন ওঠে: “সিঁড়ি কোথায়? বাথরুমে ঢোকার দরজা কোন ঘর দিয়ে?” নকশা কথার চেয়ে কম জিনিস বলে, কিন্তু যেটুকু বলে তা দ্ব্যর্থহীনভাবে বলে — আর না-বলা জিনিসগুলো ফাঁকা জায়গা হয়ে চোখে পড়ে। Requirement-এর model ঠিক এই কাজ করে।

Requirement modeling মানে requirement-কে এক বা একাধিক নির্দিষ্ট নিয়মের ছবি (বা কাঠামোবদ্ধ লেখা) দিয়ে প্রকাশ করা, যাতে তিনটি লাভ হয়:

১ · একই বোঝাপড়া

গ্রাহক, product owner, developer আর tester একই ছবির দিকে আঙুল দিয়ে কথা বলেন। “এখানে, এই তীরটা” — দ্ব্যর্থতা কমে।

shared understanding

২ · ফাঁক খোঁজা

State diagram-এ “Paid” অবস্থা থেকে “cancel” তীর না থাকলে প্রশ্নটি নিজেই উঠে আসে — ওপরের টাকা ফেরতের প্রশ্নটির মতো।

find gaps

৩ · জানানো আর মনে রাখা

নতুন সদস্য এক পাতার ছবি দেখে system-এর মূল কাঠামো বোঝেন; ছয় মাস পরে সিদ্ধান্তটি কেন নেওয়া হয়েছিল, তাও খুঁজে পান।

communicate

এক ধরনের ছবি সব প্রশ্নের উত্তর দেয় না। তাই কয়েকটি দৃষ্টিকোণ থেকে model বানানো হয়। এই পর্বে আমরা পাঁচটি দেখব:

Modelযে প্রশ্নের উত্তর দেয়ধরন
Use case diagramকারা system ব্যবহার করেন, কোন লক্ষ্যে? System-এর সীমা কোথায়?আচরণ, বাইরে থেকে
Domain modelসমস্যার জগতে কোন কোন জিনিস আছে, তারা কীভাবে সম্পর্কিত?কাঠামো (structure)
Context diagram ও DFDকোন data কোথা থেকে আসে, কোথায় যায়, কোথায় জমা থাকে?data-র প্রবাহ
Activity diagramএকটি কাজ কোন ধাপে, কার হাত ঘুরে, কোন শর্তে এগোয়?আচরণ, ধারা
State machine diagramএকটি জিনিস (যেমন অর্ডার) কোন কোন অবস্থায় থাকতে পারে, কীসে বদলায়?আচরণ, জীবনচক্র
মূল ধারণা

Requirement স্তরের model সমস্যাটি বর্ণনা করে, সমাধান নয়। এখানে “Order” মানে বাস্তবের একটি অর্ডার, কোডের একটি class নয়; “Paid” মানে ব্যবসার একটি অবস্থা, database-এর একটি column নয়। Design-এর স্তরে (মডিউল ৫, বিশেষ করে পর্ব ২৯ আর ৩০) একই ধরনের ছবি আরও বিস্তারিত হবে — method, interface, message-সহ।

২ · UML — তিন পদ্ধতি থেকে একটি ভাষা

১৯৮০-র দশকের শেষ আর ১৯৯০-এর দশকের শুরুতে object-oriented analysis-এর কয়েক ডজন পদ্ধতি ছিল, প্রত্যেকের নিজস্ব ছবি আঁকার নিয়ম। একই class কেউ আঁকতেন মেঘের মতো, কেউ আয়তক্ষেত্রে। একে অনেকে “method war” বলতেন। এর মধ্যে তিনটি পদ্ধতি সবচেয়ে প্রভাবশালী ছিল:

  • Grady Booch-এর Booch method — design আর কোডের কাছাকাছি স্তরে শক্তিশালী।
  • James Rumbaugh ও সহকর্মীদের OMT (Object Modeling Technique, ১৯৯১-এর বই) — analysis আর data-র কাঠামোয় শক্তিশালী।
  • Ivar Jacobson-এর OOSE (১৯৯২-এর বই) — যেখান থেকে use case-এর ধারণা এসেছে। Jacobson Ericsson-এ telecom system-এর কাজের অভিজ্ঞতা থেকে ১৯৮০-র দশকে use case-এর চিন্তা গড়ে তোলেন বলে জানা যায়।

Rational Software কোম্পানিতে Booch-এর সাথে Rumbaugh যোগ দেন ১৯৯৪ সালে, Jacobson ১৯৯৫ সালে; তিনজনকে তখন থেকে “three amigos” বলা হতো। তাঁদের একীভূত নোটেশনই UML (Unified Modeling Language). ১৯৯৭ সালের নভেম্বরে OMG (Object Management Group, একটি মানদণ্ড-সংস্থা) UML ১.১-কে মানদণ্ড হিসেবে গ্রহণ করে। ২০০৫ সালের দিকে বড় পুনর্গঠন UML ২.০ আসে; আমাদের জানামতে সর্বশেষ সংস্করণ UML ২.৫.১, প্রকাশ ২০১৭ সালের ডিসেম্বরে। UML-এর একটি পুরোনো সংস্করণ (২.৪.১) ISO/IEC 19505 নামে আন্তর্জাতিক মানদণ্ডও হয়েছে। নতুন সংস্করণ নিয়ে কাজ চলছে কি না, তা OMG-র website-এ নিজে দেখে নিন।

UML ২.৫-এ মোট ১৪ ধরনের diagram আছে — সাতটি কাঠামোর (class, object, component…), সাতটি আচরণের (use case, activity, state machine, sequence…)। ভয় পাওয়ার কিছু নেই; বাস্তবে বেশিরভাগ দল চার-পাঁচটিই ব্যবহার করে। দুটি কথা মনে রাখুন:

  • UML একটি নোটেশন, পদ্ধতি নয়। কোন ক্রমে কোন diagram আঁকবেন, তা UML বলে না। সেটা বলে process — যেমন পর্ব ৪-এ দেখা Unified Process, বা আপনার দলের নিজের নিয়ম।
  • Data-flow diagram UML-এর অংশ নয়। এটি আরও পুরোনো structured analysis ধারা থেকে এসেছে (অংশ ৫)। অনেক দল আজও দুটিই ব্যবহার করে।

৩ · Use case diagram — কে, কী লক্ষ্যে, কোন সীমার মধ্যে

পর্ব ২২-এ আপনি লেখা use case দেখেছেন — main success scenario, extension, precondition. Use case diagram সেই লেখার একটি সূচিপত্র: এক পাতায় দেখায় কোন কোন use case আছে আর কে কোনটিতে অংশ নেন। ধাপগুলোর বিস্তারিত diagram-এ থাকে না, থাকে লেখায়। এর উপাদানগুলো:

উপাদাননোটেশনঅর্থ
Actorকাঠির মানুষ (বা «actor» লেখা বাক্স)System-এর বাইরের একটি ভূমিকা — মানুষ, অন্য system বা যন্ত্র। একজন মানুষ নয়, একটি ভূমিকা: একই ব্যক্তি কখনো গ্রাহক, কখনো ফার্মাসিস্ট হতে পারেন।
Primary actorসাধারণত বাঁ দিকে আঁকা হয়যাঁর লক্ষ্য পূরণে use case শুরু হয় (গ্রাহক অর্ডার দেন)।
Secondary actorসাধারণত ডান দিকেযিনি বা যে system সাহায্য করে (পেমেন্ট গেটওয়ে, SMS service). এটি একটি প্রচলিত রীতি; UML নিজে বাঁ-ডান বাধ্যতামূলক করে না।
Use caseউপবৃত্ত (ellipse), ভেতরে ক্রিয়াবাচক নামActor-এর একটি মূল্যবান লক্ষ্য: “অর্ডার দেওয়া”, “বোতাম চাপা” নয়।
System boundaryনাম লেখা আয়তক্ষেত্রভেতরে যা, তা আমরা বানাব; বাইরে যা, তা আমাদের দায়িত্ব নয়।
AssociationActor থেকে use case পর্যন্ত সাধারণ রেখাActor এই use case-এ অংশ নেন। তীর দরকার নেই; তীর data-র দিক বোঝায় না।
«include»ড্যাশ রেখা, খোলা তীর, base থেকে included-এর দিকেBase use case প্রতিবার included অংশটি চালায়।
«extend»ড্যাশ রেখা, খোলা তীর, extension থেকে base-এর দিকেশর্ত পূরণ হলে তবেই extension যুক্ত হয়, base-এর একটি extension point-এ।
Generalisationসাধারণ রেখা, ফাঁপা ত্রিভুজ সাধারণের দিকেবিশেষ actor সাধারণ actor-এর সব use case উত্তরাধিকারে পান।

অনলাইন ফার্মেসির use case diagram (কাল্পনিক)

Actors, system boundary, «include», «extend» with an extension point, actor generalisation

অনলাইন ফার্মেসির use case diagram অনলাইন ফার্মেসি (কাল্পনিক) ওষুধ খোঁজা প্রেসক্রিপশন আপলোড অর্ডার দেওয়া extension points Rx লাগলে পেমেন্ট করা প্রেসক্রিপশন যাচাই «extend» «include» ভিজিটর গ্রাহক ফার্মাসিস্ট «actor» Payment gateway
ছবি ২৩.১ — গ্রাহক ভিজিটরের বিশেষ রূপ, তাই “ওষুধ খোঁজা” গ্রাহকও পান। «include»-এর তীর অর্ডার থেকে পেমেন্টের দিকে (প্রতিবার ঘটে); «extend»-এর তীর আপলোড থেকে অর্ডারের দিকে (শুধু cart-এ প্রেসক্রিপশন লাগে এমন ওষুধ থাকলে)।

«include» বনাম «extend» — তীরের দিকই আসল পরীক্ষা

দুটোই ড্যাশ রেখা আর খোলা তীর, তাই গুলিয়ে ফেলা খুব স্বাভাবিক। পার্থক্যটা “কে কাকে জানে” প্রশ্নে:

  • «include»: “অর্ডার দেওয়া” use case-এর লেখার ভেতরেই একটি ধাপ আছে — “পেমেন্ট করা use case চালান।” Base জানে included-কে, তাই তীর base থেকে বেরোয়। পেমেন্ট ছাড়া অর্ডার সম্পূর্ণ হয় না। সাধারণত একাধিক use case-এর মধ্যে ভাগ করা অংশ আলাদা করতে এটি ব্যবহার হয় — যেমন “অর্ডার দেওয়া” আর “বকেয়া পরিশোধ” দুটোই পেমেন্ট include করে।
  • «extend»: “অর্ডার দেওয়া” নিজে প্রেসক্রিপশনের কথা জানে না; সে শুধু একটি নামের জায়গা খোলা রাখে — extension point “Rx লাগলে”। “প্রেসক্রিপশন আপলোড” নিজেই ঘোষণা করে, “শর্ত [cart-এ Rx-ওষুধ আছে] সত্য হলে আমি ওই জায়গায় যুক্ত হব।” Extension জানে base-কে, তাই তীর extension থেকে বেরোয়। Base নিজে একাই সম্পূর্ণ, অর্থবহ।

বিয়েবাড়ির আয়োজনের তালিকায় “অতিথিদের খাওয়ানো”-র ভেতরে “রান্না” সবসময় থাকে — এটি include. কিন্তু “বৃষ্টি হলে প্যান্ডেলে ত্রিপল টানানো” একটি extend: মূল আয়োজন জানে না বৃষ্টি হবে কি না, শুধু একটি জায়গা রাখা আছে — “অতিথি আসার আগে”। শর্ত সত্য হলে ত্রিপল-দল সেখানে কাজে নামে।

Actor-এর generalisation-এ ফাঁপা ত্রিভুজ থাকে সাধারণ actor-এর গায়ে: “গ্রাহক একজন ভিজিটর, সাথে আরও কিছু।” Use case-এর মধ্যেও generalisation আঁকা যায় (যেমন “পেমেন্ট করা”-র দুটি বিশেষ রূপ — mobile wallet আর card), তবে এটি কম ব্যবহৃত, আর অনেক লেখক সাবধানে ব্যবহার করতে বলেন।

এবার একটি ছোট বর্ণনা থেকে ধাপে ধাপে diagram বানাই। বর্ণনা: “ভিজিটর ওষুধ খুঁজতে পারেন। নিবন্ধিত গ্রাহক অর্ডার দেন আর mobile wallet-এ পেমেন্ট করেন; পেমেন্ট হয় একটি বাইরের পেমেন্ট গেটওয়ের মাধ্যমে। কিছু ওষুধের জন্য প্রেসক্রিপশন আপলোড করতে হয়, যা একজন ফার্মাসিস্ট যাচাই করেন।” “পরে” চাপুন।

Diagram-এ যা জমা হলো
Boundary
Actor
Use case
সম্পর্ক
যাচাই
  1. Boundary আগে। প্রথম প্রশ্ন: আমরা কী বানাচ্ছি? “অনলাইন ফার্মেসি” নামের একটি আয়তক্ষেত্র। পেমেন্ট গেটওয়ে আমরা বানাচ্ছি না — তাই সেটা বাইরে থাকবে। এই সিদ্ধান্ত পরে scope নিয়ে অনেক তর্ক বাঁচায়।
  2. Actor খুঁজুন: কে system-এর সাথে কথা বলে? বর্ণনা থেকে চারটি ভূমিকা: ভিজিটর, গ্রাহক, ফার্মাসিস্ট (তিনজনই মানুষ) আর পেমেন্ট গেটওয়ে (একটি বাইরের system). প্রত্যেকে boundary-র বাইরে। “Database” actor নয় — সেটা আমাদের system-এর ভেতরের অংশ।
  3. লক্ষ্য খুঁজুন। প্রতিটি actor কী অর্জন করতে চান? চারটি use case, প্রতিটির নাম ক্রিয়াবাচক। “Login” এখানে রাখা হয়নি — গ্রাহকের আসল লক্ষ্য লগইন নয়, অর্ডার; লগইন সাধারণত অন্য use case-এর একটি precondition. (এ নিয়ে মতভেদ আছে; অনেক দল লগইনও আঁকে।)
  4. Association টানুন। কে কোন use case-এ অংশ নেন। পেমেন্ট গেটওয়ে secondary actor — পেমেন্ট করায় সাহায্য করে, শুরু করে না। খেয়াল করুন, প্রতিটি use case-এ অন্তত একটি actor আছেন।
  5. «include»: সবসময় ঘটে এমন ভাগ করা অংশ। অর্ডার সম্পূর্ণ হতে পেমেন্ট লাগবেই। তাই অর্ডার থেকে পেমেন্টের দিকে ড্যাশ তীর। পরে “বকেয়া পরিশোধ” use case এলে সেটিও একই পেমেন্ট include করবে।
  6. «extend»: শুধু শর্তে। সব অর্ডারে প্রেসক্রিপশন লাগে না। “অর্ডার দেওয়া”-র ভেতরে extension point “Rx লাগলে” ঘোষণা করা হলো, আর আপলোড থেকে অর্ডারের দিকে তীর, শর্ত [cart-এ Rx-ওষুধ আছে]. অর্ডার নিজে আপলোডের কথা জানে না।
  7. Generalisation দিয়ে পুনরাবৃত্তি কমান। গ্রাহকও ওষুধ খুঁজতে পারেন। গ্রাহক থেকে আরেকটি রেখা টানার বদলে গ্রাহক থেকে ভিজিটরের দিকে ফাঁপা ত্রিভুজ: গ্রাহক ভিজিটরের সব use case পান। ছবি ২৩.১ ঠিক এই অবস্থা দেখায়।

Use case diagram-এর সাধারণ ভুল

ভুলকেন ভুলঠিক করবেন যেভাবে
«include» আর «extend»-এর তীর উল্টোদিক দিয়েই বোঝা যায় কে কাকে জানেInclude: base → included; extend: extension → base
Use case গুলোকে তীর দিয়ে ক্রমে জোড়া (লগইন → খোঁজা → অর্ডার)Use case diagram ধারা দেখায় না; ক্রম দেখাতে activity diagramতীর সরিয়ে ক্রমটি activity diagram বা লেখা use case-এ নিন
“বোতাম চাপা”, “form পূরণ” জাতীয় use caseএগুলো ধাপ, লক্ষ্য নয়; diagram শত শত উপবৃত্তে ভরে যায়প্রশ্ন করুন: actor কি এটুকু করে সন্তুষ্ট হয়ে চলে যান?
Database বা “System” নিজেই actor, বা actor boundary-র ভেতরেActor সবসময় বাইরের; নিজের অংশ actor নয়ভেতরের অংশ বাদ দিন; বাইরের system হলে «actor» বাক্সে বাইরে রাখুন
প্রায় প্রতিটি use case ভেঙে include-এর গাছএটি functional decomposition — design-এর কাজ আগেভাগেInclude শুধু সত্যিই ভাগ করা অংশের জন্য
Actor থেকে actor-এ সাধারণ রেখাActor-দের নিজেদের কথাবার্তা system-এর বাইরের ব্যাপারবাদ দিন; সম্পর্ক থাকলে generalisation

৪ · Domain model — সমস্যার জগতের জিনিসগুলো

Use case বলে মানুষ কী করেন। Domain model বলে সেই কাজে কোন কোন জিনিস জড়িত: গ্রাহক, অর্ডার, ওষুধ, প্রেসক্রিপশন। একে conceptual class model-ও বলা হয়, কারণ প্রতিটি বাক্স একটি conceptual class — বাস্তব জগতের একটি ধারণা, কোডের class নয়। Craig Larman-এর বহুল-পঠিত বই Applying UML and Patterns (প্রথম সংস্করণ ১৯৯৭, তৃতীয় ২০০৪) এই অর্থে শব্দটি জনপ্রিয় করে।

নোটেশন UML class diagram-এর একটি হালকা অংশ:

  • বাক্সের ওপরে নাম, নিচে attribute (জিনিসটির বৈশিষ্ট্য: নাম, দাম, অবস্থা)। Method-এর ঘর থাকে না। কে কোন কাজ করবে, সেটা design-এর সিদ্ধান্ত (পর্ব ২৯)।
  • দুটি বাক্সের মধ্যে রেখা = association; রেখার দুই প্রান্তে multiplicity — প্রতিটির সাথে অন্যটি কতগুলো থাকতে পারে: 1, 0..1, 0..* (বা *), 1..*.
  • Multiplicity পড়ার নিয়ম: যে প্রান্তে সংখ্যা লেখা, সেটা সেই class-এর সংখ্যা। Customer প্রান্তে 1 আর Order প্রান্তে 0..* মানে: “একজন গ্রাহকের শূন্য বা অনেক অর্ডার; প্রতিটি অর্ডার ঠিক একজন গ্রাহকের।”

Noun analysis — বিশেষ্য থেকে class-এর প্রার্থী

Concept কোথা থেকে খুঁজবেন? একটি পুরোনো কৌশল: requirement-এর লেখা থেকে বিশেষ্য (noun) তুলে নিন — সেগুলো class বা attribute-এর প্রার্থী; ক্রিয়া (verb) — use case বা কাজের প্রার্থী। Russell Abbott ১৯৮৩ সালে Communications of the ACM-এ ইংরেজি বর্ণনা থেকে program নকশার এই চিন্তা প্রকাশ করেন বলে সাধারণত উল্লেখ করা হয়; পরে Booch-সহ অনেকে একে জনপ্রিয় করেন। নিচের কোডটি একটি noun analysis-এর খেলনা সংস্করণ — আসল ভাষা-বিশ্লেষণ নয়, শুধু কয়েকটি সহজ নিয়ম: “a/an/the”-এর পরের এক-দুটি শব্দ একটি noun phrase, বাক্যের প্রথম noun phrase কর্তা (actor-এর প্রার্থী), আর ক্রিয়ার তালিকা হাতে দেওয়া।

// Toy noun/verb analysis of a fictional online-pharmacy problem statement.
// Heuristic only: a noun phrase = up to 2 words after "a/an/the";
// verbs come from a small hand-made list; a sentence's first noun phrase
// is its subject (actor candidate); "subject + verb + next noun" = use case.
const text = `A customer searches the catalogue and adds a medicine to the cart.
The customer uploads a prescription for a restricted medicine.
A pharmacist verifies the prescription. The customer places an order
and pays the order by mobile wallet. A rider delivers the order to the
delivery address. The system sends an SMS to the customer.`;
const DET = ["a", "an", "the"], STOP = ["and", "to", "for", "by", "of", "with"];
const VERBS = ["searches", "adds", "uploads", "verifies", "places", "pays", "delivers", "sends"];
const nouns = {}, actors = new Set(), useCases = [];
for (const s of text.toLowerCase().replace(/\n/g, " ").split(".")) {
  const w = s.split(/\s+/).filter(Boolean);
  let subject = null, verb = null;
  for (let i = 0; i < w.length; i++) {
    if (VERBS.includes(w[i])) { verb = w[i]; continue; }
    if (!DET.includes(w[i])) continue;
    const np = [w[i + 1]];                       // the head word after the determiner
    const nx = w[i + 2];
    if (nx && !STOP.includes(nx) && !DET.includes(nx) && !VERBS.includes(nx)) np.push(nx);
    const phrase = np.join(" ");
    nouns[phrase] = (nouns[phrase] || 0) + 1;
    if (!subject) subject = phrase;
    else if (verb) { useCases.push(verb + " " + phrase + "  (" + subject + ")"); verb = null; }
    i += np.length;
  }
  if (subject && subject !== "system") actors.add(subject);
}
console.log("Noun phrases -> candidate classes (count):");
console.log("  " + Object.entries(nouns).sort((a, b) => b[1] - a[1])
  .map(([n, c]) => n + " x" + c).join(", "));
console.log("Actor candidates: " + [...actors].join(", "));
console.log("Use-case candidates (verb + object, actor):");
useCases.forEach(u => console.log("  " + u));

আউটপুট:

Noun phrases -> candidate classes (count):
  customer x4, order x3, prescription x2, catalogue x1, medicine x1, cart x1, restricted medicine x1, pharmacist x1, rider x1, delivery address x1, system x1, sms x1
Actor candidates: customer, pharmacist, rider
Use-case candidates (verb + object, actor):
  searches catalogue  (customer)
  adds medicine  (customer)
  uploads prescription  (customer)
  verifies prescription  (pharmacist)
  places order  (customer)
  pays order  (customer)
  delivers order  (rider)
  sends sms  (system)

প্রার্থী পাওয়া গেল ১২টি noun phrase, ৩ জন actor আর ৮টি use case-এর প্রার্থী। কিন্তু তালিকাটি শুরু, উত্তর নয়। খেয়াল করুন: (১) system একটি class নয় — এটি আমাদের boundary; (২) sms একটি মাধ্যম; (৩) restricted medicine আর medicine একই ধারণা, পার্থক্যটা একটি attribute (needsRx); (৪) delivery address সম্ভবত অর্ডারের একটি attribute; (৫) “mobile wallet”-এর আগে article নেই বলে নিয়মটি সেটি পুরোপুরি ধরতেই পারেনি — অথচ এর পেছনে আছে পেমেন্ট গেটওয়ে, একটি secondary actor! আর sends sms (system) কোনো actor-এর লক্ষ্য নয়, অন্য use case-এর একটি ধাপ। সবচেয়ে বড় শিক্ষা: যা লেখায় নেই, যেমন cart-এর প্রতিটি সারির পরিমাণ (quantity), noun analysis তা কখনো খুঁজে দেবে না। text বদলে নিজের একটি বর্ণনা দিয়ে চালিয়ে দেখুন।

এবার প্রার্থীর তালিকা থেকে মানুষের বিচার দিয়ে domain model বানাই:

প্রার্থী থেকে model
কাজ
তালিকা
Class
Attribute
Association
  1. প্রার্থী তোলা। কোডের আউটপুটের ১২টি noun phrase. এখনো কোনো বিচার নেই — শুধু তালিকা।
  2. যা concept নয়, তা বাদ। system আমাদের boundary নিজেই; sms জানানোর একটি মাধ্যম; catalogue এখানে “সব ওষুধের তালিকা” অর্থে, আলাদা তথ্য রাখে না। (বড় system-এ catalogue-এর নিজস্ব attribute থাকলে সেটি class হতেই পারে — প্রসঙ্গ দেখে সিদ্ধান্ত।) Cart আপাতত রাখা হলো, পরের ধাপে দেখব। Pharmacist আর rider actor; তাদের তথ্য আলাদা করে রাখতে হবে কি না, পরে ঠিক হবে — এই ছোট model-এ বাদ।
  3. এক করা আর attribute-এ নামানো। “restricted medicine” আলাদা class নয়, একই Medicine — শুধু needsRx সত্য। “delivery address”-এর আলাদা পরিচয় বা আচরণ এখানে নেই, তাই Order-এর একটি attribute. নিয়ম: যার নিজের পরিচয় (identity) আর একাধিক বৈশিষ্ট্য আছে, সেটা class; যা একটি মাত্র মান, সেটা attribute.
  4. লুকানো ধারণা। Cart-এ “৩ পাতা প্যারাসিটামল, ১ বোতল সিরাপ” — প্রতিটি সারিতে একটি ওষুধ আর একটি পরিমাণ। পরিমাণটি Medicine-এর নয়, Order-এরও নয়, এই সারিটির। তাই নতুন class OrderLine. Cart আসলে এখনো জমা না দেওয়া একটি Order, তাই আলাদা class নয়। Noun analysis এটি দেয়নি — এসেছে আলোচনা থেকে।
  5. Attribute বসানো। শুধু ব্যবসার কাছে অর্থবহ বৈশিষ্ট্য। খেয়াল করুন, Order-এ customerId নেই — সেই সম্পর্ক association দিয়ে দেখানো হয়, foreign key দিয়ে নয়। unitPrice OrderLine-এ আছে, কারণ কেনার দিনের দাম পরে ওষুধের দাম বদলালেও বদলাবে না।
  6. Association আর multiplicity. প্রতিটি রেখার দুই প্রান্তে প্রশ্ন: “একটির সাথে অন্যটি কতগুলো?” একটি অর্ডারে অন্তত একটি সারি (1..*); একটি অর্ডারে প্রেসক্রিপশন থাকতেও পারে, না-ও (0..1). পুরো ছবি নিচে।

অনলাইন ফার্মেসির domain model (কাল্পনিক)

Conceptual classes with attributes, associations and multiplicities — no operations

অনলাইন ফার্মেসির domain model CustomerOrder PrescriptionOrderLine Medicine namephone orderNostatusdeliveryAddress issuedBystatus quantityunitPrice namepriceneedsRx 10..* 11..* 0..*1 10..* 0..*0..1
ছবি ২৩.২ — কোনো method নেই, কোনো foreign key নেই। Multiplicity পড়ুন প্রান্ত ধরে: “একটি OrderLine ঠিক ১টি Medicine-এর; একটি Medicine শূন্য বা অনেক OrderLine-এ।” Aggregation, composition আর navigability-র মতো সূক্ষ্ম notation পর্ব ২৯-এ।

Domain model-এর চেনা ভুল: (১) placeOrder() জাতীয় method লেখা — এটি design-এর সিদ্ধান্ত আগেভাগে নেওয়া; (২) OrderManager, PharmacySystem, LoginScreen জাতীয় software-এর অংশকে concept বানানো; (৩) association-এর বদলে customerId attribute; (৪) multiplicity বাদ দেওয়া — অথচ “একটি অর্ডারে কয়টি প্রেসক্রিপশন?” প্রশ্নটিই ব্যবসার নিয়ম খুঁজে বের করে। Database কোর্সের ER modeling দেখতে অনেকটা একই রকম, কিন্তু উদ্দেশ্য আলাদা: ER diagram table-এর নকশার দিকে যায়, domain model বোঝাপড়ার দিকে।

৫ · Context diagram আর DFD — data কোথা থেকে কোথায় যায়

Object-oriented model আসার আগে, ১৯৭০-এর দশকে structured analysis ধারা গড়ে ওঠে। এর মূল ছবি data flow diagram (DFD). সাধারণভাবে Tom DeMarco-র Structured Analysis and System Specification (১৯৭৮) আর Edward Yourdon ও Larry Constantine-এর কাজকে এর ভিত্তি ধরা হয়; Chris Gane ও Trish Sarson ১৯৭৯ সালের দিকে একটু ভিন্ন নোটেশন দেন। Yourdon-এর Modern Structured Analysis (১৯৮৯) পরে ধারাটিকে হালনাগাদ করে। DFD-র চারটি উপাদান:

উপাদানYourdon/DeMarco নোটেশনGane–Sarson নোটেশনঅর্থ
External entityআয়তক্ষেত্রআয়তক্ষেত্র (প্রায়ই ছায়াসহ)System-এর বাইরের উৎস বা গন্তব্য
Processবৃত্তগোল কোণের আয়তক্ষেত্রযা data-কে বদলায়; নাম ক্রিয়াবাচক, নম্বরসহ (১, ২, ২.১)
Data storeদুটি সমান্তরাল রেখাএক দিক খোলা আয়তক্ষেত্রযেখানে data জমা থাকে (অর্ডারের খাতা)
Data flowনাম লেখা তীরনাম লেখা তীরকোন data কোন দিকে চলে

সবচেয়ে ওপরের স্তর context diagram: পুরো system একটিমাত্র process, চারপাশে external entity আর তাদের data flow. কোনো data store থাকে না। এটি system-এর সীমা এক নজরে দেখায় — use case diagram-এর boundary-র মতোই, কিন্তু data-র চোখে।

Context diagram — অনলাইন ফার্মেসি (কাল্পনিক)

One process, four external entities, named data flows (Yourdon–DeMarco style)

অনলাইন ফার্মেসির context diagram ০ অনলাইন ফার্মেসি সিস্টেম গ্রাহকফার্মাসিস্ট রাইডারপেমেন্ট গেটওয়ে অর্ডার, Rxরসিদ, অবস্থা যাচাই-অনুরোধসিদ্ধান্ত ডেলিভারির কাজপৌঁছানোর খবর পেমেন্ট অনুরোধনিশ্চিতকরণ
ছবি ২৩.৩ — প্রতিটি তীর একটি নির্দিষ্ট data, নামসহ। কোন তীর আগে, কোনটা পরে — DFD তা বলে না; ক্রম দেখাতে activity diagram.

পরের স্তরে বৃত্তটি “খোলা” হয়: একটি DFD-তে কয়েকটি process, তাদের মধ্যে data flow আর data store. আমাদের উদাহরণে প্রথম ভাঙনটি মোটামুটি এমন:

Processঢোকেবেরোয়Data store
১ অর্ডার গ্রহণগ্রাহক থেকে অর্ডাররসিদ (গ্রাহকে), যাচাই-অনুরোধ (২-এ)D1 অর্ডার (লেখে)
২ প্রেসক্রিপশন যাচাইগ্রাহক থেকে Rx, ১ থেকে অনুরোধ, ফার্মাসিস্ট থেকে সিদ্ধান্তযাচাই-অনুরোধ (ফার্মাসিস্টে), ফল (D1-এ)D2 প্রেসক্রিপশন (লেখে)
৩ পেমেন্টD1 থেকে বকেয়া, গেটওয়ে থেকে নিশ্চিতকরণপেমেন্ট অনুরোধ (গেটওয়েতে)D1 (হালনাগাদ)
৪ ডেলিভারিD1 থেকে পরিশোধিত অর্ডার, রাইডার থেকে খবরডেলিভারির কাজ (রাইডারে), অবস্থা (গ্রাহকে)D1 (হালনাগাদ)

Level-এর নাম বই-ভেদে আলাদা। অনেক বইয়ে context diagram-কেই “level 0” বলা হয়, আর ওপরের টেবিলের ভাঙনটি “level 1”. আবার অনেক বইয়ে context diagram আলাদা নামে থাকে, আর প্রথম ভাঙনটিকেই “level 0 DFD” বলা হয়। পরীক্ষায় বা দলে কোন রীতি মানা হচ্ছে, সেটা প্রথমে জেনে নিন — ধারণাটি দুই রীতিতেই এক।

DFD-র কয়েকটি নিয়ম, যা ভাঙলে ছবিটি ভুল:

  • প্রতিটি process-এ অন্তত একটি ঢোকা আর একটি বেরোনো flow. শুধু ঢোকে, বেরোয় না — একে বলে “black hole”; শুধু বেরোয় — “miracle”.
  • Data store থেকে data store, বা entity থেকে সরাসরি data store-এ flow নয়। মাঝে একটি process থাকতেই হবে, কারণ data নিজে থেকে চলে না।
  • Balancing: একটি process ভাঙলে নিচের স্তরে বাইরে থেকে ঢোকা-বেরোনো flow ওপরের স্তরের সাথে মিলতে হবে। Context diagram-এ গ্রাহক থেকে “অর্ডার, Rx” ঢুকেছে; level 1-এও সেই দুটি flow থাকতে হবে।
  • DFD flowchart নয়। এতে সিদ্ধান্তের হীরা (diamond) বা loop নেই। “যদি প্রেসক্রিপশন লাগে” — এটা DFD-র ভাষা নয়।

৬ · Activity diagram — ধারা, সিদ্ধান্ত আর দায়িত্ব

DFD ক্রম দেখায় না; use case diagram-ও না। কাজটি কোন ধাপে, কোন শর্তে, কার হাত ঘুরে এগোয় — এটা দেখাতে activity diagram. দেখতে flowchart-এর মতো, কিন্তু UML ২-তে এর অর্থ token-এর প্রবাহ দিয়ে সংজ্ঞায়িত (Petri net-এর অনুপ্রেরণায়, বলে সাধারণত বলা হয়): একটি token চলতে চলতে action ঘুরে যায়। Requirement-এর স্তরে এটি সবচেয়ে কাজে লাগে একটি ব্যবসার প্রক্রিয়া আঁকতে — যেমন অর্ডার থেকে ডেলিভারি।

উপাদাননোটেশনঅর্থ
Initial nodeভরাট কালো বৃত্তএখান থেকে শুরু
Actionগোল কোণের আয়তক্ষেত্রএকটি ধাপ, ক্রিয়াবাচক নাম
Decision / mergeহীরা (diamond)Decision: একটি পথ বেছে নেওয়া, প্রতিটি বেরোনো তীরে [guard]; merge: বিকল্প পথগুলো আবার এক হওয়া
Fork / joinমোটা দণ্ডFork: একসাথে একাধিক পথ শুরু (সমান্তরাল); join: সবগুলো শেষ হলে তবেই এগোনো
Activity finalবৃত্তের ভেতরে ভরাট বৃত্তপুরো activity শেষ
Flow finalবৃত্তের ভেতরে ×শুধু এই পথটি শেষ, বাকিগুলো চলতে থাকে
Swimlane (partition)খাড়া বা আড়াআড়ি ভাগকোন ধাপ কার দায়িত্ব

“অর্ডার থেকে ডেলিভারি” — swimlane-সহ activity diagram

Initial and final nodes, decisions with guards, a merge, fork/join, three partitions

অর্ডার থেকে ডেলিভারির activity diagram গ্রাহকসিস্টেমফার্মাসিস্ট অর্ডার জমা প্রেসক্রিপশনযাচাই পেমেন্ট নেওয়া রাইডার ঠিক করা প্যাক করা ওষুধ গ্রহণ [লাগে] [লাগে না] [বাতিল] [ঠিক] fork join
ছবি ২৩.৪ — ওপরের হীরা দুটি decision (প্রতিটি বেরোনো তীরে guard), মাঝের হীরাটি merge. Fork-এর পরে প্যাক আর রাইডার ঠিক করা একসাথে চলে; join দুটোরই অপেক্ষা করে।

Decision-এর guard সম্পূর্ণ আর পরস্পর-বর্জিত হওয়া চাই। [লাগে] আর [লাগে না] — একসাথে দুটো সত্য হতে পারে না, আর কোনো অবস্থা বাদও পড়ে না। [দাম > ৫০০] আর [দাম < ৫০০] লিখলে ঠিক ৫০০-তে কোনো পথ নেই — এমন ফাঁক activity diagram চোখে আনে, যা লেখায় সহজে চোখ এড়ায়। আর merge আর join গুলিয়ে ফেলবেন না: merge অপেক্ষা করে না, join অপেক্ষা করে।

Path গোনা — test case পরিকল্পনার শুরু

একটি activity diagram-এর শুরু থেকে শেষ পর্যন্ত যতগুলো আলাদা পথ, প্রতিটি অন্তত একটি acceptance test-এর প্রার্থী। Fork নতুন পথ বানায় না (দুই শাখা সবসময় একসাথে চলে), কিন্তু decision বানায়। নিচের কোডে ছবি ২৩.৪-এর ধারা, সাথে একটি বাস্তব জটিলতা যোগ করা হয়েছে: পেমেন্ট ব্যর্থ হলে আবার চেষ্টা (loop). Loop-কে অসীম পথ বানানো থেকে আটকাতে নিয়ম — প্রতিটি guard-যুক্ত তীর একটি পথে বড়জোর একবার।

// Activity "Place order": count start-to-end paths for test planning.
// Decision edges carry [guards]; each guarded edge is taken at most once
// (so the retry loop runs at most once);
// a fork runs all branches in parallel, so it adds no extra path.
const G = {
  start: ["search"], search: ["addToCart"], addToCart: ["needRx?"],
  "needRx?": [["M", "no"], ["upload", "yes"]],
  upload: ["verify"], verify: ["ok?"],
  "ok?": [["notifyReject", "rejected"], ["M", "approved"]],
  notifyReject: ["endCancelled"],
  M: ["pay"], pay: ["paid?"],                       // M = merge node
  "paid?": [["pay", "failed: retry"], ["fork", "ok"]],
  fork: { parallel: ["pack", "assignRider"], join: "deliver" },
  deliver: ["end"]
};
const paths = [];
function walk(node, path, used) {
  if (node.startsWith("end")) { paths.push(path.concat(node)); return; }
  const out = G[node];
  if (out.parallel) return walk(out.join, path.concat("fork(" + out.parallel.join(" || ") + ")"), used);
  for (const o of out) {
    const [to, guard] = Array.isArray(o) ? o : [o, null];
    const edge = node + ">" + to;
    if (used.has(edge)) continue;                    // each [guard] edge at most once
    const step = guard ? node + "[" + guard + "]" : node;
    walk(to, path.concat(step), guard ? new Set(used).add(edge) : used);
  }
}
walk("start", [], new Set());
paths.forEach((p, i) => console.log("P" + (i + 1) + ": " + p.filter(s => /\[|fork|end/.test(s)).join(" -> ")));
const decisions = Object.values(G).filter(v => Array.isArray(v) && Array.isArray(v[0])).length;
console.log("paths: " + paths.length + ", decision nodes: " + decisions +
  ", basis paths (decisions + 1): " + (decisions + 1));

আউটপুট:

P1: needRx?[no] -> paid?[failed: retry] -> paid?[ok] -> fork(pack || assignRider) -> end
P2: needRx?[no] -> paid?[ok] -> fork(pack || assignRider) -> end
P3: needRx?[yes] -> ok?[rejected] -> endCancelled
P4: needRx?[yes] -> ok?[approved] -> paid?[failed: retry] -> paid?[ok] -> fork(pack || assignRider) -> end
P5: needRx?[yes] -> ok?[approved] -> paid?[ok] -> fork(pack || assignRider) -> end
paths: 5, decision nodes: 3, basis paths (decisions + 1): 4

পাঁচটি পথ, মানে পাঁচটি acceptance test-এর প্রার্থী: Rx লাগে না আর এক চেষ্টায় পেমেন্ট (P2), Rx লাগে না কিন্তু প্রথম পেমেন্ট ব্যর্থ (P1), প্রেসক্রিপশন বাতিল (P3), আর অনুমোদনের পরে পেমেন্টের দুই রূপ (P4, P5). খেয়াল করুন, fork প্রতিটি পথে একবারই আছে — সংখ্যা বাড়ায়নি। শেষ লাইনে তুলনা: তিনটি দুই-মুখী decision থাকলে McCabe-এর cyclomatic complexity ৩ + ১ = ৪, অর্থাৎ ৪টি “basis path” দিয়েই প্রতিটি তীর অন্তত একবার ছোঁয়া যায়। পূর্ণ পথ (৫) আর basis path (৪) আলাদা জিনিস; loop যত বেশিবার ঘুরতে দেবেন, পূর্ণ পথ তত বাড়বে। এই হিসাবের নিয়ম পর্ব ৫০-এ, আর test case নকশার কৌশল পর্ব ৪৭-এ।

৭ · State machine diagram — একটি অর্ডারের জীবনচক্র

Activity diagram একটি প্রক্রিয়া দেখায়, যেখানে অনেক জিনিস জড়িত। State machine diagram দেখায় একটিমাত্র জিনিসের জীবন: সে কোন কোন state-এ থাকতে পারে, আর কোন event-এ, কোন শর্তে এক state থেকে আরেকটিতে যায়। এর ভিত্তি David Harel-এর statecharts (১৯৮৭ সালের প্রবন্ধ), যা তিনি একটি বিমানের avionics system-এর জটিল আচরণ বর্ণনা করতে গিয়ে গড়ে তোলেন বলে নিজে লিখেছেন।

প্রতিটি transition-এর লেবেলের পূর্ণ রূপ: event [guard] / effect.

  • Event: যা ঘটে — pay, ship, cancel.
  • Guard: বন্ধনীর ভেতরের শর্ত; event এলেও guard মিথ্যা হলে transition হয় না।
  • Effect: transition-এর সময় যে কাজ হয় — / refund.

একটি অর্ডারের state machine (কাল্পনিক ফার্মেসি)

Initial and final states, events, [guards], / effects and an internal transition

অর্ডারের state machine diagram PlacedPaid ShippedDelivered Cancelled অর্ডার হয়েছেটাকা পাওয়া গেছে পাঠানো হয়েছেপৌঁছেছে বাতিল verifyRx / rxOk := true pay[amount = total] ship[not needsRx or rxOk] deliver cancel cancel /refund
ছবি ২৩.৫ — Paid-এর ভেতরে লেখা verifyRx / rxOk := true একটি internal transition: state বদলায় না। Shipped থেকে cancel-এর তীর নেই — অর্থাৎ পাঠানোর পরে বাতিল করা যায় না। এটা কি ব্যবসার আসল নিয়ম? Diagram প্রশ্নটি তুলে দিল।

এবার একটি অর্ডারকে event-এর একটি ক্রমের ভেতর দিয়ে চালাই। অর্ডারের দাম ৪৫০ টাকা, প্রেসক্রিপশন লাগে।

অর্ডার: total 450, needsRx true
Event
Guard
ফল
State
rxOk
  1. শুরু। কালো বিন্দু থেকে অর্ডার সরাসরি Placed-এ। দাম ৪৫০, প্রেসক্রিপশন লাগে, এখনো যাচাই হয়নি (rxOk false).
  2. pay(400). Placed থেকে pay-এর একটি তীর আছে, কিন্তু guard [amount = total]: ৪০০ ≠ ৪৫০, মিথ্যা। Transition হয় না, অর্ডার Placed-এই থাকে। Guard না থাকলে কম টাকায় অর্ডার “Paid” হয়ে যেত।
  3. pay(450). এবার guard সত্য। Placed → Paid.
  4. ship. Paid থেকে ship-এর তীর আছে, guard [not needsRx or rxOk]. needsRx সত্য আর rxOk মিথ্যা, তাই পুরো শর্ত মিথ্যা — প্রেসক্রিপশন যাচাই ছাড়া ওষুধ পাঠানো যাবে না। অর্ডার Paid-এই থাকে।
  5. verifyRx. ফার্মাসিস্ট যাচাই করলেন। এটি Paid-এর internal transition: state একই থাকে, শুধু effect চলে — rxOk এখন true.
  6. ship. একই event, কিন্তু এবার guard সত্য। Paid → Shipped. একই event-এর ফল state আর data-র ওপর নির্ভর করে — state machine-এর মূল শক্তি এটাই।
  7. cancel. Shipped থেকে cancel-এর কোনো তীরই নেই। Event প্রত্যাখ্যাত (UML-এর ভাষায়, এই state-এ event-টি কোনো transition চালায় না, তাই হারিয়ে যায়)। ব্যবসার প্রশ্ন: রাইডার রওনা দেওয়ার পরে গ্রাহক মত বদলালে কী হবে? হয়তো একটি “Returned” state লাগবে — পর্ব ২৪-এ এমন requirement যাচাইয়ের কৌশল আসবে।
  8. deliver. Shipped → Delivered, তারপর final state. জীবনচক্র শেষ।

একই নিয়ম কোডে একটি transition table হিসেবে লেখা যায়। নিচের প্রোগ্রামে ছবি ২৩.৫-এর প্রতিটি তীর একটি সারি; তিনটি event-ক্রম চালানো হয়েছে:

// Order lifecycle state machine: event [guard] / effect. Unknown event = rejected.
const T = {
  Placed:  { pay:     { to: "Paid", guard: (o, ev) => ev.amount === o.total, why: "amount != total" },
             cancel:  { to: "Cancelled" } },
  Paid:    { verifyRx: { to: "Paid", effect: o => { o.rxOk = true; } },   // internal: state stays
             ship:    { to: "Shipped", guard: o => !o.needsRx || o.rxOk, why: "prescription not verified" },
             cancel:  { to: "Cancelled", effect: o => { o.refund = o.total; } } },
  Shipped: { deliver: { to: "Delivered" } },
  Delivered: {}, Cancelled: {}                                             // final states
};
function run(name, order, events) {
  let s = "Placed";
  console.log("== " + name + " (total " + order.total + ", needsRx " + order.needsRx + ")");
  for (const ev of events) {
    const t = T[s][ev.e];
    if (!t) { console.log("  REJECT " + ev.e + " in " + s + ": no such transition"); continue; }
    if (t.guard && !t.guard(order, ev)) { console.log("  REJECT " + ev.e + " in " + s + ": guard false (" + t.why + ")"); continue; }
    if (t.effect) t.effect(order);
    console.log("  " + s + " --" + ev.e + "--> " + t.to);
    s = t.to;
  }
  console.log("  final: " + s + (order.refund ? ", refund " + order.refund : ""));
}
run("A: happy path", { total: 450, needsRx: true },
  [{ e: "pay", amount: 450 }, { e: "verifyRx" }, { e: "ship" }, { e: "deliver" }]);
run("B: guards stop it", { total: 450, needsRx: true },
  [{ e: "pay", amount: 400 }, { e: "pay", amount: 450 }, { e: "ship" }, { e: "cancel" }]);
run("C: too late to cancel", { total: 120, needsRx: false },
  [{ e: "pay", amount: 120 }, { e: "ship" }, { e: "cancel" }, { e: "deliver" }, { e: "deliver" }]);

আউটপুট:

== A: happy path (total 450, needsRx true)
  Placed --pay--> Paid
  Paid --verifyRx--> Paid
  Paid --ship--> Shipped
  Shipped --deliver--> Delivered
  final: Delivered
== B: guards stop it (total 450, needsRx true)
  REJECT pay in Placed: guard false (amount != total)
  Placed --pay--> Paid
  REJECT ship in Paid: guard false (prescription not verified)
  Paid --cancel--> Cancelled
  final: Cancelled, refund 450
== C: too late to cancel (total 120, needsRx false)
  Placed --pay--> Paid
  Paid --ship--> Shipped
  REJECT cancel in Shipped: no such transition
  Shipped --deliver--> Delivered
  REJECT deliver in Delivered: no such transition
  final: Delivered

A: স্বাভাবিক পথ; verifyRx state বদলায়নি (Paid → Paid), শুধু rxOk বদলেছে। B: দুটি guard কাজ করেছে — কম টাকায় পেমেন্ট আর যাচাই ছাড়া পাঠানো দুটোই আটকেছে; তারপর Paid অবস্থায় cancel হওয়ায় effect চলে refund 450. C: প্রেসক্রিপশন লাগে না, তাই প্রথম ship-ই সফল; Shipped-এ cancel প্রত্যাখ্যাত; Delivered একটি final state, তাই দ্বিতীয় deliver-ও প্রত্যাখ্যাত। খেয়াল করুন, “প্রত্যাখ্যাত” দুই রকম: তীরই নেই আর তীর আছে কিন্তু guard মিথ্যা — test-এ দুটোই আলাদা করে যাচাই করা উচিত। কোডে আমরা verifyRx-কে internal transition হিসেবে নিয়েছি; UML-এ self-transition (state থেকে বেরিয়ে আবার ঢোকা) আলাদা জিনিস, কারণ তাতে state-এর entry/exit কাজ আবার চলে।

State machine কাগজে না এঁকে লেখা দিয়েও বানানো যায়, যা Git-এ রাখা আর pull request-এ review করা সহজ (পর্ব ১৬)। PlantUML নামের একটি জনপ্রিয় open-source tool-এ ওপরের ছবিটি মোটামুটি এভাবে লেখা হয়:

@startuml
[*] --> Placed
Placed --> Paid : pay [amount = total]
Placed --> Cancelled : cancel
Paid : verifyRx / rxOk := true
Paid --> Shipped : ship [not needsRx or rxOk]
Paid --> Cancelled : cancel / refund
Shipped --> Delivered : deliver
Delivered --> [*]
Cancelled --> [*]
@enduml

Operating systems কোর্সে process-এর state diagram (Process) একই ধারণার আরেকটি পরিচিত উদাহরণ। আর কোডে state machine বানানোর design pattern আসবে পর্ব ৩৬-এ; sequence diagram আর state machine-এর বিস্তারিত (nested state, history) পর্ব ৩০-এ।

৮ · CRUD matrix — model দিয়ে model যাচাই

প্রতিটি model আলাদাভাবে ঠিক দেখাতে পারে, অথচ একসাথে মেলে না। Domain model-এ “Payment” আছে, কিন্তু কোনো use case কি কখনো Payment পড়ে? ওষুধের তালিকা কে তৈরি করে? এই প্রশ্নের একটি পুরোনো আর কার্যকর হাতিয়ার CRUD matrix: সারিতে use case (বা process), কলামে entity, আর প্রতিটি ঘরে সেই use case entity-টিকে কী করে — C (create, তৈরি), R (read, পড়া), U (update, বদলানো), D (delete, মোছা)।

মুদি দোকানের হিসাবের খাতার কথা ভাবুন। প্রতিটি পণ্যের জন্য প্রশ্ন: কে খাতায় প্রথম তোলে (C)? কে দেখে দাম বলে (R)? কে স্টক বদলায় (U)? কে তালিকা থেকে বাদ দেয় (D)? কোনো পণ্য খাতায় কেউ কখনো তোলে না, অথচ বিক্রি হয় — তাহলে কোথাও একটি কাজ বাদ পড়েছে।

// CRUD matrix: use cases (rows) x entities (columns). C=create R=read U=update D=delete.
const entities = ["Customer", "Medicine", "Prescription", "Order", "Payment", "Delivery"];
const m = {
  "Register":            { Customer: "C" },
  "Search catalogue":    { Medicine: "R" },
  "Upload prescription": { Customer: "R", Prescription: "C" },
  "Verify prescription": { Prescription: "RU" },
  "Place order":         { Customer: "R", Medicine: "R", Prescription: "R", Order: "C" },
  "Pay for order":       { Order: "RU", Payment: "C" },
  "Assign rider":        { Order: "R", Delivery: "C" },
  "Track order":         { Order: "R", Delivery: "R" },
  "Mark delivered":      { Order: "U", Delivery: "U" },
  "Send reminder SMS":   {}
};
const pad = (s, n) => String(s).padEnd(n);
console.log(pad("use case", 20) + entities.map(e => pad(e.slice(0, 5), 6)).join("").trimEnd());
for (const [uc, row] of Object.entries(m))
  console.log(pad(uc, 20) + entities.map(e => pad(row[e] || ".", 6)).join("").trimEnd());
console.log("--- checks ---");
const never = { U: [], D: [] };
for (const e of entities) {
  const ops = Object.values(m).map(r => r[e] || "").join("");
  if (!ops.includes("C")) console.log("PROBLEM " + e + " is never created");
  if (!ops.includes("R")) console.log("PROBLEM " + e + " is never read");
  for (const op of ["U", "D"]) if (!ops.includes(op)) never[op].push(e);
}
console.log("note: never updated: " + never.U.join(", "));
console.log("note: never deleted: " + never.D.join(", "));
for (const [uc, row] of Object.entries(m))
  if (Object.keys(row).length === 0) console.log("PROBLEM use case '" + uc + "' touches no entity");

আউটপুট:

use case            Custo Medic Presc Order Payme Deliv
Register            C     .     .     .     .     .
Search catalogue    .     R     .     .     .     .
Upload prescription R     .     C     .     .     .
Verify prescription .     .     RU    .     .     .
Place order         R     R     R     C     .     .
Pay for order       .     .     .     RU    C     .
Assign rider        .     .     .     R     .     C
Track order         .     .     .     R     .     R
Mark delivered      .     .     .     U     .     U
Send reminder SMS   .     .     .     .     .     .
--- checks ---
PROBLEM Medicine is never created
PROBLEM Payment is never read
note: never updated: Customer, Medicine, Payment
note: never deleted: Customer, Medicine, Prescription, Order, Payment, Delivery
PROBLEM use case 'Send reminder SMS' touches no entity

তিনটি সত্যিকারের ফাঁক বেরোল। Medicine কখনো তৈরি হয় না — catalogue-এ ওষুধ যোগ করার কোনো use case নেই; একটি নতুন actor (catalogue manager বা ফার্মেসির প্রশাসক) আর “catalogue হালনাগাদ” use case লাগবে। এতে “Medicine কখনো update হয় না” noteটিও মিটবে, কারণ দাম বদলানোও সেখানেই। Payment কেউ পড়ে না — তাহলে refund, হিসাব মেলানো বা গ্রাহকের রসিদ কোথা থেকে আসবে? হয় একটি use case বাদ পড়েছে, নয়তো Payment আলাদা entity হওয়ার দরকারই নেই। “Send reminder SMS” কোনো entity ছোঁয় না — কাকে, কোন অর্ডারের জন্য বার্তা? এটা হয় ভুল করে বাদ পড়া ঘর, নয়তো আসলে অন্য use case-এর একটি ধাপ (অংশ ৪-এর noun analysis-ও একই কথা বলেছিল)। আর “কিছুই delete হয় না” — এটা ভুল নয়, প্রায়ই সঠিক: অর্ডার আর পেমেন্টের ইতিহাস আইন আর হিসাবের জন্য রাখতে হয়। তবে গ্রাহক নিজের account মুছতে চাইলে কী হবে — এটা একটি privacy requirement-এর প্রশ্ন, যা পর্ব ২১-এর non-functional requirement-এ ফিরে যায়।

CRUD matrix ছাড়াও model-গুলো পরস্পরকে যাচাই করে: use case diagram-এর প্রতিটি use case কি activity diagram-এর কোনো ধারায় আছে? State machine-এর প্রতিটি event কি কোনো use case থেকে আসে? Domain model-এর প্রতিটি class কি context diagram-এর কোনো data flow-এ বা data store-এ দেখা দেয়? এই মিলিয়ে দেখা পর্ব ২৪-এর traceability-র একটি ছোট রূপ।

৯ · Tool আর “just enough” modeling

Model আঁকার tool তিন রকম (নাম আর সুবিধা লেখার সময়ের; সংস্করণ-ভেদে বদলায়):

হোয়াইটবোর্ড আর কাগজ

সবচেয়ে দ্রুত, আলোচনার জন্য সেরা। ছবি তুলে ticket-এ জুড়ে দিলেই যথেষ্ট অনেক সময়।

sketch

লেখা থেকে ছবি

PlantUML, Mermaid — diagram একটি text file, Git-এ diff আর review করা যায়। কোন ধরনের diagram কোন tool সমর্থন করে, তার documentation দেখে নিন।

diagrams as code

আঁকার tool

draw.io (diagrams.net) জাতীয় সাধারণ আঁকার tool, বা পূর্ণ UML modeling tool — যেগুলো model-এর ভেতরের সামঞ্জস্যও যাচাই করে।

drawing / CASE

কতটা model বানাবেন? Waterfall-এর যুগে কিছু project-এ শত শত পাতার model হতো, যা কোড শুরুর আগেই পুরোনো হয়ে যেত। Agile-এর প্রতিক্রিয়ায় Scott Ambler-এর Agile Modeling (২০০২ সালের বই) একটি মধ্যপথ দেয়: model বানান একটি উদ্দেশ্যে, “just barely good enough” পর্যন্ত, আর যা আর কাজে লাগে না তা ফেলে দিতে ভয় পাবেন না। কয়েকটি ব্যবহারিক নিয়ম:

  • প্রশ্ন থেকে শুরু করুন। “অর্ডারের বাতিলের নিয়ম পরিষ্কার নয়” — তাহলে শুধু অর্ডারের state machine; পুরো system-এর সব diagram নয়।
  • জটিল আর ঝুঁকিপূর্ণ জায়গায় বেশি। টাকা, ওষুধ, আইন — এখানে state machine আর guard সতর্কভাবে। একটি সাধারণ “প্রোফাইল দেখা” page-এর কোনো diagram লাগে না।
  • কে রাখবে, ঠিক করুন। যে model কোডের সাথে হালনাগাদ রাখা হবে (যেমন অর্ডারের state machine), সেটি repository-তে text হিসেবে রাখুন; বাকিগুলো আলোচনার পরে ছবি তুলে রেখে দিন, “ঐতিহাসিক” চিহ্ন দিয়ে।
  • User story-র পাশে। একটি epic-এর জন্য একটি ছোট activity diagram, বা একটি story-র acceptance criteria-র পাশে একটি state টেবিল প্রায়ই পুরো দলের তর্ক কমায়।

আরও গভীরে: Martin Fowler-এর UML Distilled (তৃতীয় সংস্করণ ২০০৩) UML ব্যবহারের তিনটি ধরন আলাদা করে — sketch, blueprint আর programming language — আর বেশিরভাগ দলের জন্য sketch-এর পক্ষে যুক্তি দেয়। Alistair Cockburn-এর Writing Effective Use Cases (২০০০) include/extend-এর অতিরিক্ত ব্যবহারে সতর্ক করে আর লেখা use case-কেই মূল ধরে। Requirement-এর মানদণ্ড ISO/IEC/IEEE 29148 (পর্ব ২১) কোনো নির্দিষ্ট diagram বাধ্যতামূলক করে না; কোন model ব্যবহার হবে, তা project আর দলের সিদ্ধান্ত। Eric Evans-এর Domain-Driven Design (২০০৩) domain model-কে বোঝাপড়ার “ubiquitous language” হিসেবে আরও গভীরে নিয়ে যায় (পর্ব ৪০-এ এর ধারণা)।

পরীক্ষা ও ইন্টারভিউতে:

  • «include» আর «extend»-এর পার্থক্য কী? — Include প্রতিবার ঘটে, তীর base থেকে included-এ; extend শর্তে, extension point-এ, তীর extension থেকে base-এ।
  • Domain model-এ method লেখা হয় না কেন? — এটি সমস্যার জগতের ধারণা দেখায়; দায়িত্ব কোন class-কে দেওয়া হবে, তা design-এর সিদ্ধান্ত।
  • DFD আর flowchart-এর পার্থক্য? — DFD data-র প্রবাহ আর জমা দেখায়, ক্রম বা সিদ্ধান্ত নয়; flowchart নিয়ন্ত্রণের ক্রম দেখায়।
  • Activity diagram-এ merge আর join-এর পার্থক্য? — Merge বিকল্প পথ এক করে, অপেক্ষা করে না; join সমান্তরাল সব পথের শেষ হওয়ার অপেক্ষা করে।
  • State machine-এ guard কী? — Transition-এর বন্ধনীতে লেখা শর্ত; event এলেও শর্ত মিথ্যা হলে state বদলায় না।

১০ · AI-এর যুগে — AI-এর আঁকা model যাচাই

এখন একটি requirement-এর লেখা AI-কে দিলেই কয়েক সেকেন্ডে PlantUML বা Mermaid-এর কোড চলে আসে, যা ছবি হয়ে খুব পেশাদার দেখায়। কিন্তু দেখতে ঠিক আর notation-এ ঠিক এক নয়। এই বিষয়ে AI-এর চেনা ভুলগুলো:

AI-এর ভুলকীভাবে ধরবেন
«include»/«extend»-এর তীর উল্টো, বা ঐচ্ছিক কাজে includeপ্রতিটি তীরে প্রশ্ন: এটা কি প্রতিবার ঘটে? কে কাকে জানে?
Domain model-এ method, foreign key, Manager/Service classপ্রতিটি class-কে জিজ্ঞেস করুন: ব্যবসার মানুষ কি এই শব্দ ব্যবহার করেন?
Multiplicity আন্দাজে বসানো (সব জায়গায় 1..*)প্রতিটি প্রান্ত stakeholder-এর সাথে যাচাই; লেখা requirement-এ খুঁজুন
State machine-এ guard বাদ, বা guard-গুলো সম্পূর্ণ নয়প্রতিটি state-এ প্রতিটি event-এর জন্য টেবিল বানান; ফাঁকা ঘর খুঁজুন
Requirement-এ নেই এমন feature বা নিয়ম যোগ করাপ্রতিটি উপাদানের উৎস (কোন বাক্য, কোন story) চেয়ে নিন
Tool-এর ভুল syntax বা নেই এমন diagram ধরন; UML-এর ভুল সংস্করণ বা বানানো নিয়মআসলেই render করে দেখুন; tool-এর documentation আর OMG-র spec মিলিয়ে নিন

নিচের prompt-এ AI-কে diagram-এর সাথে প্রতিটি উপাদানের উৎস আর অনুমানের তালিকা দিতে বলা হয়েছে — যাতে আপনি প্রতিটি তীর লেখার সাথে মেলাতে পারেন। দলের আসল requirement দেওয়ার আগে গ্রাহকের নাম, চুক্তির শর্ত বা গোপন তথ্য সরিয়ে নিন।

নিচে একটি কাল্পনিক courier tracking অ্যাপের requirement দিলাম (নাম ও গোপন তথ্য বাদ দিয়ে)। [এখানে requirement-এর লেখা] ১. PlantUML-এ একটি use case diagram দাও। প্রতিটি «include» আর «extend»-এর পাশে মন্তব্যে লেখো: কেন include (সবসময় ঘটে) বা কেন extend (কোন শর্তে, কোন extension point-এ)। তীরের দিক UML ২.৫.১ অনুযায়ী রাখো। ২. একটি domain model দাও — শুধু class, attribute, association আর multiplicity; কোনো method বা foreign key নয়। ৩. "Parcel"-এর state machine দাও, প্রতিটি transition event [guard] / effect আকারে। ৪. প্রতিটি actor, class আর transition-এর পাশে লেখো requirement-এর কোন বাক্য থেকে এসেছে। যা লেখায় নেই অথচ তুমি ধরে নিয়েছ, তা আলাদা "অনুমান" তালিকায় দাও। কোনো নিয়ম বা মানদণ্ড বানিয়ে বলবে না।

AI-এর দেওয়া model হাতে পেলে — চেকলিস্ট

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

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

প্র ০১UML এত বড় মানদণ্ড, অথচ অনেক দল আজ পূর্ণ UML ব্যবহার করে না। তাহলে কি UML ব্যর্থ হয়েছে?

উত্তর নির্ভর করে “সফলতা” বলতে কী বোঝেন তার ওপর। ১৯৯০-এর দশকের শেষে আর ২০০০-এর দশকের শুরুতে অনেকের স্বপ্ন ছিল: UML-এ পুরো system-এর model আঁকা হবে, আর tool সেখান থেকে কোড বানিয়ে দেবে। OMG ২০০১ সালের দিকে Model Driven Architecture (MDA) নামে এই চিন্তা প্রচার করে। কিছু ক্ষেত্রে — বিশেষ করে embedded আর নিরাপত্তা-সংবেদনশীল system-এ, যেখানে state machine থেকে কোড বানানো স্বাভাবিক — এটি সত্যিই কাজে লেগেছে। কিন্তু সাধারণ ব্যবসার software-এ বড় আকারে ছড়ায়নি; model আর কোড দুটো আলাদা জিনিস হালনাগাদ রাখার খরচ বেশি ছিল।

Martin Fowler UML Distilled-এ UML ব্যবহারের তিনটি ধরন বলেছিলেন: sketch (আলোচনার জন্য দ্রুত, অসম্পূর্ণ ছবি), blueprint (বিস্তারিত নকশা, যা থেকে অন্যরা কোড লেখেন), আর programming language (model নিজেই চলে)। বাস্তবে sketch সবচেয়ে টিকে আছে। হোয়াইটবোর্ডে একটি state machine, ticket-এ একটি sequence diagram, README-তে একটি Mermaid ছবি — এগুলো UML-এর নোটেশন ধার করে, কিন্তু spec-এর প্রতিটি নিয়ম মানে না।

Agile আন্দোলনের (পর্ব ৫) “comprehensive documentation-এর চেয়ে কাজ করা software” মূল্যবোধও বড় আগাম model-কে নিরুৎসাহিত করেছে। তবে মনে রাখুন, Manifesto documentation বাদ দিতে বলেনি, শুধু অগ্রাধিকার বলেছে। আর বিশ্ববিদ্যালয়, চাকরির পরীক্ষা আর ইন্টারভিউতে UML এখনো একটি সাধারণ ভাষা — দুজন অপরিচিত engineer একটি whiteboard-এ এক মিনিটে একই ছবি বুঝতে পারেন, কারণ দুজনেই একই নোটেশন জানেন।

অর্থাৎ UML একটি “সব কিছু model থেকে” বিপ্লব হিসেবে বড় আকারে সফল হয়নি, কিন্তু একটি সাধারণ চিত্রভাষা হিসেবে প্রায় সর্বত্র ছড়িয়েছে — ঠিক যেমন সবাই গানের স্বরলিপি পুরোটা না জানলেও সা-রে-গা-মা চেনেন।

মূল উপলব্ধি: UML শিখুন একটি ভাষা হিসেবে, ধর্মগ্রন্থ হিসেবে নয়। সঠিক নোটেশন জানলে আপনার sketch দ্ব্যর্থহীন হয়; কতটা আঁকবেন, তা ঠিক করবে প্রশ্নটি।

প্র ০২«include» আর «extend» নিয়ে এত বিভ্রান্তি কেন? Extend-এর তীর উল্টো দিকে কেন — এর পেছনের চিন্তা কী?

বিভ্রান্তির একটি অংশ ইতিহাস থেকে। UML-এর শুরুর সংস্করণগুলোতে use case-এর সম্পর্কের নাম ছিল «uses» আর «extends», আর দুটোকে generalisation-এর মতো দেখানো হতো। ১৯৯৯ সালের দিকে UML ১.৩-এ বর্তমান «include» আর «extend» আসে, ভিন্ন অর্থে। পুরোনো বই, পুরোনো tool আর পুরোনো slide-এ আগের নোটেশন এখনো ঘোরে — আর AI সেগুলো থেকেই শেখে।

দ্বিতীয় অংশ ধারণাগত। Jacobson-এর extend-এর চিন্তার উৎস হিসেবে প্রায়ই telecom-এর অভিজ্ঞতা বলা হয়: একটি চালু, পরীক্ষিত call-handling system-এ নতুন feature (যেমন call waiting) যোগ করতে হবে মূল কোড না বদলে। তাই extension নিজে জানে কোথায় যুক্ত হবে; base শুধু কয়েকটি নামের জায়গা (extension point) খোলা রাখে। এই কারণেই তীর extension থেকে base-এ — নির্ভরতা যার, তীর তার থেকে বেরোয়। Include-এ উল্টো: base-এর লেখার ভেতরেই included-এর নাম আছে, তাই base নির্ভরশীল, তীর base থেকে।

এই চিন্তা পরে design-এ আরও শক্তভাবে ফিরে আসে: “নতুন আচরণ যোগ করতে পুরোনো কোড বদলাবেন না” — এটাই open-closed principle (পর্ব ২৭)। Extend সেই নীতির requirement-স্তরের রূপ।

তৃতীয় সমস্যা অপব্যবহার। অনেক diagram-এ প্রতিটি use case তিন-চারটি include-এ ভাঙা — যা আসলে functional decomposition, অর্থাৎ design আগেভাগে করে ফেলা। Cockburn-সহ অনেক লেখক তাই পরামর্শ দেন: সম্পর্কগুলো কম ব্যবহার করুন; লেখা use case-এর ধাপ আর extension-ই আসল জায়গা। একটি ব্যবহারিক পরীক্ষা: সম্পর্কটি মুছে দিলে কি কোনো পাঠক কিছু হারাবেন? না হলে মুছে দিন।

মূল উপলব্ধি: তীরের দিক নির্ভরতার দিক: যে জানে, তার থেকে তীর। Include = প্রতিবার, base জানে; extend = শর্তে, extension জানে। আর দুটোই কম, প্রয়োজনে।

প্র ০৩Object-oriented model আসার পরে কি DFD পুরোনো হয়ে গেছে? আজ কোথায় এর ব্যবহার আছে?

১৯৯০-এর দশকে object-oriented analysis জনপ্রিয় হওয়ার সাথে structured analysis-এর জায়গা অনেকটা কমে। কারণ DFD process আর data-কে আলাদা করে দেখে, আর OO চিন্তা দুটোকে একসাথে (object-এর ভেতরে data আর আচরণ) রাখতে চায়। DFD থেকে design-এ যাওয়ার স্বাভাবিক পথ ছিল structured design — function আর module-এর গাছ — যা OO ভাষার সাথে খাপ খায় না।

কিন্তু DFD হারিয়ে যায়নি; তার শক্তির জায়গায় সে টিকে আছে। সবচেয়ে পরিচিত উদাহরণ threat modeling: Microsoft-এর STRIDE-ভিত্তিক threat modeling পদ্ধতি আর তাদের প্রকাশ্য threat modeling tool system-কে DFD আকারে আঁকতে বলে, যেখানে “trust boundary” নামে একটি বাড়তি উপাদান যোগ হয়। কারণ নিরাপত্তার প্রশ্নগুলো data-কেন্দ্রিক: কোন data কোন সীমা পার হয়? কোথায় জমা থাকে? কে পড়তে পারে?

দ্বিতীয় জায়গা privacy আর data protection. ব্যক্তিগত তথ্য কোথা থেকে আসে, কোথায় যায়, কত দিন থাকে — এই “data map” আঁকতে বহু প্রতিষ্ঠান DFD-র মতো ছবি ব্যবহার করে, কারণ নিয়ন্ত্রক আর আইনজীবীরা object-এর চেয়ে data-র যাত্রা সহজে বোঝেন। বাংলাদেশসহ বিভিন্ন দেশে ব্যক্তিগত তথ্য সুরক্ষার আইন নিয়ে আলোচনা আর পরিবর্তন চলছে; নির্দিষ্ট দেশের সর্বশেষ আইন নিজে যাচাই করে নিন।

তৃতীয়ত, data pipeline আর integration: ব্যাংক, সরকারি সেবা বা বড় প্রতিষ্ঠানে অনেক system-এর মধ্যে file আর message চলাচল বোঝাতে context diagram এখনো সবচেয়ে সরল ছবি। System design-এর আলোচনায় (requirement আর trade-off) প্রথমে যে “বাক্স আর তীর” আঁকা হয়, তার অনেকটাই এই চিন্তার উত্তরসূরি।

আর পরীক্ষার কথা: বহু বিশ্ববিদ্যালয়ের software engineering আর system analysis পাঠ্যক্রমে আর চাকরির লিখিত পরীক্ষায় DFD এখনো নিয়মিত আসে — বিশেষ করে level ভাঙা আর balancing-এর প্রশ্ন।

মূল উপলব্ধি: প্রতিটি model একটি প্রশ্নের উত্তর দেয়। “Data কোথায় যায়?” — এই প্রশ্নে DFD এখনো সবচেয়ে সরাসরি উত্তর, তাই নিরাপত্তা আর privacy-তে সে নতুন করে প্রাসঙ্গিক।

প্র ০৪অর্ডারের অবস্থা database-এ একটি সাধারণ text column আর কয়েকটি true/false দিয়ে রাখলে সমস্যা কোথায়? State machine কী বাড়তি দেয়?

অনেক system শুরু হয় এভাবে: অর্ডারে status নামে একটি text column, আর তার পাশে is_paid, is_shipped, is_cancelled জাতীয় কয়েকটি true/false. প্রথম কয়েক মাস চলে। তারপর অদ্ভুত অর্ডার দেখা দেয়: is_cancelled সত্য, আবার is_shipped-ও সত্য। বা status “Delivered”, অথচ is_paid মিথ্যা। তিনটি true/false-এ ২³ = ৮টি সমন্বয় সম্ভব, কিন্তু ব্যবসায় বৈধ অবস্থা হয়তো পাঁচটি। বাকি তিনটি “অসম্ভব অবস্থা” কোডের কোনো কোণে ঠিকই তৈরি হয়ে যায়।

State machine দুটি জিনিস স্পষ্ট করে। এক, বৈধ অবস্থার পূর্ণ তালিকা — একটি অর্ডার একবারে ঠিক একটি state-এ। দুই, বৈধ transition-এর পূর্ণ তালিকা — কোন state থেকে কোন event-এ কোথায় যাওয়া যায়; বাকি সব নিষিদ্ধ। অংশ ৭-এর প্রোগ্রামে transition table-এর বাইরে কিছুই হতে পারে না, তাই Delivered অর্ডার আবার deliver হয় না।

Harel যখন statecharts গড়ে তোলেন, তাঁর লক্ষ্যও ছিল জটিল প্রতিক্রিয়াশীল (reactive) system-এর আচরণ এমনভাবে লেখা, যাতে প্রকৌশলীরা আর বিশেষজ্ঞরা একই ছবিতে প্রশ্ন তুলতে পারেন। সমতল state machine-এ state-এর সংখ্যা দ্রুত বিস্ফোরিত হয়; তাই তিনি nested state আর সমান্তরাল অংশ যোগ করেন (পর্ব ৩০)।

Test-এর দিক থেকেও লাভ বড়। “প্রতিটি state-এ প্রতিটি event” একটি টেবিল দেয় — ৫টি state × ৫টি event = ২৫টি ঘর; প্রতিটি ঘর হয় একটি transition, নয়তো একটি প্রত্যাখ্যান, আর প্রতিটির জন্য test লেখা যায়। ISTQB-র মতো testing পাঠ্যক্রমে এটিকে state transition testing বলা হয়।

বাস্তবে state machine কোডে রাখার কয়েকটি উপায় আছে: একটি enum আর transition table (অংশ ৭-এর মতো), state design pattern (পর্ব ৩৬), বা workflow engine. Database-এ status column থাকতেই পারে — শর্ত হলো, সেটি বদলানোর একমাত্র পথ যেন transition table-এর ভেতর দিয়ে যায়।

মূল উপলব্ধি: State machine “অসম্ভব অবস্থা”-কে অসম্ভব করে, আর প্রতিটি state × event-এর প্রশ্ন জোর করে তোলে — যার উত্তর প্রায়ই একটি ভুলে যাওয়া ব্যবসার নিয়ম।

প্র ০৫Model আর কোড সময়ের সাথে আলাদা হয়ে যায়। তাহলে model বানিয়ে লাভ কী, আর কোনটা রাখবেন, কোনটা ফেলে দেবেন?

এটা সত্যিকারের সমস্যা, আর পুরোনো। ১৯৯২ সালে Jack Reeves একটি প্রবন্ধে (“What Is Software Design?”) যুক্তি দেন যে software-এর আসল design document হলো source code নিজেই; diagram তার একটি সহায়ক ছায়া। ছায়া যদি বস্তুর সাথে না নড়ে, তবে বিভ্রান্তি বাড়ায়। অনেক দলের অভিজ্ঞতা: কেউ wiki-তে একটি সুন্দর diagram দেখে বিশ্বাস করলেন, অথচ সেটা দুই বছর পুরোনো।

কিন্তু তাই বলে model বাদ দেওয়া উত্তর নয়। Requirement-স্তরের model-এর বড় মূল্য তৈরি হয় আঁকার সময় — আলোচনা, প্রশ্ন, ফাঁক খুঁজে পাওয়া। অংশ ৮-এর CRUD matrix একবার চালিয়ে যে তিনটি ফাঁক বেরোল, সেগুলো requirement-এ ঠিক হয়ে গেলে matrix-টি ফেলে দিলেও লাভটা থেকে যায়। Scott Ambler এই কারণেই model-কে দুই ভাগে দেখতে বলেন: অস্থায়ী (আলোচনার জন্য, পরে ফেলে দেওয়া) আর রক্ষণাবেক্ষিত (যা দল জেনে-বুঝে হালনাগাদ রাখবে)।

কোনটা রক্ষণাবেক্ষিত হবে? কয়েকটি প্রশ্ন সাহায্য করে: এটা কি বারবার কেউ জিজ্ঞেস করবেন (যেমন অর্ডারের বাতিলের নিয়ম)? এটা কি আইন, টাকা বা নিরাপত্তার সাথে জড়িত? এটা কি দলের বাইরের মানুষ (নতুন সদস্য, নিরীক্ষক) পড়বেন? উত্তর হ্যাঁ হলে রাখুন — আর এমনভাবে রাখুন যাতে হালনাগাদ সস্তা হয়।

সস্তা করার আধুনিক কৌশল “docs as code”: diagram text হিসেবে (PlantUML বা Mermaid) কোডের পাশে একই repository-তে, পরিবর্তন একই pull request-এ, review-ও একই সাথে। আরেক ধাপ এগিয়ে, state machine-এর transition table কোডেই থাকে আর diagram সেখান থেকে বানানো হয় — তখন আলাদা হয়ে যাওয়ার সুযোগই কমে। কোনো ছবি হালনাগাদ না রাখার সিদ্ধান্ত নিলে তার ওপরে তারিখ আর “ঐতিহাসিক” লিখে রাখুন, যাতে কেউ ভুল করে বিশ্বাস না করেন।

মূল উপলব্ধি: Model-এর মূল্য প্রায়ই আঁকার সময়ের চিন্তায়। অল্প কয়েকটি গুরুত্বপূর্ণ model কোডের পাশে text হিসেবে রক্ষণাবেক্ষণ করুন; বাকিগুলো কাজ শেষে নির্দ্বিধায় তারিখ দিয়ে তুলে রাখুন।

অনুশীলন

Model আঁকা, আউটপুট অনুমান, ভুল diagram-এর সমালোচনা আর path-এর হিসাব — প্রতিটির পূর্ণ উত্তর দেওয়া আছে।

  1. Model বর্ণনা করুন (courier tracking অ্যাপ, কাল্পনিক): “প্রেরক একটি parcel বুক করেন আর অনলাইনে টাকা দেন; টাকা নেয় একটি বাইরের পেমেন্ট গেটওয়ে। Hub-এর কর্মী parcel গ্রহণ করে scan করেন। একজন রাইডার parcel পৌঁছে দেন। প্রাপক বা প্রেরক parcel-এর অবস্থা দেখতে পারেন। পৌঁছানোর সময় প্রাপক না থাকলে রাইডার ‘ব্যর্থ চেষ্টা’ লিখে রাখেন; তিনবার ব্যর্থ হলে parcel প্রেরকের কাছে ফেরত যায়।” (ক) Actor আর use case-এর তালিকা দিন, কোনটি include বা extend হবে বলুন। (খ) একটি domain model বর্ণনা করুন — class, attribute, multiplicity. (গ) Parcel-এর state machine-এর state আর transition লিখুন।

    (ক) Actor: প্রেরক (primary), প্রাপক (primary — অবস্থা দেখার জন্য), hub কর্মী, রাইডার (মানুষ), পেমেন্ট গেটওয়ে (secondary, system). প্রেরক আর প্রাপকের সাধারণ রূপ “গ্রাহক” ধরে generalisation আঁকা যায়, কারণ দুজনেই “অবস্থা দেখা” পান। Use case: parcel বুক করা, পেমেন্ট করা, parcel গ্রহণ (hub), parcel পৌঁছে দেওয়া, অবস্থা দেখা। “পার্সেল বুক করা” «include» “পেমেন্ট করা” (অনলাইনে টাকা প্রতিবার)। “ব্যর্থ চেষ্টা লেখা” «extend» “parcel পৌঁছে দেওয়া”, extension point “প্রাপক অনুপস্থিত”, শর্ত [প্রাপক নেই]. যদি ক্যাশ-অন-ডেলিভারিও থাকত, তাহলে পেমেন্ট আর প্রতিবার অনলাইনে হতো না — তখন include ঠিক হতো না, এটাই প্রশ্ন করার জায়গা।

    (খ) Customer (name, phone) — Parcel (trackingNo, weight, status, attemptCount): প্রেরক হিসেবে Customer 1 — 0..* Parcel; প্রাপক হিসেবে আরেকটি association, Customer 0..1 — 0..* Parcel (প্রাপক নিবন্ধিত না-ও হতে পারেন; তখন প্রাপকের নাম-ঠিকানা Parcel-এর attribute). Parcel 1 — 0..* ScanEvent (time, place; সদ্য বুক করা parcel-এ এখনো কোনো scan নেই) — এটি লুকানো ধারণা: “scan করেন” থেকে আসে, অবস্থার ইতিহাস রাখে। আর DeliveryAttempt (time, result): Parcel 1 — 0..3 DeliveryAttempt; প্রতিটি চেষ্টা একজন Rider (name)-এর, Rider 1 — 0..* DeliveryAttempt. Method বা foreign key নেই।

    (গ) State: Booked → (pay [amount = fee]) → Paid → (receiveAtHub) → AtHub → (assignRider) → OutForDelivery. OutForDelivery থেকে: deliver → Delivered (final); fail [attemptCount < 2] / attemptCount++ → AtHub (পরের দিন আবার); fail [attemptCount = 2] / attemptCount++ → ReturningToSender → (returned) → Returned (final). খেয়াল করুন দুটি guard মিলে সম্পূর্ণ আর পরস্পর-বর্জিত — তৃতীয় ব্যর্থতায় ফেরত। Booked থেকে cancel → Cancelled আছে কি না, requirement বলেনি: এটি stakeholder-কে জিজ্ঞেস করার প্রশ্ন।

  2. আউটপুট অনুমান করুন: একটি অর্ডারের state machine-এ transition-গুলো: Placed —pay [amount = total]→ Paid; Placed —cancel→ Cancelled; Paid-এ internal transition verifyRx / rxOk := true; Paid —ship [not needsRx or rxOk]→ Shipped; Paid —cancel / refund = total→ Cancelled; Shipped —deliver→ Delivered. Delivered আর Cancelled final; তীর না থাকলে বা guard মিথ্যা হলে event প্রত্যাখ্যাত। অর্ডারের total 300, needsRx true. Event-এর ক্রম: ship, pay(300), ship, verifyRx, cancel, pay(300). প্রতিটি event-এর ফল আর শেষ অবস্থা কী? (অংশ ৭-এর প্রোগ্রামের run দিয়ে চালালে যেমন ছাপবে, সেই আকারে লিখুন।)
    == D (total 300, needsRx true)
      REJECT ship in Placed: no such transition
      Placed --pay--> Paid
      REJECT ship in Paid: guard false (prescription not verified)
      Paid --verifyRx--> Paid
      Paid --cancel--> Cancelled
      REJECT pay in Cancelled: no such transition
      final: Cancelled, refund 300

    প্রথম ship: Placed থেকে ship-এর তীরই নেই। pay(300): guard সত্য, Paid. দ্বিতীয় ship: তীর আছে কিন্তু needsRx সত্য আর rxOk মিথ্যা, তাই guard মিথ্যা। verifyRx: internal, state একই। cancel: Paid থেকে Cancelled, effect refund ৩০০। শেষ pay: Cancelled final state, কোনো তীর নেই। লক্ষ করুন, প্রেসক্রিপশন যাচাইয়ের পরেও ship না করে cancel করা হয়েছে — টাকা ফেরত পাওয়ার শর্ত পূরণ হয়েছে কারণ অর্ডার তখনো Paid-এ ছিল।

  3. ভুল diagram-এর সমালোচনা: একজন সহকর্মী (বা একটি AI) ফার্মেসি অ্যাপের জন্য এই diagram দিয়েছেন: (১) boundary-র ভেতরে “Database” নামে একটি actor, “অর্ডার দেওয়া”-র সাথে যুক্ত; (২) “লগইন” → “ওষুধ খোঁজা” → “Cart-এ যোগ” → “পেমেন্ট” — চারটি use case সাধারণ তীর দিয়ে ক্রমে জোড়া; (৩) “অর্ডার দেওয়া” থেকে “প্রেসক্রিপশন আপলোড”-এর দিকে «include»; (৪) “পেমেন্ট” থেকে “অর্ডার দেওয়া”-র দিকে «include»; (৫) “Pay বোতাম চাপা” নামে একটি use case; (৬) domain model-এ Order class-এ customerId, calculateTotal() আর OrderManager নামে একটি class. প্রতিটি ভুল কেন, আর ঠিক রূপ কী?
    • (১) Actor boundary-র বাইরে থাকে, আর database আমাদের system-এরই অংশ — এটা actor নয়। বাদ দিন। বাইরের কোনো system (পেমেন্ট গেটওয়ে) হলে বাইরে «actor» বাক্সে।
    • (২) Use case diagram ক্রম দেখায় না। তীরগুলো সরিয়ে দিন; ক্রমটি activity diagram বা লেখা use case-এর ধাপে নিন। “লগইন” আর “Cart-এ যোগ” সম্ভবত লক্ষ্য নয়, ধাপ বা precondition.
    • (৩) প্রেসক্রিপশন সব অর্ডারে লাগে না, তাই include ভুল। ঠিক রূপ: “প্রেসক্রিপশন আপলোড” থেকে “অর্ডার দেওয়া”-র দিকে «extend», extension point “Rx লাগলে”, শর্ত [cart-এ Rx-ওষুধ].
    • (৪) সম্পর্কটি ঠিক (পেমেন্ট প্রতিবার), কিন্তু তীর উল্টো। Include-এর তীর base (অর্ডার দেওয়া) থেকে included (পেমেন্ট)-এর দিকে।
    • (৫) বোতাম চাপা একটি UI-এর ধাপ, actor-এর মূল্যবান লক্ষ্য নয়। মুছে দিন; এটি “পেমেন্ট করা”-র লেখা ধাপের অংশ।
    • (৬) Domain model-এ method (calculateTotal()) নয়, foreign key (customerId) নয় — Customer 1 — 0..* Order association দিয়ে সম্পর্ক দেখান; OrderManager software-এর একটি নকশা-সিদ্ধান্ত, ব্যবসার ধারণা নয়। মোট দাম যদি ব্যবসার কাছে অর্থবহ হয়, সেটি একটি derived attribute হিসেবে লেখা যায় (UML-এ নামের আগে /, যেমন /total).
  4. Path আর complexity-র হিসাব: একটি কাল্পনিক বিশ্ববিদ্যালয়ের ফলাফল পোর্টালে “পুনর্নিরীক্ষার আবেদন” activity: শুরু → আবেদন জমা → decision ১: [ফি দেওয়া আছে] বা [ফি বাকি]; ফি বাকি হলে “ফি পরিশোধ” তারপর merge → decision ২: পেমেন্টের মাধ্যম [mobile wallet], [card] বা [ব্যাংকে জমা] — তিনটি পথ, তারপর merge → fork: “বিভাগে পাঠানো” আর “শিক্ষার্থীকে রসিদ” সমান্তরালে → join → শেষ। কোনো loop নেই। (ক) শুরু থেকে শেষ পর্যন্ত কতগুলো আলাদা পথ? (খ) Fork কি সংখ্যা বাড়ায়? (গ) Cyclomatic complexity কত, আর এর অর্থ কী?

    (ক) দুটি decision পরপর আর পরস্পর-স্বাধীন, তাই পথ = ২ × ৩ = ৬টি: (ফি দেওয়া, wallet), (ফি দেওয়া, card), (ফি দেওয়া, ব্যাংক), (ফি বাকি, wallet), (ফি বাকি, card), (ফি বাকি, ব্যাংক)। (অনেকে বলতে পারেন, ফি দেওয়া থাকলে আবার মাধ্যম বাছাই কেন? — ভালো প্রশ্ন; বাস্তবে decision ২ হয়তো শুধু ফি-বাকি পথে থাকা উচিত। তখন পথ ১ + ৩ = ৪। Diagram এই requirement-এর ফাঁকটি দেখিয়ে দিল।)

    (খ) না। Fork-এর দুই শাখা প্রতিটি পথে একসাথে চলে; তাই এটি নতুন বিকল্প পথ নয়। (দুই শাখার ভেতরের ক্রম কীভাবে মিশতে পারে, সেটা আলাদা concurrency-র প্রশ্ন।)

    (গ) দুই-মুখী decision ১টি (১ বাড়তি পথ) আর তিন-মুখী decision ১টি (২ বাড়তি পথ); V = ১ + ২ + ১ = ৪. অর্থাৎ ৪টি basis path দিয়ে প্রতিটি তীর অন্তত একবার ছোঁয়া যায়, যেমন: (দেওয়া, wallet), (বাকি, wallet), (দেওয়া, card), (দেওয়া, ব্যাংক)। পূর্ণ পথ ৬, basis path ৪ — ৬টিই test করলে সব সমন্বয় ধরা পড়ে, ৪টি করলে সব তীর। এই পার্থক্যের বিস্তারিত পর্ব ৫০-এ।

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

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

ক্রমUML-এর ইতিহাসের ঘটনাগুলো সময়ের ক্রমে সাজান (▲▼ চেপে সরান)

  1. Booch, OMT আর OOSE — আলাদা আলাদা object-oriented পদ্ধতি
  2. Rumbaugh Rational-এ Booch-এর সাথে যোগ দেন (১৯৯৪)
  3. Jacobson-ও Rational-এ যোগ দেন (১৯৯৫)
  4. OMG UML ১.১-কে মানদণ্ড হিসেবে গ্রহণ করে (১৯৯৭)
  5. UML ২.৫.১ প্রকাশিত হয় (২০১৭)

একটি অনলাইন ফার্মেসির use case diagram-এ “অর্ডার দেওয়া” প্রতিবার “পেমেন্ট করা” চালায়। এই সম্পর্ক সঠিকভাবে কীভাবে আঁকা হয়?

  • “পেমেন্ট করা” থেকে “অর্ডার দেওয়া”-র দিকে ড্যাশ তীর, «extend»
  • “পেমেন্ট করা” থেকে “অর্ডার দেওয়া”-র দিকে ড্যাশ তীর, «include»
  • “অর্ডার দেওয়া” থেকে “পেমেন্ট করা”-র দিকে ড্যাশ তীর, «include»
  • “পেমেন্ট করা” থেকে “অর্ডার দেওয়া”-র দিকে ফাঁপা ত্রিভুজের তীর

একটি domain model-এ Customer আর Order-এর association-এ Customer প্রান্তে 1 আর Order প্রান্তে 0..* লেখা। এর সঠিক অর্থ কোনটি?

  • প্রতিটি Order ঠিক একজন Customer-এর; একজন Customer-এর শূন্য বা অনেক Order থাকতে পারে
  • প্রতিটি Customer ঠিক একটি Order-এর; একটি Order-এ অনেক Customer থাকতে পারেন
  • প্রতিটি Customer-এর অন্তত একটি Order থাকতেই হবে
  • Customer আগে তৈরি হয়, Order পরে

একটি data flow diagram-এ নিচের কোনটি নিয়মবিরুদ্ধ?

  • একটি process থেকে একটি data store-এ নাম লেখা flow
  • একটি external entity থেকে একটি process-এ flow
  • “১ অর্ডার গ্রহণ” নামে একটি process
  • একটি data store থেকে আরেকটি data store-এ সরাসরি flow

একটি activity diagram-এ “পেমেন্ট নেওয়া”-র পরে একটি মোটা দণ্ড থেকে “প্যাক করা” আর “রাইডার ঠিক করা” দুটি পথ বেরিয়েছে, আর পরে আরেকটি মোটা দণ্ডে মিলেছে। এর অর্থ কী?

  • শর্ত অনুযায়ী দুটির একটি পথ নেওয়া হবে
  • দুটি কাজ সমান্তরালে চলবে, আর দুটোই শেষ হলে তবেই এগোনো যাবে
  • আগে প্যাক করা, তারপর রাইডার ঠিক করা
  • যেকোনো একটি কাজ শেষ হলেই পরের ধাপে যাওয়া যাবে

একটি অর্ডারের state machine-এ Paid state থেকে একমাত্র ship-এর transition: ship [not needsRx or rxOk] → Shipped. একটি অর্ডার Paid অবস্থায় আছে, needsRx = true, rxOk = false. এখন ship event এলে কী হবে?

  • অর্ডার Shipped-এ যাবে, কারণ ship event এসেছে
  • অর্ডার স্বয়ংক্রিয়ভাবে Cancelled হবে
  • Guard মিথ্যা, তাই transition হবে না; অর্ডার Paid-এই থাকবে
  • অবস্থা অনির্দিষ্ট — কী হবে বলা যায় না

একটি কাল্পনিক ফার্মেসির বর্ণনায় আছে: “Customer places an order and pays by mobile wallet. The system sends an SMS.” Noun analysis-এর পরে কোন সিদ্ধান্তটি সবচেয়ে যুক্তিসংগত?

  • প্রতিটি বিশেষ্য — customer, order, wallet, system, SMS — একটি করে class
  • Customer আর Order class; “system” boundary, SMS মাধ্যম; wallet-এর পেছনের গেটওয়ে একটি secondary actor
  • “System” একটি actor, কারণ বাক্যে এটি কর্তা
  • “places” আর “pays” Order class-এর method হিসেবে domain model-এ লেখা

শব্দকোষ

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

মনে রাখুন

  1. Requirement model সমস্যাকে ছবিতে দ্ব্যর্থহীন করে — একই বোঝাপড়া, ফাঁক খোঁজা আর জানানো; প্রতিটি model একটি নির্দিষ্ট প্রশ্নের উত্তর।
  2. Use case diagram: actor বাইরে, use case লক্ষ্য; «include» প্রতিবার, base → included; «extend» শর্তে, extension → base, extension point-এ।
  3. Domain model: noun analysis থেকে প্রার্থী, তারপর বিচার; attribute আর multiplicity আছে, method আর foreign key নেই.
  4. DFD data-র পথ দেখায় (ক্রম নয়); activity diagram ধারা, decision-merge, fork-join আর swimlane; state machine একটি জিনিসের জীবন — event [guard] / effect.
  5. CRUD matrix আর path গোনা দিয়ে model যাচাই করুন; আর যতটুকু দরকার, ততটুকুই model — গুরুত্বপূর্ণগুলো text হিসেবে Git-এ।

পরের পর্বে: Requirement জমা হলো, model-ও হলো। কিন্তু সব কি একসাথে বানানো যাবে? পর্ব ২৪-এ requirement management — কোনটা আগে (prioritization), requirement ঠিক কি না (validation), কোন requirement থেকে কোন কোড আর test (traceability), আর মাঝপথে scope বাড়তে থাকলে কী করবেন।

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

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