این مجموعه با نگاهی به ویدیوی آموزشی Best Practice در Pluralsight که توسط Steve Smith تهیه شده، گردآوری شده است. این مجموعه مقالات به شکل ویدیویی هم در کانال تلگرامم قرار میگیره. خوشحال میشم برای با خبر شدن از مقالات و نوشته ها و ویدیوهای آموزشی در این کانال حضور داشته باشید و نظراتتون رو بنویسید. https://t.me/mediapub_channel
فهرست مقالات مجموعه معرفی Best Practice های دات نت :
مدل های موجود در Web Api معمولا DTO هستند. DTO ها آبجکت های بدون Behavior هستند که فقط state را مشخص میکنند، یعنی فقط Property دارند. برخی DTO را با POCO اشتباه میگیرند.(Plain Old CLR C# Object )
همه DTO ها POCO هستند(DTO ها از System.Object به طور پیش فرض ارث بری میکنند) ولی همه POCO ها DTO نیستند چون میتوانند Behavior داشته باشند یعنی حاوی متد باشند.
در این مقاله منظور از مدل همه مدلهایی که استفاده میکنیم نیست بلکه مدلهاییست که در ورودی یا خروجی api استفاده میشوند و کلاینت به معنی برنامه نویس Front-End با آنها تعامل دارند.
چند ویژگی مهم و پایه مدل ها :
- تمرکز مدل ها روی انتقال اطلاعات است.
- مدل ها یا کلاس هستند یا record.
- مدل ها با اینکه حاوی متد نیستند اما میتوانند ولیدیشن را به روش Data Annotation هندل کنند. این یک خاصیت درونی در دات نت است و به کمک اتریبیوت ApiController امکان کنترل ولیدیشن را به ما میدهد.
- نامگذاری DTO ها بر پایه Entity ها نیست. مثلا CustomerDTO یا AuthorDTO نامگذاری مناسب نیستند. نامگداری مناسب متاثر از کارکرد DTO است، مثلا CreateAuthorRequest یک نامگذاری مناسب است برای یک کارکرد خاص و در جای دیگر DTO دیگر با کارکرد دیگر لازم است.
به مثال فوق نگاه کنید:
اولا نامگذاری مناسب استفاده شده و صرفا به AuthorDTO بسنده نشده چون کاملا کارکرد مبهمی دارد.
ثانیا همه پراپرتی ها به شکل پابلیک هستند.
ثالثا چون هدف ایجاد یک ریسورس است و کلمه Command داریم از شناسه یا Id در پراپرتی ها استفاده نشده است چون در سمت سرور تولید میشود.
رابعا با اینکه این کلاس یک DTO است و قرار نیست Behavior داشته باشد اما استفاده از Annotation ایرادی ندارد. ضروری بودن یک پراپرتی یا محدودیت در تعداد کاراکتر ها از این روش امکانپذیر است.
خامسا از ثابت ها برای ارقام مربوط به MaxLength استفاده کردیم که قابلیت تغییر در یکجا و تاثیر در همه جا را ایجاد کرده باشیم.
میتوانیم از ویژگی جدید سی شارپ یعنی record برای ایجاد DTO استفاده کنیم.
ظاهر باید تعداد خطوط کد در روش record کمتر شود ولی چون Data Annotation داریم چندان فرقی با حالت قبل ندارد. طبیعتا برای DTO هایی که در خروجی یا بین لایه ها استفاده میشوند این روش ساده تر است.
در آنچه ارسال میکنی سخت گیر و در آنچه دریافت میکنی آسانگیر باش.
واضح است وقتی برنامه شما دیتایی به برنامه دیگری ارسال میکند باید دقیقا بر اساس آنچه آن برنامه میخواهد و به شکل اینترفیس در اختیار ما قرار داده دیتا را ارسال کرد. اما وقتی برنامه های دیگر، برنامه شما را صدا میزنند باید بتوانید دیتای ریکوئست هایی که کاملا دقیق و بر مبنای مدل ورودی نیستند نیز دریافت کنید.
وقتی Api شما کاربران زیادی دارد مهم نیست که برای api چه قراردادی در دریافت یا پاسخ قرار داده اید، آنچه مهم است رفتار عینی کاربران و نحوه تعامل آنها با api است. سیستم شما بستگی به نحوه استفاده دیگران دارد.
مثال : فرض کنید api برای توئیتر را نوشته اید و میخواهید alias برای کاربر دریافت کنید. همچنین api شما برای مدتی نیز استفاده شده است و فرمت های مختلفی برای نمایش alias وجود دارد.
طبیعتا این روش کلاینت هایی را که انتظار ندارند همیشه از @ استفاده کنند با خطا مواجه خواهد کرد. پس استفاده از Regular expression به این شکل کار صحیحی نیست. به علاوه اینکه معمولا خطای صادر شده توسط Regular expression مبهم است.
پس دو مشکل داریم یکی عدم انعطاف پذیری در قبول alias و دیگری پیام خطای نامناسب برای کاربر.
راه حل برای رعایت قانون Postel این است که تمامی فرمت های alias را قبول کنیم ولی در کد به یک مدل واحد تبدیل کنیم. در این حالت api همیشه درست کار میکند و کلاینت ها با خطا روبرو نمیشوند.
دیتایی که در اختیار کلاینت قرار میگیرد نباید خیلی بزرگ و نباید خیلی کوچک باشد. بزرگ بودن خروجی باعث مصرف کردن دیتای اضافی در شبکه و کند شدن میشود. دیتای کم نیز ممکن است کلاینت را مجبور به فراخوانی چندین api کند تا بتواند مجموعه دیتای مورد نیاز خود را بدست آورد.
اما چطور بفهمیم کلاینت برای فراهم کردن دیتای مورد نیاز یک صفحه به چندین api نیاز دارد؟
با در نظر گرفتن caching و data storage میتوان گفت آنچه کلاینت نیاز دارد شاید از پیش در اختیار داشته باشد. در مرحله بعد اگر هیچ دیتایی نداشته باشد پاسخ این سوال 1 است.
اگر فقط یک کلاینت داشته باشیم
اگر تنها یک Client داشته باشید بهتر است api خود را بر اساس نیاز این تک کلاینت طراحی کنید. یعنی از الگوری BFF استفاده کنید (Backend For Frontend) . در این روش مبنای طراحی بر اساس نیازهای کلاینت است. هر صفحه در بخش کلاینت مجموعه ای از دیتا نیاز دارد که ممکن است توسط بخش های مختلف از Backend قابل تامین باشد.
متدهایی که کلاینت از آنها استفاده نمیکند را expose نکنید.
تا جایی که ممکن است عملیات Data Aggregation را در سمت سرور انجام دهید که کلاینت دقیقا دیتایی را دریافت کند که به آن نیاز دارد.
اگر کلاینت های زیادی داشته باشیم(یا public api باشد)
برای هر ریسورس خود حالت summary , detailed داشته باشید. حالت summary را در api هایی که list ارائه میدهند استفاده کنید. حالت detailed را به شکل api های مستقل طراحی کنید.
ریسورس هایی که به شکل کالکشن قرار است در لیست به کلاینت ارائه شوند قابلیت sort و filter داشته باشند و یک فرمت استاندارد داشته باشند.
برای حالت های کالکشن هایی که sub-collection دارد تصمیم بگیرید که به چه شکل نمایش دهید. آیا به شکل لیست کامل نمایش داده شوند یا برای آنها نیز URI جایگزین کنیم؟
بصورت پیش فرض تنها 30 رکورد هر بار در لیست وجود دارد.
همانطور که میبینید اطلاعات مربوط به pagination در خروجی وجود ندارد. این اطلاعات در هدر خرجی است.
در برخی api ها اطلاعات مربوط به pagination در بدنه خروجی وجود دارد.
ملاحظاتی برای طراحی api زمانی که pagination دارند باید لحاظ شود:
استفاده از نام نا مناسب و غیر استاندارد و غیر قابل درک برای Client.
مشکل بعدی شباهت مدل ها به جداول دیتابیس است که در تیم هایی که حول محور داده برنامه نویسی میکنند بیشتر اتفاق می افتد. حواستان باشد که کلاینت قرار نیست نسبت به نحوه ذخیره سازی ریسورسها مطلع باشد!
مشکل بعدی تغییر مدلها حین توسعه یا در آینده بدون استفاده از سازوکار ورژنینگ است.
مشکل بعدی عدم قابلیت برای کنترل سایز خروجی مدل های api است. مثلا جوری که بنویسید کلاینت نتواند روی تعداد رکوردهای خروجی یک لیست اختیار داشته باشد!
مشکل بعدی عدم تهیه document برای client است. مثلا عدم استفاده از swagger میتواند کلاینت را دچار مشکل کند. داکیومنت باید واضح، کامل و به روز باشد.
میخواهیم یک مدل بد برای خروجی را که وردپرس استفاده میکند بررسی کنیم.
نام ID به شکل حروف بزرگ است. جیسون معمولا به شکل camel case است. یعنی با حروف کوچک شروع میشود. پراپرتی های دیگر هم با اینکه حروف کوچک هستند ولی camel case نیستند. همه اسم ها انگار پیشوند دارند به جز Id که هماهنگی وجود ندارد.
هیچ نوع ریلیشنی برای post_author تعریف نشده و صرفا به عدد بسنده شده. همچنین برای comment هم ریلیشنی تعریف نشده.
مدل فوق اصلاح شده ی مدل قبلی است. نامگذاری camel case شده. آدرس برای author و comment به منظور نمایش ریلیشن تعریف شده است.(استاندارد لینک به ریسورس)
مدل api به منظور درک بهتر کلاینت برای کار با api تهیه میشود و نباید دیدگاه دیتابیسی یا دیدگاه معماری نرم افزاری در آن دخیل باشد.
جمع بندی :
در این مقاله مبانی استفاده از مدل های api بررسی شد. به قانون Postel پرداختیم. در مورد اینکه web api چه مدل دیتایی باید برگرداند پرداختیم. مشخصه های api خوب بررسی شد و آنتی پترن های یک api model را دیدیم.