برداشتی از کتاب Architecture Patterns with Python فصل اول - مدل‌سازی دامنه

طرح از فتانه کهن‌زاده
طرح از فتانه کهن‌زاده


در این مقاله به فصل اول کتاب می‌پردازیم. نام این فصل مدل‌سازی دامنه یا domain modeling می‌باشد.

این مقاله صرفاً ترجمه جمله به جمله کتاب نمی‌باشد و بیشتر شبیه به یک یادداشت شخصی است. امیدوارم که دوستان دیگر هم بتوانند از آن استفاده کنند.

در این فصل یادخواهید گرفت که

  • مدل دامنه چیست.
  • چگونه می‌توان یک مدل دامنه از کسب و کار تهیه کرد.
  • تفاوت بین value object و entity در چیست.
  • عملیات‌های entity می‌توانند در خارج از entity به صورت domain service نوشته شوند.

در راستای این مقاله، می‌توانید مقالات زیر را هم مشاهده کنید:

منظور از یک مدل دامنه چیست؟

در مقدمه کتاب از اصطلاح business logic layer یا لایه منطقی بیزینس به عنوان لایه میانی در معماری سه‌لایه استفاده شده بود. اما کتاب در ادامه از اصطلاح مدل دامنه یا domain model بجای آن استفاده می‌کند. این اصطلاح از مبحث طراحی دامنه محور یا DDD گرفته شده است. اما مدل دامنه به چه معنی است. در ابتدا دامنه و مدل را به صورت جداگانه تعریف می‌کنیم. سپس از این دو استفاده می‌کنیم و یک تعریف روشن از مدل دامنه ارائه می‌دهیم.

دامنه روشی شیک برای بیان مسئله‌ای است که می‌خواهید حل کنید. با یک مثال تعریف دامنه را بیشتر توضیح می‌دهیم. یک فروشگاه آنلاین مبلمان را در نظر بگیرید. با در نظر گرفتن اینکه در مورد چه سیستمی در این فروشگاه آنلاین مبلمان صحبت می‌کنید، دامنه می‌تواند، خرید مبلمان، طراحی محصول و یا بسته‌بندی و تحویل آن باشد. کار برنامه‌نویسان بهبود و اتوماتیک کردن فرایندهای کسب و کار است. پس می‌توان گفت، دامنه مجموعه فعالیت‌هایی است که آن فرایندها پشتیبانی می‌کنند.

مدل یک نقشه از یک فرآیند یا پدیده است که یک ویژگی مفید را به تصویر می‌کشد. انسان‌ها در ایجاد یک مدل از اشیاء فوق‌العاده هستند. برای مثال اگر کسی توپی را به سمت شما پرتاب کند، تقریباً به صورت ناخودآگاه می‌توانید حرکت آن را پیش‌بینی کنید. زیرا مدلی از حرکت یک جسم در فضا در ذهن شما وجود دارد. اما مدل‌ها همیشه کامل نیستند. برای مثال نمی‌توانید حرکت آن توپ با سرعت نور در فضا را به راحتی مدل کنید. البته به این معنا نیست که مدل ذهنی شما اشتباه است، بلکه برخی از پیش‌بینی‌ها خارج از دامنه شما می‌باشد.

مدل دامنه نقشه ذهنی است که صاحبان مشاغل از کسب و کار خود دارند. همه صاحبان کسب و کار این نقشه‌های ذهنی را دارند - این نقشه‌های ذهنی می‌گوید که انسان‌ها چگونه در مورد فرآیندهای پیچیده فکر می‌کنند.

این کتاب قصد دارد که اتوماتیک کردن فرایندهای یک کسب و کار را به صورت واقعی انجام دهد. پس به عنوان مهندس نرم افزار اگر زمانی از شما خواسته شد که نرم‌افزار یک کسب و کار را بنویسید، قدم اول این است که اصطلاحاتی که در آن کسب و کار استفاده می‌شود را یاد بگیرید. کسب و کاری که می‌خواهیم در نظر بگیریم یک شرکت خرده فروشی مبلمان است، که مبلمان خود را از تولیدکنندگان سراسر جهان تهیه می‌کند و آن را به سراسر اروپا می‌فروشد.

زمانی که یک مبل و یا یک میز سفارش می‌دهید، باید بدانیم که چگونه می‌توان کالاهای شما را از لهستان یا چین و یا ویتنام به اتاق نشیمن شما وارد کرد. به بیان دیگر، ما سیستم‌های جداگانه‌ای داریم که مسئول خرید کالا، فروش آن به مشتری، و حمل آن کالا برای مشتری هستند. همچنین به یک سیستم میانی نیاز داریم تا فرایند تخصیص کالا به سفارش مشتری را هماهنگ کند.

تصور کنید که این کسب و کار تصمیم به یک روش جدید برای تخصیص کالا به مشتری می‌گیرد. تا به حال کسب و کار براساس آنچه که کالا به صورت فیزیکی در انبار موجود بود، زمان عرضه و تحویل آن را مشخص می‌کرد. اگر آن کالا در انبار تمام می‌شد، تا زمانی که آن کالا از سمت کارخانه برسد، به آن برچسب تمام شده می‌دادند.

در اینجا یک نوآوری بکار می‌بریم. اگر سیستمی داشته باشیم که بتواند تمام محموله‌های ما را ردیابی کند، و زمانی که قرار است این محموله‌ها به انبار حمل شوند را داشته باشیم، می‌توانیم این کالاهایی که در حال حمل به انبار هستند را به عنوان موجودی واقعی و بخشی از موجودی انبار در نظر بگیریم، اما با زمان تحویل کمی طولانی‌تر. با این کار به نظر می‌رسد که دیگر لازم نیست به محصولات برچسب تمام شده زده شود، و بیشتر می‌فروشیم، و بیزینس می‌تواند با نگهداری کمتر کالاها در انبار در هزینه‌های خود صرفه‌جویی کند.

پس تخصیص سفارش‌ها دیگر یک موضوع بی‌اهمیت برای کاهش یک مقدار واحد در سیستم انبار نیست. ما به مکانیزم تخصیص پیچیده‌تری نیاز داریم. حال زمان این رسیده است که مدل دامنه را برای این کسب و کار تعریف کنیم.

بررسی زبان دامنه

برای درک مدل دامنه نیاز به زمان، صبوری و تهیه مستندات زیادی از کسب و کار داریم. در ابتدا یک گفتگوی اولیه با کارشناسان کسب و کار انجام می‌دهیم، و بر سر یک واژه‌نامه مشترک و یک سری قوانین که در کسب و کار وجود دارد، به منظور تهیه یک نسخه اولیه از مدل دامنه با هم به توافق می‌رسیم. در این گفتگو تا جایی که امکان دارد، برای توضیح هر قانون، مثال‌های عینی از کارشناسان می‌خواهیم. باید مطمئن باشیم که این قوانین در اصطلاحات تجاری گفته شده بیان می‌شوند. سپس یک سری نام یا شناسه‌ برای اشیای خود انتخاب می‌کنیم تا صحبت در مورد مثال‌ها راحت‌تر باشد. مثال خرده‌فروشی مبلمان را که در بالا بیان کردیم را در نظر بگیرید. به همین منظور با کارشناسان کسب و کار صحبت کردیم. یادداشت‌هایی که در این گفتگو تهیه کردیم به شرح زیر می‌باشد:


یادداشت تهیه شده از گفتگو با کارشناسان کسب و کار:

یک محصول با یک SKU مشخص می‌شود. SKU مخفف عبارت stock-keeping unit (واحد انبارداری) است. مشتریان درخواست سفارش می‌دهند. یک سفارش با یک order reference مشخص می‌شود و که می‌تواند شامل چند order line باشد. هر order line هم دو ویژگی دارد، SKU و تعداد آن. برای مثال:

  • ۱۰ واحد از میز قرمز
  • ۱ واحد از میزنهارخوری

واحد خرید شرکت دسته‌های (batches) کوچکی از محصولات را سفارش می‌دهند. یک دسته محصول شامل یک شناسه منحصربفرد به نام reference، یک SKU و تعداد آن می‌باشد.

حال نیاز داریم که order line ها را به batch ها اختصاص دهیم. وقتی یک order line را به یک batch اختصاص دادیم، محصول را از آن batch خاص به آدرس مشتری ارسال خواهیم کرد. پس وقتی x واحد از یک محصول را به یک batch اختصاص می‌دهیم، موجودی آن x واحد کاهش می‌یابد.

برای مثال:

  • یک batch یا دسته ۲۰ تایی میز داریم، ۲ میز را به یک order line اختصاص می‌دهیم.
  • در دسته ما ۱۸ میز باقیمانده است.

اگر موجودی یک batch کمتر از تعداد گفته شده order line باشد، فرایند اختصاص را نمی‌توانیم انجام دهیم. برای مثال:

  • یک batch شامل یک تشک آبی، و یک order line با ۲ تشک آبی داریم.
  • این line را به این batch نمی‌توانیم اختصاص دهیم.

یک order line یکسان را دو بار نمی‌توانیم به یک batch اختصاص دهیم. برای مثال:

  • یک batch از ۱۰ گلدان آبی داریم، به آن یک order line شامل ۲ گلدان را اختصاص می‌دهیم.
  • اگر order line را دوباره به همان دسته اختصاص دهیم، دسته باید هنوز موجودی ۸ عدد را نشان دهد.

با توجه به اینکه batch ها در حال حمل به سمت انبار باشند و یا در انبار موجود باشند، هر کدام یک ETA دارند. batch های موجود در انبار نسبت به batch های در حال حمل اولویت دارد. اولویت batch های در حال حمل هم با توجه به اینکه ETA کدام یک زودتر است، مشخص می‌شود.


همان طور که در گفتگو با کارشناسان کسب و کار مشاهده کردید، فرایندها را از دید کارشناسان به صورت کامل یادداشت‌برداری کردیم. حال زمان آن است که مدل دامنه خود را طراحی کنیم و برای هر یک از موارد بالا تست unit بنویسیم، تا بتوانیم صحت آنها را در برنامه بررسی کنیم.

تست unit برای مدل‌های دامنه

زمانی که می‌خواهیم یک تست unit بنویسیم، باید به موارد زیر در آن توجه کنیم:

  • نام تست unit نشان‌دهنده رفتاری است که می‌خواهیم از سیستم مشاهده کنیم.
  • نام کلاس‌ها و متغیرهایی که استفاده می‌کنیم، از اصطلاحات کسب و کاری که از مصاحبه با کارشناسان کسب و کار استخراج کرده‌ایم می‌آید.
  • اگر کد تست unit را به یک همکاری که برنامه‌نویس نمی‌باشد نشان دهیم، باید بتواند توصیف ما را از رفتار سیستم تأیید کند.

اولین سناریو این بود که یک batch و یک order line داریم. حال می‌خواهیم order line را به batch اختصاص دهیم، با این کار باید موجودی batch به اندازه تعداد order line کم شود. تست unit آن به صورت زیر است:

https://gist.github.com/68ef6c533af494d9e81b6ee2e4c197ac.git

مدل دامنه را هم به صورت زیر تعریف می‌کنیم:

https://gist.github.com/983162bdedc833d0da05d9ca666a522b.git

در گام اول، مدل دامنه را ساده در نظر گرفته‌ایم. به این صورت که در مدل Batch از فقط یک خصوصیت available_quantity به عنوان موجودی استفاده کرده‌ایم. تابع allocate هم تعداد موجود در order line را از موجودی انبار کم می‌کند.

در گام‌های بعدی با توجه به توضیحات کارشناسان کسب و کار تست‌هایی را بیان می‌کنیم که در آن نمی‌توانستیم order line را به batch اختصاص دهیم.

https://gist.github.com/mohammadali66/e026dc4dd54776d9a104a2f3057d7f3a

با توجه به تست‌های unit که برای فرایندهای شکست نوشتیم، نیاز داریم که مدل Batch را کمی تغییر دهیم.

https://gist.github.com/mohammadali66/b275517b62fe41aa0c413ce251578998

به مدل دامنه توجه کنید. در این مدل، یک batch مجموعه‌ای از اشیای Order line را که به آن اختصاص داده شده است را در مشخصه allocations_ نگه می‌دارد. وقتی عمل allocate را انجام می‌دهیم، در ابتدا بررسی می‌شود که آیا SKU مربوط به order line با batch یکسان است؟ و موجودی کافی است یا نه؟ سپس به مجموعه allocations_ اضافه می‌شود.

اگر به مدل دامنه دقت کنید، می‌بینید که آن را خیلی ساده پیاده‌سازی کرده‌ایم. در واقعیت شاید پارامترها و قوانین دیگری هم دخیل باشند. برای مثال مشتری می‌خواهد سفارش خود را در یک تاریخ خاصی دریافت کند. یا اینکه با توجه به موقعیت مکانی مشتری، ممکن است که از یک انبار خاصی برای تخصیص سفارش استفاده کنیم. پس ممکن است که در یک کسب و کار واقعی پیچیدگی‌های زیاد دیگری داشته باشید که در اینجا از آنها اجتناب کرده‌ایم.

اما برای اینکه بتوانیم مدل دامنه بهتری بنویسیم، پایتون امکانات خوبی در اختیار ما گذاشته است. در ادامه در مورد این امکانات پایتون برای بهبود مدل دامنه خود صحبت می‌کنیم.

تعریف type در پایتون

در پایتون با استفاده از کلاس NewType می‌توانید یک type جدید تعریف کنید.

https://gist.github.com/mohammadali66/11847f8ae2683f4fc55735334d56e39f

تعریف Value Object

یک value object شیٔ است که برای خود مشخصه id ندارد، و با داده‌هایی که در خود نگه می‌دارد شناخته می‌شود. اگر هم یکی از داده‌ها تغییر کند، value object تغییر هویت می‌دهد. برای درک این مطلب، سه مدل تعریف value object را در پایتون بیان می‌کنیم.

https://gist.github.com/mohammadali66/b4350799ea293d638c5f6427f40ba540

این تعاریف همه یکی هستند. شما می‌توانید یک شئ value object را با dataclass یا با NamedTuple و یا با namedtuple تعریف کنید. رفتار جالبی که value object ها دارند این است که از عملیات‌های ریاضی پشتیبانی می‌کنند. برای مثال:

https://gist.github.com/mohammadali66/3f2ba9813b2f7c558747ba2a04a238ce

حال که در مورد value object ها صحبت کردیم، به مدل دامنه خود برمی‌گردیم. در کسب و کار ما یک سفارش یا order از چندین line تشکیل می‌شود. هر line برای خود یک SKU و تعداد دارد. برای مثال یک order را می‌توان به صورت زیر درنظر گرفت:

https://gist.github.com/mohammadali66/81bc55eae3a38de13d6c9c7a95b5bf13

توجه کنید یک order خود یک reference دارد که از آن به عنوان شناسه یکتا استفاده می‌شود، اما line برای خود یک شناسه یکتا ندارد. با توجه سناریو مطرح شده، به این نکته توجه کنید: زمانی که یک مفهوم کسب و کاری داشته باشیم که شامل داده باشد اما از خود یک شناسه هویتی یکتا نداشته باشد، برای نمایش آن از الگوی value object استفاده می‌شود. پس value object یک شئ دامنه است که با استفاده از داده‌هایی که در خود نگه می‌دارد، شناخته می‌شود.

https://gist.github.com/mohammadali66/efadbbad9c6a66deb5b20940e62edf08

مقایسه Value Object با Entity

یک order line در کسب وکار مطرح شده با استفاده از order ID و SKU و تعداد محصول شناخته می‌شود. اگر یکی از این مقادیر را تغییر دهیم، یک line جدید خواهیم داشت. این همان تعریف value object است که می‌گوید، هر شئ که تنها با استفاده از داده‌های خودش هویت بگیرد و هویت طولانی مدت نداشته باشد. اما آیا در مورد batch هم می‌توانیم چنین چیزی بگوییم.

اصطلاح entity توصیف کننده یک شئ دامنه است که هویت طولانی مدت دارد. در بخش‌های قبل کلاس Name را به عنوان یک value object معرفی کردیم. اگر ما نام Harry Percival را تعریف کرده باشیم، سپس یک حرف آن را تغییر دهیم، دیگر یک نام جدید Barry Percival داریم. برای ما هم مشخص است که Harry Percival با Barry Percival متفاوت است.

https://gist.github.com/mohammadali66/95537879772f60c2002dc2c0b746a6de

اما اگر Harry را به عنوان یک شخص در نظر بگیریم، چه اتفاقی رخ می‌دهد. در انسان‌ها، اگر نام، ظاهر آنها و یا حتی اگر جنسیت‌شان تغییر کند، باز هم هویت خودشان را دارند. به خاطر اینکه انسان‌ها برخلاف اسامی هویت ثابتی دارند.

https://gist.github.com/mohammadali66/ffcba1c5675f606a0d6d1f41401fd085

اشیای entity برخلاف value object ها، برابری هویت دارند. می‌توانید یکی از مقادیر آن را تغییر دهید، با این حال هنوز همان هویت خودش را دارد. batch ها در مثال ما از همین جنس هستند. می‌توانیم به یک batch مقادیر line متعددی اختصاص دهیم، و یا تاریخ دریافت آن را تغییر دهیم، اما همچنان هویت یکسانی دارد.

برای این که برابری را در یک entity بررسی کنیم، می‌توانیم از تابع __eq__ استفاده کنیم. با این کار عملگر == را برای آن تعریف کرده‌ایم.

https://gist.github.com/mohammadali66/cd4b24987f403fd65daab12e7749167c

تابع hash به منظور کنترل رفتار اشیا زمانی که در یک مجموعه‌ای یا به عنوان dict keys استفاده می‌شود، مفید هستند.

یک تابع سرویس دامنه

عملیات‌های سرویس دامنه می‌توانند در entity یا value object قرار نداشته باشند. اختصاص یک order line به مجموعه‌ای از batch ها بیشتر شبیه به یک تابع می‌ماند، که در اینجا آن را به صورت یک تابع allocate می‌نویسیم.

https://gist.github.com/mohammadali66/7ee4796dd0c4878e071d656cf4bdfb4b

اگر از استفاده از ()next را دوست ندارید و می‌خواهید بجای آن از ()sorted استفاده کنید، می‌توانید تابع __gt__ مدل دامنه را پیاده‌سازی کنید:

https://gist.github.com/mohammadali66/c92c823d53aebf32188b290a750277dc

به انتهای فصل اول کتاب رسیدیم. امیدوارم برای شما مفید بوده باشد.