معماری چندکسبوکاری، تجاری و تجارت الکترونیک · فصل ۷ — Connector، Order، Packing، Barcode، Carrier و Notification · 275
Carrier Integration و Tracking Events
workflow تجاری را به UI یا integration منفرد تقلیل میدهد و state، rights، approval و measurement را یکپارچه نمیکند. موضوع جاری: «Carrier Integration و Tracking Events». میتواند «Carrier Integration و Tracking Events» را برای یک workflow محدود با state/contract/approval و verification مناسب طراحی کند.
خلاصهٔ درس
workflow تجاری را به UI یا integration منفرد تقلیل میدهد و state، rights، approval و measurement را یکپارچه نمیکند. موضوع جاری: «Carrier Integration و Tracking Events».
میتواند «Carrier Integration و Tracking Events» را برای یک workflow محدود با state/contract/approval و verification مناسب طراحی کند.
خروجی این درس
میتواند «Carrier Integration و Tracking Events» را برای یک workflow محدود با state/contract/approval و verification مناسب طراحی کند.
مسئلهی واقعی: webhook آمده، اما آیا واقعاً «وضعیت مرسوله» را میدانیم؟
یک carrier ممکن است همان event را دوباره بفرستد، event قدیمی را بعد از event جدید تحویل دهد، ETA را جابهجا کند، متن انسانی status را عوض کند یا یک event را فقط در pull بعدی نشان دهد. اگر business logic مستقیماً با رشتههایی مثل Delivered، In transit یا متن فارسی/انگلیسی provider تصمیم بگیرد، اولین تغییر vendor میتواند state داخلی را خراب کند. هدف این درس ساختن یک Carrier Tracking Normalization and Reconciliation Ledger v1 است: provider evidence را immutable نگه میداریم، identity و زمان را جدا ثبت میکنیم، duplicate و out-of-order را تشخیص میدهیم، معنا را با adapter به vocabulary کوچک داخلی نگاشت میکنیم و push را با pull reconciliation میسنجیم.
مرز درس
مالکیت COM-034 فقط «carrier event truth + normalization + reconciliation» است. COM-033 مالک scan/QR UX و decoded identity است؛ COM-035 مالک پیام مشتری و Minimal PII است. این درس هیچ carrier subscription زنده، shipment mutation، billing action یا ارسال پیام واقعی انجام نمیدهد.
سه خط قرمز:
- provider text کلید canonical نیست. متن برای نمایش/تشخیص انسانی مفید است، نه برای state machine.
- ETA proof of delivery نیست. زمان تخمینی حتی اگر دقیق باشد، فقط estimate evidence است.
- webhook arrival order، event order نیست.
received_atباید ازprovider_event_timeجدا بماند.
مدل ذهنی: Receive → Authenticate → Append → Dedupe → Order → Normalize → Reconcile → Project
هر payload ورودی اول بهعنوان evidence ثبت میشود، نه بهعنوان «حقیقت نهایی business». مسیر صحیح:
provider payload → authenticity/subscription check → immutable event append → dedupe → event-time ordering → provider adapter → normalized state → push/pull reconciliation → bounded customer projection
این جداسازی اجازه میدهد provider-specific detail را حفظ کنیم ولی business logic به code/text یک carrier خاص قفل نشود. provider_event_time میگوید carrier ادعا میکند رخداد چه زمانی اتفاق افتاده؛ received_at میگوید سیستم ما چه زمانی payload را دریافت کرده. این دو زمان عمداً یکی نیستند.
FACT، INFERENCE و DESIGN را قاطی نکن
FACT: DHL Unified Push v1.2.5 push notification برای status و delivery-time/routing update مستند میکند. FedEx AIV هم webhookهای near-real-time برای tracking event مستند میکند. GS1 EPCIS مدل visibility event و تفاوت event-time با record-time را استاندارد میکند.
Freshness correction مهم: DHL در ۲۰۲۶ تغییر timestamp با timezone offset و متنهای استانداردشده را اعلام کرد، اما پس از rollout بهدلیل performance issue موقتاً rollback کرد. نتیجهی آموزشی این نیست که «DHL همیشه offset دارد»؛ نتیجه این است که format و description vendor میتواند تغییر کند و حتی برگردد.
INFERENCE: برای multi-carrier، code/text/time/receive/dedupe/normalized state/estimate باید جدا باشند تا تغییر یک vendor business logic را نشکند.
DESIGN: MAKNOW vocabulary محدود داخلی، stateهای UNKNOWN/EXCEPTION/ESTIMATE_ONLY، reconciliation و ممنوعیت promotion از ETA/text به DELIVERED را الزام میکند.
قرارداد ۲۵بعدی Tracking Ledger
| carrier_account_ref | provider_event_id | provider_event_code | provider_event_time | received_at | delivery_channel | dedupe_key | normalized_event_type | normalized_state | event_order_state | reconciliation_cursor | evidence_ref |
|---|---|---|---|---|---|---|---|---|---|---|---|
| DHL-ACCT-A | DHL-E100 | transit | 2026-09-14T10:00:00+02:00 | 2026-09-14T10:00:08+02:00 | PUSH | dhl:SHP-001:DHL-E100 | MOVEMENT | IN_TRANSIT | IN_ORDER | 2026-09-14T10:00:00+02:00 | EV-DHL-100 |
| DHL-ACCT-A | DHL-E100 | transit | 2026-09-14T10:00:00+02:00 | 2026-09-14T10:03:15+02:00 | PUSH | dhl:SHP-001:DHL-E100 | MOVEMENT | DUPLICATE | DUPLICATE | 2026-09-14T10:00:00+02:00 | EV-DHL-100-R |
| DHL-ACCT-A | DHL-E099 | transit | 2026-09-14T09:40:00+02:00 | 2026-09-14T10:05:00+02:00 | PUSH | dhl:SHP-001:DHL-E099 | MOVEMENT | OUT_OF_ORDER | OLDER_THAN_CURSOR | 2026-09-14T10:00:00+02:00 | EV-DHL-099 |
| FDX-ACCT-B | FDX-500 | DL | 2026-09-14T16:20:00-04:00 | 2026-09-14T20:20:12Z | PUSH | fdx:SHP-002:FDX-500 | DELIVERY_CONFIRMED | DELIVERED | IN_ORDER | 2026-09-14T16:20:00-04:00 | EV-FDX-500 |
Gate 1 — Event identity را قبل از state بساز
provider_event_id بهترین identity است اگر vendor آن را پایدار و واقعاً event-level ارائه کند. اگر کافی نیست، dedupe_key باید از فیلدهای پایدار و versioned ساخته شود؛ نه از متن انسانی که ممکن است عوض شود. payload_digest evidence برای payload دقیق است، نه جایگزین semantic identity.
Gate 2 — Authenticity و subscription context را evidence کن
برای webhook باید معلوم باشد payload از چه subscription/account/channel آمده و کنترل authenticity چه بوده است. نبود این evidence یعنی HOLD یا UNKNOWN، نه auto-accept.
Gate 3 — Event time و receipt time را قاطی نکن
یک event با زمان 10:00 ممکن است ساعت 10:07 برسد و event ساعت 10:05 ممکن است 10:06 رسیده باشد. ordering canonical بر مبنای event semantics انجام میشود، اما receive order برای observability و incident analysis حفظ میشود.
Gate 4 — Duplicate را append کن، اما دوباره business transition نده
duplicate evidence را حذف نکن؛ mark کن که duplicate است و به event قبلی اشاره بده. این باعث auditability میشود. ولی همان event نباید دوباره state transition، notification یا downstream side effect ایجاد کند.
Gate 5 — Out-of-order نباید state را عقب ببرد
اگر آخرین canonical evidence OUT_FOR_DELIVERY در 12:00 است و ساعت 12:05 یک event IN_TRANSIT مربوط به 11:30 میرسد، ledger آن را نگه میدارد اما canonical state را به عقب برنمیگرداند.
Gate 6 — Provider adapter، نه provider string comparison
هر carrier یک adapter دارد: (provider_event_code + context) → normalized_event_type/state. متن انسانی فقط detail است. اگر code جدید ناشناخته آمد، نتیجه UNKNOWN است؛ business logic نباید با if "delivered" in text.lower() تصمیم بگیرد.
Gate 7 — ETA را در lane جدا نگه دار
estimated_delivery_at و estimate_confidence_or_source برای prediction هستند. ETA میتواند تغییر کند بدون اینکه lifecycle shipment تغییر کرده باشد. DELIVERED فقط با event صریح و معتبر delivery promotion میگیرد.
Gate 8 — Push و Pull مکملاند
push latency را کم میکند؛ pull reconciliation completeness را میسنجد. اگر nightly pull eventی نشان داد که در push ledger نیست، RECONCILIATION_GAP ثبت میشود و event با provenance خودش وارد ledger میشود. نتیجه «webhook خراب است» نیست؛ نتیجه «دیدگاه push ناقص بوده» است.
Gate 9 — Customer projection باید از normalized truth بیاید
customer-visible projection فقط fieldهای safe و لازم را میگیرد؛ raw provider payload، internal exception detail و vendor-specific text بهصورت مستقیم پیام مشتری نمیشوند. COM-035 تصمیم نهایی پیام/PII را میگیرد.
Gate 10 — Missing decisive evidence = fail closed
اگر authenticity، event code mapping، event time یا evidence_ref مفقود است، state موفق نساز. HOLD/UNKNOWN/DISPUTED state واقعی هستند، نه خطاهای موقت برای پنهانکردن.
مثالهای حلشده
1. Push معتبر با code پایدار → IN_TRANSIT
carrier event معتبر است، code در adapter شناخته شده، event time و receive time ثبت شده و dedupe جدید است. state داخلی IN_TRANSIT میشود؛ provider text صرفاً detail میماند.
2. همان event دوباره میآید → DUPLICATE
provider_event_id و dedupe_key با evidence قبلی match میکنند. ledger payload دوم را برای audit نگه میدارد اما canonical state و side effect تغییر نمیکند.
3. Event قدیمی بعد از event جدید میرسد → OUT_OF_ORDER
receive order جدیدتر است ولی event time قدیمیتر از canonical cursor است. event append میشود؛ state regression ممنوع است.
4. ETA فردا شد امروز → ESTIMATE_ONLY
تغییر ETA در lane estimate ثبت میشود. اگر متن provider حتی عبارت delivered داشته باشد اما code/event type delivery را ثابت نکند، DELIVERED نمیسازیم.
5. Pull یک event گمشده پیدا میکند → RECONCILIATION_GAP
reconciliation cursor نشان میدهد event در pull هست ولی در push ledger نیست. آن را با source=PULL ثبت میکنیم و gap را observable نگه میداریم.
6. Carrier دوم با codeهای متفاوت
برای FedEx یا carrier دوم adapter جدید میسازیم و contract داخلی را همان نگه میداریم. business logic نباید به لیستی از if carrier == X and text == Y تبدیل شود.
Failure modeهای رایج
- Direct string mapping: متن provider مستقیماً state داخلی میشود.
- Receive-order fallacy: آخرین payload رسیده، آخرین رخداد فرض میشود.
- Duplicate side effects: retry webhook باعث notification یا transition دوباره میشود.
- ETA promotion: ETA یا delivery window به delivered تبدیل میشود.
- Text-based delivery: وجود واژهی delivered در description به proof تبدیل میشود.
- Push-only worldview: reconciliation pull حذف میشود چون webhook «real-time» است.
- Schema optimism: parser فقط enumهای امروز را میپذیرد؛ value جدید provider سیستم را میشکند.
- Second-carrier copy: mapping carrier اول برای carrier دوم reuse میشود بدون adapter recomputation.
Alternatives و trade-offها
Push-only: latency خوب، completeness و recovery ضعیفتر. برای سامانهای که event miss/outage اهمیت دارد کافی نیست.
Polling-only: سادهتر و recoverable، اما latency/traffic بیشتر و cadence-driven است. در بعضی providerها rate/expense محدودکننده میشود.
Push + reconciliation pull: پیچیدگی بیشتر، اما event latency و completeness evidence را از هم جدا میکند. این الگوی پیشفرض این درس است.
Universal status string: ساده در demo، شکننده در multi-carrier. Provider adapter + normalized vocabulary کمی کار بیشتر دارد اما blast radius تغییر vendor را محدود میکند.
Single timestamp: ارزانتر، اما incident/debug/audit را مبهم میکند. event_time + received_at هزینهی storage ناچیزی در برابر ارزش evidence دارد.
Experienced bridge: ده سؤال review
- event identity واقعاً stable است یا از text/hash تصادفی ساخته شده؟
- authenticity/subscription evidence در کجا نگه داشته میشود؟
- duplicate چگونه بدون side effect مجدد ثبت میشود؟
- ordering بر اساس کدام timestamp است و clock ambiguity چگونه مدیریت میشود؟
- unknown provider code چه stateی میگیرد؟
- ETA چگونه از lifecycle state جداست؟
- late event چگونه از state regression جلوگیری میکند؟
- reconciliation cursor دقیقاً completeness را تا کجا اثبات میکند؟
- customer projection چه provider-specific dataای را عمداً حذف میکند؟
- با اضافهشدن carrier دوم، چه چیز reuse میشود و چه چیز باید recompute شود؟
تمرین هدایتشده و مستقل
در Lab آفلاین، هشت fixture داریم. ابتدا baseline را اجرا کن. سپس duplicate، out-of-order، ETA-only، text-change، pull-only gap، missing-auth و second-carrier transfer را بررسی کن. هدف گرفتن label نیست؛ باید بفهمی کدام evidence dimension تصمیم را عوض کرده است.
بعد یک fixture جدید بساز که provider code ناشناخته دارد ولی متن آن خیلی شبیه delivered است. پاسخ صحیح نباید با text search ساخته شود. سپس یک carrier دوم اضافه کن و فقط adapter را تغییر بده؛ contract داخلی و business logic باید ثابت بمانند.
Verification evidence
خروجی قابلقبول باید اینها را داشته باشد:
- ledger با هر ۲۵ dimension یا دلیل صریح برای nullable بودن field؛
- duplicate و out-of-order بدون canonical mutation نادرست؛
- mapping مبتنی بر provider code/context، نه free text؛
- ETA در estimate lane؛
- push/pull reconciliation با cursor؛
- customer projection محدود و جدا از raw payload؛
- evidence_ref برای تصمیمهای decisive؛
- transfer به carrier دوم بدون provider-string business branching.
Lab این package deterministic و کاملاً آفلاین است؛ هیچ API key، shipment واقعی یا network access لازم ندارد.
Assessment و transfer
برای جمعبندی باید بتوانی چهار کار را مستقل انجام دهی:
- مرز COM-034 و یک non-goal مجاور را توضیح دهی؛
- baseline ledger را از fixture بسازی؛
- duplicate یا out-of-order mutation را تشخیص دهی و repair حداقلی را بگویی؛
- carrier دوم را با adapter جدید وارد کنی، بدون اینکه business logic را به provider-specific string comparison تبدیل کنی.
اگر baseline decision را کپی کردی ولی changed evidence را recompute نکردی، transfer fail است.
منابع و freshness
- DHL Shipment Tracking Unified Push v1.2.5 — current push/subscription/status evidence؛ review due: 2026-10-18.
- DHL Parcel Germany 2026 backend notice — timestamp/text change و rollback بعدی؛ این history عمداً بهعنوان volatility evidence استفاده شده، نه current-format guarantee.
- FedEx Advanced Integrated Visibility — near-real-time webhook/event evidence؛ current service announcements نشان میدهند operational delay/republish میتواند رخ دهد، بنابراین completeness را از marketing phrase نتیجه نمیگیریم.
- GS1 EPCIS 2.0.1 — event model، eventTime و recordTime semantics؛ foundational reference.
هر claim بیرونی فقط FACT source است. canonical states، promotion rules و reconciliation policy طراحی MAKNOW هستند.
ارزیابی
پاسخنامهی این درس در بستهی آموزش وجود ندارد؛ خودت جواب بده و صادقانه علامت بزن. «نیاز به مرور» درس را به صف تکرار میفرستد.
COM-034-S1 retrieval
Explain the COM-034 boundary and one adjacent non-goal.
COM-034-S2 application
Build Carrier Tracking Normalization and Reconciliation Ledger v1 from the baseline fixture.
COM-034-S3 diagnosis
Diagnose a duplicate provider event and a late older event without rewriting canonical history.
COM-034-S4 transfer
Add a second carrier through a new adapter while preserving the internal contract.
ملاکهای ارزیابی (rubric)
- Identity & boundary: owned carrier-event boundary and adjacent non-goals are explicit.
- Evidence binding: provider event identity, event/receive time, dedupe and source evidence are reproducible.
- Negative evidence: duplicates, late events, estimates, missing authenticity and reconciliation gaps do not become false success.
- Bounded decision: normalized state is no stronger than evidence.
- Transfer: carrier two is integrated through a recomputed adapter, not copied string rules.
منابع و تازگی
مطالعهٔ موارد (case studies)
فایلهای آزمایشگاه
فایل run_checks.py را اجرا کنید. هشت fixture مصنوعی baseline، duplicate، out-of-order، ETA-only، تغییر description، gap در reconciliation، نبود authenticity و انتقال به carrier دوم را میسنجند. هیچ شبکه، API key یا دادهی واقعی استفاده نمیشود.