চূড়ান্ত প্রকল্প — একটি মোবাইল অ্যাপের আর্কিটেকচার ডিজাইন ও সিমুলেট করা
এই পাঠে যা শিখবেন
- কোর্সের একাধিক সিমুলেশন প্যাটার্নকে (লাইফসাইকেল, নেভিগেশন, স্টেট, স্টোরেজ, পারমিশন, নেটওয়ার্কিং) একটি সুসংগত সিস্টেমে সংযুক্ত করা
- কেন লোকাল কী-ভ্যালু স্টোরেজে আগে থেকে সেভ করা ডেটা একটি অ্যাপ kill+relaunch সাইকেলের পরও টিকে থাকে, যেখানে RAM-এর বাকি সবকিছু হারিয়ে যায়
- একটি অ্যাকশন (ক্যামেরা) কীভাবে সত্যিই কল-টাইমে পারমিশন স্ট্যাটাস চেক করে গেট করা হয় — denied ও granted উভয় ক্ষেত্রে
- একটি সম্পূর্ণ এন্ড-টু-এন্ড মোবাইল ব্যবহারকারী ফ্লো ট্রেস করা — লঞ্চ থেকে সিঙ্ক পর্যন্ত
১ · ক্যাপস্টোন পরিকল্পনা — একটি নোট অ্যাপ সিমুলেট করা
কোর্স জুড়ে আমরা একটি একটি করে মোবাইল-আর্কিটেকচার ধারণা শিখেছি — অ্যাপ লাইফসাইকেল (M1), নেভিগেশন (M6), স্টেট ম্যানেজমেন্ট (M7), লোকাল স্টোরেজ (M8), নেটওয়ার্কিং (M9), ও ডিভাইস পারমিশন (M10)। এই ক্যাপস্টোনে সেগুলো আলাদা আলাদা না রেখে একসাথে জোড়া লাগিয়ে একটি ছোট্ট কিন্তু সম্পূর্ণ, কার্যকর "নোট অ্যাপ" মোবাইল আর্কিটেকচার সিমুলেট করা হবে।
২ · মডিউল ১ — AppLifecycle (L01/L04-এর ধাঁচে)
প্রথম টুকরোটি হলো L01-এ প্রবর্তিত সেই একই AppLifecycle স্টেট মেশিন — শুধু নির্দিষ্ট বৈধ
ট্রানজিশনই মেনে চলে, killed একটি টার্মিনাল স্টেট যেখান থেকে সরাসরি কোথাও ফেরা যায় না।
# ========== মডিউল ১: AppLifecycle -- state machine (L01/L04-এর ধাঁচে) ==========
class AppLifecycle:
ALLOWED = {
'not_running': {'foreground'},
'foreground': {'background'},
'background': {'foreground', 'killed'},
'killed': set(), # টার্মিনাল স্টেট -- নতুন ইনস্ট্যান্স দিয়েই শুধু আবার চালু করা যায়
}
def __init__(self):
self.state = 'not_running'
self.history = [self.state]
def transition(self, new_state, reason):
if new_state in self.ALLOWED[self.state]:
old_state = self.state
self.state = new_state
self.history.append(new_state)
print(f"[lifecycle] {reason:42s} | {old_state} -> {new_state} (বৈধ)")
return True
print(f"[lifecycle] {reason:42s} | {self.state} -> {new_state} (অবৈধ, প্রত্যাখ্যাত)")
return False
# ---- স্যানিটি-চেক ----
_demo_lifecycle = AppLifecycle()
_demo_lifecycle.transition('foreground', 'স্যানিটি-চেক: প্রথম লঞ্চ')
print("প্রাথমিক যাচাই সম্পন্ন, বর্তমান স্টেট:", _demo_lifecycle.state)
৩ · মডিউল ২ — NavigationStack (L24-এর ধাঁচে)
দ্বিতীয় টুকরোটি একটি real navigation stack — একটি Python লিস্ট, push()/pop()/
current() সহ। রুট স্ক্রিন থেকে pop() করার চেষ্টা প্রত্যাখ্যাত হয়, ঠিক যেমন বাস্তব
অ্যাপে হোম স্ক্রিন থেকে "ব্যাক" চাপলে অ্যাপ নিজেই ব্যাকগ্রাউন্ডে চলে যায়, ন্যাভিগেশন স্ট্যাক ফাঁকা হয় না।
# ========== মডিউল ২: NavigationStack -- push/pop/current (L24-এর ধাঁচে) ==========
class NavigationStack:
def __init__(self, root_screen):
self._stack = [root_screen]
def push(self, screen):
self._stack.append(screen)
print(f"[nav] push({screen!r:12s}) -> স্ট্যাক: {self._stack}")
def pop(self):
if len(self._stack) <= 1:
print("[nav] pop() প্রত্যাখ্যাত -- রুট স্ক্রিন থেকে পপ করা যায় না")
return None
screen = self._stack.pop()
print(f"[nav] pop() -> সরানো হলো {screen!r}, স্ট্যাক: {self._stack}")
return screen
def current(self):
return self._stack[-1]
# ---- স্যানিটি-চেক ----
_demo_nav = NavigationStack("Home")
print("প্রাথমিক স্ট্যাক:", _demo_nav._stack)
_demo_nav.push("Settings")
_demo_nav.pop()
_demo_nav.pop() # রুট থেকে পপ -- প্রত্যাখ্যাত হওয়া উচিত
৪ · মডিউল ৩ — Store (L28/L29-এর ধাঁচে)
তৃতীয় টুকরোটি একটি স্টেট স্টোর — set_state() সবসময় একটি নতুন state dict তৈরি
করে (immutability, পুরনোটি মিউটেট না করে), আর subscribe() করা প্রতিটি লিসেনার প্রতিটি
পরিবর্তনে নোটিফাই হয়।
# ========== মডিউল ৩: Store -- set_state()/subscribe() (L28/L29-এর ধাঁচে) ==========
class Store:
def __init__(self, initial_state):
self.state = dict(initial_state)
self._listeners = []
def set_state(self, partial):
self.state = {**self.state, **partial}
for listener in self._listeners:
listener(self.state)
return self.state
def subscribe(self, listener):
self._listeners.append(listener)
def on_note_change(state):
print(f" [store-subscriber] state পরিবর্তন হলো -> draft_note={state.get('draft_note')!r}")
# ---- স্যানিটি-চেক ----
_demo_store = Store({"draft_note": None})
_demo_store.subscribe(on_note_change)
print("প্রাথমিক state:", _demo_store.state)
_demo_store.set_state({"draft_note": "স্যানিটি-চেক নোট"})
৫ · মডিউল ৪ — KeyValueStore (L31/L32-এর ধাঁচে)
চতুর্থ টুকরোটি সবচেয়ে গুরুত্বপূর্ণ এই ক্যাপস্টোনের জন্য — একটি লোকাল কী-ভ্যালু "ডিস্ক"। এটি একটি সাধারণ Python dict-ভিত্তিক ক্লাস, কিন্তু নিচের মডিউল ৭-এ এর একটিমাত্র ইনস্ট্যান্স তৈরি করে সেটাকে "kill" সিমুলেশনের ওপার থেকেও অপরিবর্তিত রাখা হবে — ঠিক যেমন বাস্তব ডিভাইসে ডিস্কে লেখা ডেটা প্রসেস kill হলেও টিকে থাকে (RAM-এর বাকি সবকিছুর বিপরীতে)।
# ========== মডিউল ৪: KeyValueStore -- get/set/remove, ডিভাইস-ডিস্ক persistence-এর ভূমিকায় (L31/L32) ==========
class KeyValueStore:
def __init__(self):
self._data = {}
def get(self, key, default=None):
return self._data.get(key, default)
def set(self, key, value):
self._data[key] = value
def remove(self, key):
self._data.pop(key, None)
# DEVICE_DISK-ই এই ক্যাপস্টোনের একমাত্র "persist" হওয়া জায়গা -- মডিউল ৭-এ kill সিমুলেশনের
# আগে-পরে এই একই ইনস্ট্যান্স ব্যবহৃত হবে, নতুন করে তৈরি করা হবে না
DEVICE_DISK = KeyValueStore()
print("DEVICE_DISK শুরুতে খালি:", DEVICE_DISK.get("draft_note", "(কিছু নেই)"))
৬ · মডিউল ৫ — পারমিশন গেট (L41-এর ধাঁচে)
পঞ্চম টুকরোটি ক্যামেরা অ্যাক্সেসের জন্য একটি পারমিশন স্টেট মেশিন। attach_photo() প্রতিবার
কল হওয়ার সময় সত্যিই PERMISSIONS ডিকশনারি চেক করে — আগে থেকে ধরে নেয় না — তাই একই ফাংশন
denied ও granted উভয় অবস্থায় সঠিক আচরণ করে।
# ========== মডিউল ৫: পারমিশন গেট -- ক্যামেরা অ্যাক্সেস (L41-এর ধাঁচে) ==========
PERMISSIONS = {"camera": "not_requested"}
def request_permission(name, os_outcome):
"""os_outcome ডিটারমিনিস্টিকভাবে দেওয়া হচ্ছে (real OS ডায়ালগের সিদ্ধান্তের প্রতিনিধিত্ব) -- র্যান্ডম না"""
PERMISSIONS[name] = os_outcome
print(f"[permission] '{name}' রিকোয়েস্ট করা হলো -> OS সিদ্ধান্ত: {os_outcome}")
return os_outcome
def attach_photo():
status = PERMISSIONS.get("camera", "not_requested") # প্রতিবার কল-টাইমে সত্যিই চেক করা হয়
if status != "granted":
print(f"[camera] ব্লকড -- বর্তমান পারমিশন স্ট্যাটাস: {status!r}")
return False
print("[camera] ক্যামেরা খোলা হলো, ছবি তোলা হলো")
return True
# ---- স্যানিটি-চেক: রিকোয়েস্টের আগে, denied, ও granted -- তিনটি অবস্থাই যাচাই ----
print("যাচাই ১ -- কোনো রিকোয়েস্ট ছাড়াই সরাসরি চেষ্টা:")
result1 = attach_photo()
assert result1 is False
print("\nযাচাই ২ -- OS পারমিশন denied করলো:")
request_permission("camera", "denied")
result2 = attach_photo()
assert result2 is False
print("\nযাচাই ৩ -- OS পারমিশন granted করলো:")
request_permission("camera", "granted")
result3 = attach_photo()
assert result3 is True
৭ · মডিউল ৬ — রিট্রাই/ব্যাকঅফ নেটওয়ার্কিং (L38-এর ধাঁচে)
ষষ্ঠ টুকরোটি সিঙ্ক কলের জন্য এক্সপোনেনশিয়াল ব্যাকঅফ — একটি ডিটারমিনিস্টিক flaky ফাংশন (র্যান্ডম নয়) যা নির্দিষ্ট কয়েকবার ব্যর্থ হওয়ার পর সফল হয়, আর প্রতিটি চেষ্টার প্রকৃত গণনাকৃত delay প্রিন্ট হয়।
# ========== মডিউল ৬: রিট্রাই/ব্যাকঅফ নেটওয়ার্কিং -- sync() কল (L38-এর ধাঁচে) ==========
def flaky_sync_call(attempt_number, fail_until_attempt=2):
"""ডিটারমিনিস্টিক flaky ফাংশন -- fail_until_attempt সংখ্যক বার ব্যর্থ হয়, তারপর সফল হয়"""
return attempt_number > fail_until_attempt
def sync_with_backoff(max_attempts=5, base_delay=1.0, max_delay=8.0):
for attempt in range(1, max_attempts + 1):
success = flaky_sync_call(attempt)
if success:
print(f"[sync] attempt {attempt}: সফল")
return True, attempt
delay = min(base_delay * (2 ** (attempt - 1)), max_delay)
print(f"[sync] attempt {attempt}: ব্যর্থ -- {delay:.1f}s পর আবার চেষ্টা হবে")
print("[sync] সর্বোচ্চ চেষ্টা শেষ, ব্যর্থ")
return False, max_attempts
# ---- স্যানিটি-চেক ----
_ok, _tries = sync_with_backoff()
print(f"\nস্যানিটি-চেক ফলাফল: success={_ok}, attempts={_tries}")
assert _ok is True and _tries == 3, "২ বার ব্যর্থ হয়ে ৩য় চেষ্টায় সফল হওয়া উচিত ছিল"
৮ · মডিউল ৭ — সম্পূর্ণ এন্ড-টু-এন্ড ফ্লো
এখন সবগুলো টুকরো একসাথে — একটি সম্পূর্ণ ব্যবহারকারী-যাত্রা: (a) অ্যাপ চালু হওয়া, (b) "New Note" স্ক্রিনে যাওয়া, (c) নোট টাইপ করে state store-এ সেভ করা, (d) ব্যাকগ্রাউন্ড → kill (কিন্তু তার আগে ডিস্কে persist), (e) রিলঞ্চ করে ডিস্ক থেকে নোট ফিরে পাওয়া, (f) sync বাটনে ট্যাপ (রিট্রাই/ব্যাকঅফ), ও (g) ছবি অ্যাটাচ করার চেষ্টা (denied, তারপর granted)।
# ========== মডিউল ৭: সম্পূর্ণ এন্ড-টু-এন্ড ফ্লো ==========
print("=" * 70)
print("ধাপ (a): অ্যাপ চালু হলো -- লাইফসাইকেল ট্রানজিশন ও নেভিগেশন স্ট্যাক Home দিয়ে শুরু")
lifecycle = AppLifecycle()
lifecycle.transition('foreground', 'ব্যবহারকারী অ্যাপ আইকনে ট্যাপ করলো')
nav = NavigationStack("Home")
store = Store({"draft_note": None})
store.subscribe(on_note_change)
print("current screen:", nav.current())
print("\n" + "=" * 70)
print("ধাপ (b): ব্যবহারকারী 'New Note' স্ক্রিনে গেলো")
nav.push("NewNote")
print("\n" + "=" * 70)
print("ধাপ (c): ব্যবহারকারী নোট টাইপ করলো, state store-এ সেভ হলো")
note_text = "কেনাকাটার তালিকা: চাল, ডাল, তেল"
store.set_state({"draft_note": note_text})
print("\n" + "=" * 70)
print("ধাপ (d): ব্যাকগ্রাউন্ডে যাওয়ার আগে draft ডিস্কে persist করা হলো -- তারপর OS অ্যাপটি kill করলো")
DEVICE_DISK.set("draft_note", store.state["draft_note"])
print("DEVICE_DISK-এ persist হলো:", DEVICE_DISK.get("draft_note"))
lifecycle.transition('background', 'ব্যবহারকারী হোম বাটন চাপলো')
lifecycle.transition('killed', 'OS মেমোরি খালি করতে অ্যাপ বন্ধ করে দিলো')
print("lifecycle history:", lifecycle.history)
# kill হওয়ার মানে RAM-এর সবকিছু হারিয়ে যাওয়া -- store/nav/lifecycle ইনস্ট্যান্স আর ব্যবহারযোগ্য না,
# কিন্তু DEVICE_DISK (মডিউল ৪-এর একই KeyValueStore ইনস্ট্যান্স) অপরিবর্তিত থেকে যায়
del store
del nav
del lifecycle
print("\n" + "=" * 70)
print("ধাপ (e): অ্যাপ পুনরায় চালু হলো (fresh not_running -> foreground), draft ডিস্ক থেকে ফেরত পড়া হলো")
lifecycle = AppLifecycle() # সম্পূর্ণ নতুন ইনস্ট্যান্স -- killed থেকে সরাসরি resume করা যায় না
lifecycle.transition('foreground', 'ব্যবহারকারী আবার অ্যাপ চালু করলো')
nav = NavigationStack("Home")
store = Store({"draft_note": None})
store.subscribe(on_note_change)
recovered_text = DEVICE_DISK.get("draft_note") # -- একই DEVICE_DISK, নতুন করে তৈরি করা হয়নি
store.set_state({"draft_note": recovered_text})
print("রিলঞ্চের পর store-এ পুনরুদ্ধার করা draft:", store.state["draft_note"])
assert store.state["draft_note"] == note_text, "পার্সিস্টেড নোট মূল নোটের সাথে হুবহু মিলতে হবে"
print("যাচাই সফল: kill + relaunch-এর পরেও নোটটি অবিকৃত অবস্থায় ফিরে এসেছে")
print("\n" + "=" * 70)
print("ধাপ (f): ব্যবহারকারী 'sync' বাটনে ট্যাপ করলো -- রিট্রাই/ব্যাকঅফ সহ নেটওয়ার্ক কল")
success, attempts_taken = sync_with_backoff()
print(f"sync ফলাফল: success={success}, মোট attempts={attempts_taken}")
assert success, "sync() শেষ পর্যন্ত সফল হওয়া উচিত ছিল"
print("\n" + "=" * 70)
print("ধাপ (g): ব্যবহারকারী ছবি অ্যাটাচ করার চেষ্টা করলো -- প্রথমে denied, তারপর granted")
request_permission("camera", "denied")
result_denied = attach_photo()
print("denied অবস্থায় attach_photo() ফলাফল:", result_denied)
assert result_denied is False
request_permission("camera", "granted")
result_granted = attach_photo()
print("granted অবস্থায় attach_photo() ফলাফল:", result_granted)
assert result_granted is True
print("\n" + "=" * 70)
print("সারমর্ম: নোট kill+relaunch-এর পরও টিকে ছিল, sync রিট্রাই করে সফল হয়েছে, এবং ক্যামেরা পারমিশন ঠিকভাবে গেট করেছে।")
del store, del nav, ও del lifecycle দিয়ে RAM-এর
সব অবজেক্ট সত্যিই মুছে ফেলা হয়েছে — কোনো লুকানো রেফারেন্স রাখা হয়নি। ধাপ (e)-এ যা কিছু ব্যবহার করা হয়েছে তা
হয় সম্পূর্ণ নতুন ইনস্ট্যান্স (lifecycle, nav, store),
অথবা একই DEVICE_DISK ইনস্ট্যান্স যা মডিউল ৪-এ একবারই তৈরি হয়েছিল ও কখনো
পুনরায় তৈরি হয়নি — এটাই প্রমাণ করে নোটটি সত্যিই ডিস্ক-স্তর থেকে ফিরে এসেছে, কোনো লুকানো RAM-রেফারেন্স থেকে
নয়।
একটি মোবাইল অ্যাপ আসলে এইরকম কয়েকটি স্বাধীন কিন্তু আন্তঃসংযুক্ত স্তরের সমষ্টি — একটি লাইফসাইকেল যা বলে অ্যাপ কোন মুহূর্তে কী অবস্থায় আছে, একটি নেভিগেশন স্তর যা বলে ব্যবহারকারী কোথায় আছে, একটি স্টেট স্তর যা UI-কে সাম্প্রতিক তথ্য দেখায়, একটি পার্সিস্টেন্স স্তর যা RAM হারিয়ে গেলেও গুরুত্বপূর্ণ ডেটা বাঁচিয়ে রাখে, একটি পারমিশন স্তর যা ডিভাইস-হার্ডওয়্যার অ্যাক্সেস নিয়ন্ত্রণ করে, ও একটি নেটওয়ার্ক স্তর যা অস্থির সংযোগেও নির্ভরযোগ্যভাবে কাজ করে। এই কোর্সে আমরা প্রতিটি স্তর আলাদা আলাদা করে শিখেছি (M1-M12), আর এই শেষ পাঠে দেখলাম কীভাবে তারা একসাথে একটি বাস্তবসম্মত অ্যাপে রূপ নেয় — এখন থেকে যেকোনো নির্দিষ্ট প্ল্যাটফর্মে (iOS/Swift, Android/Kotlin, React Native, Flutter) কাজ করার সময় এই একই অন্তর্নিহিত ধারণাগুলোই ভিন্ন সিনট্যাক্সে খুঁজে পাবেন।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
ধাপ (e)-এ যদি DEVICE_DISK.set(...) ধাপ (d)-এ কল করা না হতো (শুধু store.set_state()
কল হতো, কিন্তু ডিস্কে persist করা না হতো), রিলঞ্চের পর recovered_text-এর মান কী হতো?
DEVICE_DISK.get("draft_note") তখন None রিটার্ন করত (ডিফল্ট মান, যেহেতু key
কখনো সেট হয়নি), কারণ store নিজেই একটি RAM-এর অবজেক্ট ছিল যা del store-এ
হারিয়ে গেছে -- সেভ করা ডেটা কোথাও persist না করা থাকলে kill+relaunch সাইকেলের পর সেটি স্থায়ীভাবে হারিয়ে
যায়। এটাই দেখায় কেন L31-এর মূল শিক্ষা এত গুরুত্বপূর্ণ: গুরুত্বপূর্ণ স্টেট ব্যাকগ্রাউন্ডে যাওয়ার আগেই
ডিস্কে লেখা থাকতে হবে, ব্যাকগ্রাউন্ডে যাওয়ার পরে নয় (কারণ kill যেকোনো মুহূর্তে ঘটতে পারে, কোনো
ওয়ার্নিং ছাড়াই)।
প্র ০২
মডিউল ৭-এর ধাপ (g)-এ প্রথমে request_permission("camera", "denied") ও পরে
request_permission("camera", "granted") -- দুটোই কল করা হয়েছে। বাস্তব একটি অ্যাপে এটি কী
পরিস্থিতির প্রতিনিধিত্ব করে?
এটি প্রতিনিধিত্ব করে যখন ব্যবহারকারী প্রথমবার পারমিশন ডায়ালগে "Deny" চাপে (হয়তো ভুলবশত বা প্রসঙ্গ না
বুঝে), তারপর অ্যাপের সেটিংসে গিয়ে নিজে থেকে ক্যামেরা পারমিশন চালু করে দেয় (অথবা অ্যাপ আবার রিকোয়েস্ট
করলে এবার "Allow" চাপে)। যেহেতু attach_photo() প্রতিবার কল-টাইমে সত্যিকারের
PERMISSIONS ডিকশনারি চেক করে (আগে থেকে ধরে নেয় না), এটি উভয় মুহূর্তেই সঠিক আচরণ করে --
denied অবস্থায় ব্লক করে, granted অবস্থায় অনুমতি দেয়।
প্র ০৩
sync_with_backoff()-এর ভেতরে flaky_sync_call() র্যান্ডম না করে
ডিটারমিনিস্টিক (নির্দিষ্ট সংখ্যক বার ব্যর্থ হওয়া) রাখা হয়েছে কেন?
কারণ একটি শিক্ষামূলক সিমুলেশনের ফলাফল পুনরাবৃত্তিযোগ্য (reproducible) হওয়া দরকার -- র্যান্ডম হলে
প্রতিবার Run করলে ভিন্ন সংখ্যক attempt লাগতে পারত, যা এই পাঠের assert _tries == 3-এর মতো
নির্দিষ্ট যাচাইকে অসম্ভব করে দিত এবং একই কোড দুইজনের কাছে দুই রকম আউটপুট দেখাত। বাস্তব প্রোডাকশন কোডে
নেটওয়ার্ক ব্যর্থতা সত্যিই অনিশ্চিত (random) হয়, কিন্তু ব্যাকঅফ লজিকটি (কতবার চেষ্টা হলো, কত
delay হলো) যেকোনো নির্দিষ্ট ব্যর্থতা-প্যাটার্নের জন্য হাতে-কলমে ট্রেস করা যাওয়া উচিত -- ঠিক যেমন এখানে
করা হয়েছে।
অনুশীলন
-
চিন্তা করুন: মডিউল ৭-এর ধাপ (c)-এ ব্যবহারকারী নোট টাইপ করার সাথে সাথে যদি প্রতিটি
কি-স্ট্রোকে
DEVICE_DISK.set()সরাসরি কল করা হতো (শুধু ধাপ (d)-এ ব্যাকগ্রাউন্ডে যাওয়ার আগে নয়), এর কী সুবিধা ও অসুবিধা হতে পারে?সুবিধা: অ্যাপ যদি কোনো ওয়ার্নিং ছাড়াই ক্র্যাশ করে (শুধু স্বাভাবিক ব্যাকগ্রাউন্ড-তারপর-kill নয়), তাহলেও প্রায় সব লেখা টিকে থাকবে -- কারণ প্রতিটি কি-স্ট্রোকই ডিস্কে গেছে। অসুবিধা: প্রতিটি কি-স্ট্রোকে ডিস্কে লেখা ব্যাটারি ও I/O খরচ অনেক বাড়িয়ে দেয় (M11/L48-এর ব্যাটারি-বাজেট আলোচনার মতো) -- বাস্তব অ্যাপগুলো তাই প্রায়ই "debounce" করে (যেমন টাইপ থামার ৫০০ms পরে একবার সেভ করা) অথবা শুধু ব্যাকগ্রাউন্ডে যাওয়ার মুহূর্তে সেভ করে, এই ক্যাপস্টোনের ধাপ (d)-এর মতোই।
-
পরীক্ষা করুন: মডিউল ৬-এ
flaky_sync_call-এর ডিফল্টfail_until_attempt২ থেকে বাড়িয়ে ৫ করুন এবং মডিউল ৭-এর ধাপ (f) আবার Run করুন (sync_with_backoff(max_attempts=5)ডিফল্টেই চলে) -- ফলাফল কী দেখায়, আর কেন?fail_until_attempt=5মানেflaky_sync_call()প্রথম ৫ বার ব্যর্থ হবে, ৬ষ্ঠ বারে সফল হবে -- কিন্তুsync_with_backoff()-এরmax_attemptsডিফল্ট মান ৫, তাই লুপটি ৫ বার চেষ্টা করেই থেমে যাবে, কখনো সফল হবে না। আউটপুটে "[sync] সর্বোচ্চ চেষ্টা শেষ, ব্যর্থ" প্রিন্ট হবে এবং ফাংশন(False, 5)রিটার্ন করবে -- এটি দেখায় কেন রিট্রাই লজিকে একটি সর্বোচ্চ সীমা (max attempts) থাকা জরুরি, নয়তো একটি সত্যিকারের-ব্যর্থ নেটওয়ার্ক কল অনির্দিষ্টকালের জন্য চেষ্টা চালিয়েই যেত।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস আবার দেখুন ৫৭টি পাঠ · সম্পূর্ণ অভিনন্দন — Mobile App Development কোর্সের সবগুলো পাঠ এখন সম্পূর্ণ। ফাউন্ডেশন থেকে শুরু করে UI/UX, আর্কিটেকচার প্যাটার্ন, নেটিভ বনাম ক্রস-প্ল্যাটফর্ম, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, নেটওয়ার্কিং, ডিভাইস ফিচার, পারফরম্যান্স, টেস্টিং/সিকিউরিটি/ডিপ্লয়মেন্ট, ও এই ক্যাপস্টোন পর্যন্ত — সম্পূর্ণ যাত্রা শেষ হলো। কোনো পাঠ আর "শীঘ্রই আসছে" নেই।
- আগের পাঠ L56 ক্রস-প্ল্যাটফর্ম বনাম নেটিভ সিদ্ধান্ত কাঠামো — একটি worked example।
- Full-Stack Web Frameworks কোর্স সহোদর কোর্স এই কোর্স জুড়ে যে সাধারণ আর্কিটেকচারাল ভিত্তি (লাইফসাইকেল-অ্যাওয়্যার স্টেট, ইউনিডাইরেকশনাল ডেটা ফ্লো, রিট্রাই/ব্যাকঅফ, টেস্টিং পিরামিড, CI/CD) মোবাইল-প্রেক্ষাপটে প্রয়োগ করা হয়েছে, তার ওয়েব-প্রেক্ষাপট সংস্করণ সেই কোর্সে পাওয়া যাবে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks ও Mobile App Development — সব এক জায়গায়।