در این بخش، مباحث دیگری از tactical design شامل modeling و architecture را بررسی می کنیم.
Architecture
در strategic design این قابلیت را داریم که بتوانیم در BCهای مختلف بسته به نیازمندی که وجود دارد، از معماری و طراحی های متفاوت استفاده کنیم. زمانی که یک BC داریم که در آن پیچیدگی خاصی وجود ندارد و صرفا دارای عملیات های CRUD است، با زمانی که یک BC وجود دارد که دارای اجزای متفاوتی است و ruleهای متفاوت به هم وابسته هستند و همچنین invariantهای مختلفی را چک می کند، می تواند متفاوت باشد.
موضوعی که می خواهیم در مورد آن صحبت کنیم، شامل آن دسته از BCهایی می شود که در آن ها حجم قابل قبولی از پیچیدگی وجود دارد و می خواهیم آن ها را طراحی کنیم. یا به صورت دقیق تر در مورد معماری های Domain-Centric قصد داریم صحبت کنیم.
Domain-Centric Architecture
روشی که در گذشته به صورت سنتی کار می شد و به معماری سه لایه معروف بود، بر این اساس بود که معماری را به سه بخش عمده Presentation Layer, Business Layer, Data Layer تقسیم می کرد.
در این نوع معماری، معمولا از الگویی استفاده می شد که با نام Transaction Script آن را می شناسیم. نگاه transaction script بر این اساس بود که flowهای پروژه به صورت پروسیجر مدل می شدند. به این معنی که به جای استفاده از یک مدل object oriented، محدود به serviceهایی بودیم که در لایه business layer نوشته می شدند. هر service، تعدادی پروسیجر درون خود داشت که یک روال را پشت سرهم انجام می داد. domain modelهایی که می توانستیم در نظر بگیریم، محدود به objectهایی بودند که مسئولیت حمل داده را داشتند و آن ها را با عنوان Anemic Domain Model می شناسیم.
Rich Domain Model
این مفهوم در مورد domainهایی صحبت می کند که در قالب object، هم شامل داده هستند و هم رفتارهای حول محور آن داده ها را encapsulate می کنند. صرفا در آن ها business logic می بینیم و application logic درون آن ها دیده نمی شود. همچنین وابستگی به هیچ فریم ورک و زیر ساختی ندارند، کاملا testable هستند و در آن ها از domain language استفاده می شود.
در طراحی هایی که به شکل domain centric مورد استفاده قرار می گیرد، ویژگی هایی وجود دارد که آن ها را بررسی می کنیم.
چهار abstraction layer تحت عناوین
User Interface, Application Layer, Domain Layer, Infrastructure Layer
در اینجا مطرح می شود.
User Interface
وظیفه لایه ای که به اسم User Interface یا Presentation Layer می شناسیم، نمایش اطلاعات به کاربر و یا تفسیر commandهایی است که توسط کاربر انجام می شود.
Domain Layer
در Domain Layer، مفاهیم و قوانین business و مدلی که حول این محور تحت عنوان domain model طراحی می شود را قرار می دهیم. این لایه را به عنوان قلب سیستم می شناسیم. لایه Domain در معماری، high levelترین module می باشد. به هیچ لایه ای وابستگی ندارد و از مباحث technical فارغ است.
Application Layer
بعد از domain model، به عنوان high levelترین لایه در معماری است. در این لایه use caceهای برنامه مدل می شوند. این لایه شامل application logicهایی است که یک use case را از طریق سپردن یا delegate کردن به domain layer و infrastructure layer، به سرانجام می رساند. بنابراین، application logicها، ماهیت Orchestration دارند. نکته ای که در آن وجود دارد، این است که این لایه ها باید سبک و کوچک باشند. از این جهت که صرفا delegation انجام می دهند. بنابراین اگر پیچیدگی در آن ها دیده شود، نشان دهنده آن است که احتمالا domain logic را اشتباها در آن قرار داده ایم.
Infrastructure Layer
در Infrastructure Layer، با سرویس ها و مباحثی روبرو هستیم که ماهیت زیرساختی دارند. مثل کار با دیتابیس، logger و مفاهیمی از این قبیل.
از انواع logic که در یک سیستم می تواند وجود داشته باشد، Application Logic و Business Logic را عنوان کردیم. فرض کنید که در مثال بخش قبل، یک پیشنهاد بر روی یک Auction ثبت می شود. ابتدا باید auction را از دیتابیس load کنیم. بعد از آن، باز بودن آن را بررسی می کنیم. اگر باز بود بررسی می کنیم که این پیشنهاد از پیشنهاد برنده بیشتر باشد. و یا اگر اولین پیشنهاد است، بررسی می کنیم که از قیمت شروع بیشتر باشد. بعد از آن، object در دیتابیس ذخیره می شود.
در این مساله، با دو دسته منطق روبرو هستیم. منطق اجرای این use case، که جزء منطق business ما نیست و صرفا نشان می دهد این کار به چه ترتیبی و چگونه باید انجام شود. این منطق، application logic محسوب می شود. و یکسری منطق دیگر به نام business logic که مربوط به business هستند و در domain layer قرار می گیرند.
در تصویر زیر یک sequence diagram را مشاهده می کنید.
در بالای تصویر، لایه بندی که مدنظر بوده است، مشخص شده است.
همانطور که ملاحظه می کنید، در User Interface، یک TransferController قرار دارد که برای عملیات انتقال وجه، یک Application Service را فراخوانی می کند. Application Service از طریق FundsTransferService کار Orchestration را انجام می دهد و logic برنامه در Domain Layer اتفاق می افتد.
یکی از موضوعات مهمی که در معماری وجود دارد را در قالب یک مثال بررسی کنیم. در طراحی به سبک قدیم، یک BLL یا business logic layer و یک DAL یا data access layer را در نظر بگیرید.
یک object به نام TaxRepository در DAL وجود دارد که از طریق متد GetCurrentTaxRate، نرخ فعلی مالیات را برمی گرداند. یک کلاس SalaryCaculationService در BLL وجود دارد که حقوق یک فرد را محاسبه می کند.
در زمان پیاده سازی، SalaryCaculationService برای بدست آوردن نرخ فعلی مالیات، کلاس TaxRepository را فراخوانی می کند و از طریق آن محاسبات را انجام می دهد.
پکیج BLL را یک پکیج High Level در نظر می گیریم. از این نظر که به مفاهیم business نزدیک تر است و موضوعات در آن مرتبط با business هستند. به این دلیل برای ما حجم پیچیدکی و اهمیت بیشتری دارد. پکیج DAL یک Low Level است. از این جهت که بیشتر technical است و policyهای business در آن کمتر دیده می شود.
وقتی به جهت dependency نگاه کنیم، می بینیم که ماژول high level به ماژول low level وابسته شده است. بنابراین تغییرات در ماژول low level در ماژول high level تاثیر گذار است.
یک اصل به نام Dependency Inversion Principle وجود دارد و اشاره به این دارد که high levelها نباید به سمت low levelها وابستگی داشته باشند و هر دو باید به abstraction وابسته باشند.
اصل dependency inversion به این شکل عمل می کند که ماژول high level، سطح abstraction را تعریف می کند و از آن استفاده می کند. ماژول low level نیز آن abstraction را پیاده سازی می کند و از این طریق جهت وابستگی تغییر می کند. در نتیجه تغییرات نیز از سمت high level module به سمت low level module حرکت می کند.
در طراحی های مختلف، ممکن است اسامی متفاوتی را شنیده باشیم.
در تصویر بالا، قسمتی که با نام Entities یا Enterprise Business Rules مشخص شده است را با نام domain معرفی کردیم. بر روی آن یک abstraction دیگر به نام Use Cases یا Application Business Rules قرار دارد که آن را با نام application معرفی کردیم. بر روی آن بخش های مختلفی از Controllers, Gateways, Presenters دیده می شود که با نام Interface Adapters معرفی شده است.
در سال های اخیر، ایده های متفاوتی در مورد architecture دیده شده است. مثل Hexagonal Architecture یا Onion Architecture و غیره. ایده کلی این معماری ها بسیار شبیه به هم است. مباحثی که این طراحی ها دنبال می کنند شامل موارد زیر است.
استقلال از فریم ورک ها و زیر ساخت ها یکی از موضوعات مهم است.
تست کردن business ruleها، فارغ از دیتابیس، سرویس های خارجی و غیره.
استقلال از UI، به این معنی که UI می تواند به راحتی تغییر کند بدون اینکه نیاز به تغییر در business ruleها باشد.
از مواردی هستند که در انواع معماری ها به صورت یکسان دیده می شوند.
Stability
مفهوم Stability در اینجا به معنای هزینه تغییر است. هر چیزی که تغییر دادن آن سخت باشد stable است. یکی از المان هایی که در stability تاثیر گذار است، dependency می باشد. در نظر بگیرید که سه پکیج داریم و هر سه آن به یک ماژول dependency دارند. بنابراین در این شرایط تغییر دادن آن ماژول کار سختی است.
برای مثال، یک application layer داریم و سه UI با تکنولوژی های REST, gRPC, SOAP نیز داریم. وابستگی به application در نقش یک high level زیاد است و تغییر دادن آن سخت است. بنابراین یک ماژول stable است.
قانونی به نام Stable Dependency Principle یا SDP وجود دارد که در این مورد توضیح می دهد. بعضی از کامپوننت ها با انتظار تغییرات زیاد طراحی شده اند. بنابراین هر کامپوننت که تغییر دادن آن سخت است، نباید به کامپوننتی که انتظار تغییر زیادی در آن دیده می شود، وابسته باشد. در نتیجه در این مسائل باید از dependency inversion استفاده کنیم.
قانونی به نام Stable Abstraction Principle یا SAP وجود دارد که در این مورد توضیح می دهد. اگر کامپوننت stable وجود دارد، باید abstract شود. به کامپوننتی abstract گفته می شود که عمده اجزایی که داخل آن هستند abstraction باشند.
در این نمودار، اگر کامپوننت ها در قسمت Zone of Pain قرار بگیرند، به این معنی است که این کامپوننت ها stable و concrete هستند. اگر کامپوننت ها در منطقه Zone of Uselessness قرار بگیرند، به این معنی است که این کامپوننت ها instable هستند و وابستگی به آن ها کم است و خیلی تاثیر گذار نیستند.
اگر بخواهیم از لحاظ وابستگی، design خوبی داشته باشیم، عمده کامپوننت ها باید در قسمت سبز رنگ قرار بگیرند.
در مثال application layer که زده شد، راه حل این است که یک پکیج برای مثال با نام Application.Contracts درست کنیم که abstraction در داخل آن قرار می گیرد و dependecyها نیز به آن باشد. پیاده سازی ها نیز در لایه Application قرار می گیرند.