Advanced Web Application Architecture
Chapter: The Domain Model – Generating the identifier earlier (2.6)– Using a Value Object for the identifier (2.7)في النقط اللي فاتت من الكتاب، اتكلمنا عن مفاهيم مهمة جدًا:Domainهو المجال اللي التطبيق شغال فيه (بيع كتب – حجز – توصيل… إلخ) Modelهو التمثيل المنطقي للحكاية اللي البزنس عايز يحكيهاGatewayطريقة نشيل بيها الـ SQL من الكود، وكل Class يمثل جدول كامل في الداتابيزEntityلما نقولOrderإحنا مش بنقول row في جدول إحنا بنقول: Order = طلب حقيقي في البزنس عنده:Quantity
Amountومحمي بقواعد (Assertions) مينفعش يبقى مش تمام
الـ Repository مجرد طبقة وسيطة بين عالمين:
👈 Domain → Objects، Rules، Business Logic
👈 Database → Tables، Rows، Arrays، SQL
و كمان عملنا ال implementation بتاع الRepo خش شوف
-----------------------------------------------------------------------------------------------
قبل كدا السيناريو كده
Controller يعمل Order يبعته للـ Repository Repository يعمل save Database تولّد ID Repository يرجّع الـ ID Controller يحطه جوه الـ Order أو يستخدمه
الشكل ده معناه إيه؟
الـ Order اتخلق من غير ID
الـ Order استنى الداتابيز تقول له “إنت مين”
الدومين بقى معتمد على التخزين
وده غلط كبير في Domain Design
ليه غلط؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟
1
Order من غير ID = Order ناقص و احنا قولنا ان الOrder في البزنس مش كائن وهمي
👈 Order حقيقي = لازم يكون له هوية
الهوية دي
بتستخدم في logging و في events و بتستخدم في tracking و بتستخدم في comparisons
-----------------------------------------------------------------------------------
يعني لو الـ Order لسه معندوش ID يبقى لسه مش كائن كامل وده ضد فكرة Entity = كائن له هوية ثابتة
-----------------------------------------------------------------------------------
طب بس كدا لأ لسه فيه غلط تاني
2
خليت الداتابيز تقرر هوية الدومين أنت كده قلت ضمنيًا
“يا Database، قولي الـ Order ده اسمه إيه”
وده خطر لأن:
مش كل Database فيها auto increment و مش كل storage بيرجع ID فورًا في systems async مفيش ID أصلاً وقت الـ save و لو غيرت DB → المنطق كله يقع
-----------------------------------------------------------------------------------
يعني الدومين بقى ضعيف والداتابيز بقت صاحبة قرار وده عكس الصح
-----------------------------------------------------------------------------------
طب بس كدا لأ لسه فيه غلط تالت
3
الsave بقى ليه side effect خطر
ليه ؟؟
علشان الsave المفروض يخزن وبس
لكن لما يرجّع ID و يكمّل الكائن و يغيّر حالته
يبقى كده
كسرنا Command / Query Separation
يعني إيه
يا تغيّر في السيستم او يا تسأل السيستم
بس ما تعملش الاتنين في نفس الوقت
-------------------------------------------------------------------
الCommand = أمر → يغيّر حالة السيستم
و الQuery = سؤال → يرجّع داتا بس، من غير أي تغيير
من الاخر
👈 الsave بقى بيعمل أكتر من دور
------------------------------------------------------------------
الوقتي ه Generating the identifier earlier
يعني ايه بقا
الـ Order لازم يبقى كامل من أول ثانية اتخلق فيها
وكامل يعني
معاه ID و معاه بياناته و معاه قواعده
من الاخر مش مستني حد يكمّله بعدين.
طب و الحل بقاااا؟؟؟؟
الحل مش في نوع الـ ID و لكن في توقيت إنشاء الـ ID
بدل ما:
الداتابيز تولّد الـ ID نخلي الدومين يولّد الـ ID أو على الأقل يطلبه قبل الإنشاء
طب بقولك م تيجي نفكها حتة حتة
اولا الCommand يعني إيه؟
أي ميثود بتعمل insert او update اوdelete أو أي تغيير في الحالة
دي Command
احنا قولنا القاعدة اي ؟؟؟؟؟؟
الـ Command مايرجعش داتا أقصى حاجة void أو Exception لو فشل--------------------------------------------
ثانيا الQuery يعني إيه؟
أي ميثود بتسأل عن حاجة بترجع داتا من غير ما تلمس الحالة
دي Query
احنا قولنا القاعدة اي ؟؟؟؟؟؟
الـ Query مايغيّرش أي حاجة
تعالي نشوف الكلام دا معناه اي و اي الحل زي م قولنا الحل
مش في نوع الـ ID و لكن في توقيت إنشاء الـ ID
يعني اي ندي الـ Order الـ ID من الأول يعني نعرفه ف ال constractor

معنى الكلام ده؟
Order مش بيتولد إلا وهو كامل
مفيش Order “نص مستوي”
الهوية جزء من تعريف الكائن
التغيير التاني: Repository بقى يجيب ID مش يولّده وقت save

ركز هنا حلو اوي
👈 Repository مش بيرجع ID من save
👈 Repository بيطلع ID قبل الإنشاء
ليه Repository هو اللي بيطلع الـ ID؟
سؤال مهم جدا عشان
👈 هو الوحيد اللي عارف طريقة التخزين و هو اللي يعرف هل Integer ولا UUID ولا Sequence
👈 لكن الدومين مش مهتم يعرف
--------------------------------------------------------------------------------------
الدومين بس يقول “هاتلي هوية صالحة” و دا فرق فلسفي مش شكلي بس
--------------------------------------------------------------------------------------
👈 مثال naive (بس تعليمي)

الطريقة دي:
تنفع للفهم لكن خطره وده طبيعي… المهم الفكرة مش التنفيذ.
ايه هيه المشكلة الكبيرة اوي بقا شوفتها؟؟؟؟؟؟
الطريقة دي بسيطة، لكن لو فيه users كتير بيتعاملوا مع النظام في نفس الوقت، ممكن يحصل تعارض IDs.
الحل الصح عمليًا: UUID

ليه UUID ممتاز هنا؟
مش محتاج DB و مفيش collisions و كمان ينفع distributed systems
و ف النهاية بتحصل ع Order مستقل تمامًا
Controller بعد 2.6

-----------------------------------------------------------------------------------------
النتيجة: الأوردر جاهز بالـ ID من أول لحظة → ممتاز للـ Domain Model.
Order اتخلق كامل و save مجرد حفظ و مفيش تعديل بعد الإنشاء
------------------------------------------------------------------------------------------
حل متقدم: Sequence Table (لما تحب Integer من غير مشاكل)

-------------------------------------------------------------------------------------------------
الخلاصة الحقيقية لـ 2.6
2.6 مش عن ID و لكن عن استقلال الدومين و اكتمال الكيان من لحظة ولادته و كمان فصل القرار عن التخزين و اخيرا احترام الـ Entity كObject مش Row
-------------------------------------------------------------------------------------------------
قبل الOrder ناقص DB بتتحكم save بيعمل أكتر من دوره
بعد الOrder كامل من البداية DB مجرد تنفيذ save() void نظيف Domain محترم نفسه
-------------------------------------------------------------------------------------------------
كدا شوفنا اي الغلط ف اني استخدم ال id بعد م احفظ الorder و الحل اي بس تعالي نكمل
--------------------------------------------------------------------------------------------------
2.7 Using a Value Object for the identifier
يعني اي؟؟؟؟؟
مش كفاية إننا ندي الـ Order ID من الأول
لا، إحنا كمان مش عايزين الـ ID يبقى primitive type زيintأوstring
عايزينه يبقى كائن له معنى في الدومين → Value Object
ليييييه
نقدر نغيّر نوع الـ ID (int, UUID, ULID) بسهولة
نخفي التفاصيل الداخلية عن باقي النظام
نخلي الـ Order مستقل تمامًا عن طريقة التخزين
الهدف
نغلف نوع البيانات الداخلي → Entity تتحكم فيه بالكامل → Encapsulation محفوظ
يعني اي ؟؟؟
يعني بدل م اقول int $orderId اقول OrderId $orderId
وده فرق كبير جدًا في التصميم، مش تجميل كود وخلاص.
ليه أصلاً نعمل Value Object للـ ID؟
خلينا نربطها باللي فات قبل كدا
كنا عملنا حاجة حلوة الـ Order بياخد ID من الأول Repository عنده nextIdentityبس لسه فيه مشكلة 👇
الـ Order عارف إن الـ ID UUID وعارف إنه String و عارف إنه متخزن إزاي وده معناه:
تفاصيل تخزين بدأت تتسرب جوه الـ Domain
Encapsulation على وشك يتكسر
طب و اي الحل الي بقاااااا؟؟؟
👈 نغلف الـ ID جوه Value Object
يعني نعمل كلاس اسمه OrderId
👈 هو الوحيد اللي عارف نوع الـ ID
👈 عارف بيتحوّل لإيه
👈 عارف جاي من UUID ولا int ولا ULID
والباقي كله يتعامل معاه كـ OrderId وخلاص
تعالي نعمله سواااااا

تعالي بقا علشان الكود دا محتاج واقفة
اول معلومة ليه final
👈 final → مفيش inheritance لأن ده قيمة مش سلوك
👈 يعني محدش من بره يعرف الـ ID شكله إيه
👈 يعني حطيت اول قفل ع Encapsulation
تاني و اهم معلومة ليه الـ constructor private؟
👈 علشان محدش يعمل (new OrderId($anyThing
👈 إنت اللي بتتحكم إزاي الكائن ده يتخلق وده جوهر الـ Value Object

ده اسمه Named Constructor و معناه طريقة واضحة لبناء الـ OrderId
لو بكرة غيرت UUID بـ ULID هتغير هنا وبس والـ Domain كله مش هيحس
تالت معلومة خاصة ب asString
اللي عايز يطلع الـ ID بره الدومين
ياخده string وبس
يعني
👈 Session او Database او API او Logs كلهم ياخدوا string لكن محدش يلمس الـ UUID نفسه
👈 Encapsulation اتقفل بالقفل التاني
نعدي على الـ Entity

--------------------------------------------------------------------------------------------------
لما كتبت كدا private OrderId $id
هنا حصل التحول الحقيقي الـ Order مبقاش عارف UUID و مبقاش عارف string و مبقاش عارف int
هو عارف حاجة واحدة 👈 أنا عندي OrderId
--------------------------------------------------------------------------------------------------
لما كتبت كدا OrderId $id جوه ال Constractor
هنا Order كامل من أول لحظة الـ ID جزء من هويته مش مستني DB ولا Repository
--------------------------------------------------------------------------------------------------
لما كتبت كدا public function id(): OrderId
إنت ما رجعتش string رجعت OrderId
يعني
اللي جوه الدومين يتعامل مع Value Object
التحويل يحصل عند الحدود بس (Boundaries)
--------------------------------------------------------------------------------------------------
لما كتبت كدا this->id->asString جوه الmappedData
Entity هي اللي قررت إمتى تطلع الـ ID بره وبأي شكل
مش Repository مش Controller ده Encapsulation صح
نعدي على الـ Repo

--------------------------------------------------------------------------------------------------
الـ Repository بينفّذ و مش بيفكر و مش بيقرر هو بس بيقول اتفضل OrderId جاهز
--------------------------------------------------------------------------------------------------
لما كتبت كدا public function save(Order $order): void
save() → voidيعني مفيش return و مفيش IDs طالعةو مفيش leaks و اخيرا CQRS محترم
بمعني تاني المشكلة الي كتبناها فوق
CQRS = Command Query Responsibility Segregation
يعني: نفصل بين الحاجات اللي بتغير حالة النظام (Commands) والحاجات اللي بتقرا البيانات (Queries).
Command: أي حاجة بتعمل تغيير (insert, update, delete) → مفيش return غير نجاح/فشل، مش data.
Query: أي حاجة عايز تعرف بيها حاجة عن النظام → مفيش تغيير، بس بترجع البيانات.
--------------------------------------------------------------------------------------------------
نعدي على الـ Controller بقا

--------------------------------------------------------------------------------------------------
لما كتبت كدا orderId = $orderRepository->nextIdentity
الController ما يعرفش UUID وميعرفش string من الاخر خد OrderId وخلاص
--------------------------------------------------------------------------------------------------
لما كتبت كدا new Order و اعطيته كل الداتا حتي الId
الـ Order اتخلق: كامل و متماسك و مستقل عن التخزين
--------------------------------------------------------------------------------------------------
لما كتبت كدا orderRepository->save($order)
الـ Order اتحفظ وبس من غير رجوع و كمان من غير side effects
--------------------------------------------------------------------------------------------------
لما خليت الContainer يعمل set للcurrentOrder
عن طريق order->asstring
هنا بس خرجنا بره الدومين فحوّلنا الـ Value Object لـ string وده مقصود وده صح
--------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------
الخلاصة بقاااااااااااا
2.7 مش عن UUID و لكن عن السيطرة
السيطرة على النوع و السيطرة على الإنشاء و السيطرة على الخروج بره الدومين
النتيجة:
Order كائن محترم
Encapsulation متقفل
Repository عبد مطيع
Database تفصيلة تنفيذية
--------------------------------------------------------------------------------------------------
بعد ما خلّينا الـ constructor private
طلع سؤال ذكي جدًا:
هو كده إحنا طبقنا Singleton Design Pattern؟
الإجابة المختصرة:
لا لا لا لا لا لا لا لا لا لا لا لا لا لا لا
ليه OrderId مش Singleton؟
خلّينا الأول نوضح الفرق الأساسي، لأن هنا معظم الناس بتتلخبط.
أولًا: يعني إيه Singleton؟
Singleton هدفه حاجة واحدة بس يبقى فيه نسخة واحدة فقط من الـ Object في السيستم كله
خصائصه المشهورة:
Instance واحدة بس
static getInstanceنفس الـ Object بيتعاد كل مرة
مفيش new خالص من برّه
طيب Value Object (زي OrderId) وضعه إيه؟
Value Object هدفه مختلف تمامًا التحكم في إزاي الكائن يتخلق مش عدد النسخ اللي تتخلق
يعني عادي جدًا:
100OrderIdاو حتي 1,000OrderIdاو مليونOrderIdولا أي مشكلة خالص كل واحد مستقل المهم كلهم متخلقين صح
يبقى ليه الـ constructor معمول private؟
خلّيك فاكر الجملة دي كويس إنت اللي بتتحكم إزاي الكائن يتخلق ودي مش رفاهية… دي حماية للدومين.
المشكلة لو الـ constructor كان public
كان يحصل إيه؟
أي حد ممكن يعملstring او int اوUUID مش valid اوnull اوgarbage
الObject يتخلق بس مكسور من جوه القواعد اتكسرت قبل ما الدومين يبدأ أصلاً
يعني:
Encapsulation راحت
Value Object فقد قيمته
الدومين بقى مكشوف
يبقي الحل اي بقا ؟؟؟؟؟؟؟؟؟؟؟؟
private constractor
--------------------------------------------------------------------------------------------------
طب إزاي الكائن يتخلق أصلًا؟
هنا بقى يدخل Factory Method
Factory Method = طريقة رسمية لإنشاء Object
يعني اي ؟؟
هي دالة شغلتها الأساسية إنها تخلق Object بدل ما الناس تستخدم new مباشرة.
ده Factory Method كامل
ليه السطر ده Factory Method؟
خلّينا نمشي عملي، مش نظري.
Factory Method هوه Method مسؤول عن إنشاء كائن بدل ما الإنشاء يحصل بـ new من برّه وده اللي حصل حرفيًا.--------------------------------------------------------------------------------------------------الصورة الكاملة بقى
اولا
قفلنا الإنشاء المباشر عن طريق ال private constractor
ثانيا
فتحنا باب واحد رسمي عن طريق static function fromUuid
static→ من غير instance بيرجعOrderIdجاهز
يعني
عايز OrderId؟
ادخل من الباب ده بس تعالي من عند الstatic function
----------------------------------------------------------------------------------------------------------------------------------------------------------
طيب ليه مش constructor وخلاص؟
عشان الـ constructor
بيجبرك تستخدم newصعب تمنع إساءة الاستخدام
مبيشرحش الكائن اتخلق إزاي وليه
لكن OrderId::fromUuid($uuid) بتقول معنى واضح:
OrderId جاي من UUID مش new وخلاص
طيب ليه استخدمنا self؟
معناها: رجّع نفس الكلاس لو اسم الكلاس اتغير → الكود سليم و لو حصل inheritance → still safe
والمهم
الـ Factory Method بيرجع نفس نوعه
هل كل Factory Method لازم يكون static؟
لا لا لا
بس في Value Objects
static هو الطبيعيلأنك معندكش instance أصلًا
الهدف: خلق كائن من قيمة
وده اللي هنا بالظبط.
ربط كل ده بالـ Encapsulation
الـ Encapsulation اتحفظ إزاي؟
الإنشاء اتقفل → private constructorالقواعد اتحطت في مكان واحد
مفيش خلق عشوائي
يعني
أي OrderId في السيستم عدّى من fromUuidوده ضمان قوي جدًا.
الخلاصة النهائية (مهمة جدًا)
OrderId مش Singleton
private constructor ≠ Singleton
الهدف مش نسخة واحدة
الهدف خلق مضبوط
private constructor = منع
Factory Method = بديل منظم
Factory Method = باب رسمي للإنشاء
fromUuid = المصنع
self = ضمان النوع
Encapsulation محفوظ
الدومين متحكم في نفسه