فرشید عزیزی
فرشید عزیزی
خواندن ۷ دقیقه·۲ سال پیش

اشیاء انتقال داده Data Transfer Object یا DTO

تعریف DTO ها (Data Transfer Objects)

تصور کنید یک Domain Model به زیبایی طراحی شده‌ است که از Repository ها برای مدیریت دریافت موجودیت‌های Domain از پایگاه داده شما با یک ORM استفاده می‌کند، به عنوان مثال. Entity Framework، در MVC یا یک کنترلر Web API.

مشکل این است که لایه Presentation به اشیایی با شکلی متفاوت از مجموعه های لایه دامنه شما نیاز دارد.منظور من از شکل های مختلف این است که این لایه ممکن است به داده های ترکیب شده از چندین Aggregate یا فقط بخش هایی از یک Aggregate نیاز داشته باشد.

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

معماری شما به طور موثر لایه بندی شده است، اما شما به دنبال مکان مناسبی برای تبدیل Aggregates به DTO (اشیاء انتقال داده) می‌گردید.

مسئولیت یک Repository در تداوم وضعیت Aggregates است، نه به اشتراک گذاشتن وضعیت Aggregates با Presentation Layer. این در شرح وظایف Repository نیست. در شرح وظایف DTO این است که حامل حالت Aggregate به لایه Presentation باشد. با این حال، DTO باید در جایی مونتاژ شود…

مونتاژ کننده DTO اگر در Repository نیست، پس کجا Aggregate(های) خود را به اشیاء مناسب برای لایه Presentation خود شکل می دهید؟ یک اسمبلر اختصاصی DTO مسئولیت نگاشت (مانند Mapper) ویژگی ها را از Aggregate(ها) به DTO دارد.

یک DTO Assembler می تواند در یک Application Service که کلاینت Repository شما است زندگی کند. Application Service "از Repository برای خواندن نمونه های Aggregate لازم استفاده می کند و سپس به یک DTO Assembler واگذار می کند تا ویژگی های DTO را ترسیم کند."


The DTO Assembler:

public class PartAssembler { public PartDTO WriteDto(Part part) { var partDto = new PartDTO(); partDto.PartNumber = part.PartNumber; partDto.CreatedBy = part.CreatedBy; partDto.CreatedOn = part.CreatedOn; partDto.UnitOfMeasure = part.UnitOfMeasure; partDto.ExtendedDescription = part.ExtendedDescription; partDto.PartDescription = part.PartDescription; partDto.SalesCode = part.SalesCode; var componentsList = part.Components .Select(component => component.ComponentNumber) .ToList(); var laborSequenceList = part.LaborSequences .Select(labor => labor.LaborSequenceNumber) .ToList(); partDto.ComponentList = componentsList; partDto.LaborSequenceList = laborSequenceList; return partDto; } }

The Application Service:

public class PartCatalogService { private readonly IRepository<Part> _partRepository; private readonly PartAssembler _partAssembler; public PartCatalogService(IRepository<Part> partRepository, PartAssembler partAssembler) { _partRepository = partRepository; _partAssembler = partAssembler; } public PartDTO GetPart(string partNumber) { var part = _partRepository.Get(x => x.PartNumber == partNumber); return _partAssembler.WriteDto(part); } }

سورس کامل مثال فوق


جمع بندی :

اشیاء انتقال داده (DTO) برای انتقال داده ها بین Application Layer و Presentation Layer یا سایر انواع کلاینت ها استفاده می شود.

به طور معمول، یک application service از لایه Presentation (اختیاری) با پارامتر DTO فراخوانی می شود. از اشیاء دامنه برای انجام برخی busines logicها خاص استفاده می کند و (به صورت اختیاری) یک DTO را به لایه Presentation برمی گرداند. بنابراین، لایه Presentation به طور کامل از لایه Domain جدا می شود.

در ابتدا، ایجاد یک کلاس DTO برای هر روش application service می تواند کاری خسته کننده و وقت گیر در نظر گرفته شود. با این حال، اگر به درستی از آنها استفاده کنید، می توانند به شما کمک کند. چرا و چگونه؟


  • ایجاد یک سطح انتزاع از لایه دامنه(Abstraction of the Domain Layer)
    خوب DTO ها روشی کارآمد برای انتزاع اشیاء دامنه از لایه Presentation ارائه می کنند. در واقع، لایه های شما به درستی از هم جدا شده اند. اگر می خواهید لایه Presentation را به طور کامل تغییر دهید، می توانید با لایه های برنامه و دامنه موجود ادامه دهید. از طرف دیگر، می توانید لایه دامنه خود را دوباره بنویسید، طرح پایگاه داده، موجودیت ها و چارچوب ORM را کاملاً تغییر دهید، همه اینها بدون تغییر لایه Presentation . البته این تا زمانی است که contract /قراردادها (method signatures and DTOs) در application service شما بدون تغییر باقی بماند.
  • پنهان کردن داده ها(Data Hiding)
    فرض کنید یک User entity با ویژگی های Id، Name, EmailAddress و Password دارید. اگر یک متد GetAllUsers() از یک UserAppService یک List<User>, e برگرداند، هر کسی می تواند به رمز عبور همه کاربران شما دسترسی داشته باشد، حتی اگر آن را روی صفحه نمایش نشان ندهید. این فقط در مورد امنیت نیست، بلکه در مورد پنهان کردن داده ها است. application service باید فقط آنچه را که لایه Presentation (یا client) نیاز دارد، برگرداند. نه بیشتر نه کمتر
  • مشکل Serialization & Lazy Load
    هنگامی که داده ها (یک شی) را به لایه Presentation برمی گردانید، به احتمال زیاد serialized می شود. به عنوان مثال، در یک REST API که JSON را برمی گرداند، شی شما به JSON سریال می شود و برای client ارسال می شود. بازگرداندن یک موجودیت به لایه Presentation می تواند از این نظر مشکل ساز باشد، به خصوص اگر از یک پایگاه داده رابطه ای و یک ارائه دهنده ORM مانند Entity Framework Core استفاده می کنید. چگونه؟
    در یک برنامه واقعی، موجودیت های شما ممکن است به یکدیگر ارجاع داشته باشند. موجودیت کاربر
    می تواند به Role های خود اشاره داشته باشد. اگر می‌خواهید کاربر را serialized کنید، نقش‌های آن نیز سریالی هستند. کلاس Role ممکن است یک List<Permission> داشته باشد و کلاس Permission می تواند مرجعی به یک کلاس PermissionGroup و غیره داشته باشد... تصور کنید همه این اشیاء در یک زمان serialized شوند. شما به راحتی و به طور تصادفی می توانید کل پایگاه داده خود را serialized کنید! همچنین، اگر اشیاء شما دارای ارجاعات circular هستند، ممکن است اصلا serialized نباشند.

اما راه حل چیست؟ مشخص کردن propertyها به‌عنوان NonSerialized؟

نه، شما نمی توانید بدانید که چه زمانی باید serialized شود و چه زمانی نباید. ممکن است در یک متد application servic مورد نیاز باشد و در متد دیگر مورد نیاز نباشد. بازگرداندن DTO های ایمن، قابل سریال سازی و طراحی خاص انتخاب خوبی در این شرایط است.

بارگذاری تنبل یا Lazy Load به این معنی است که داده های مرتبط از پایگاه داده زمانی که به navigation property دسترسی پیدا می شود بارگیری می شود.
تقریباً تمام فریمورک های ORM از Lazy Load پشتیبانی می‌کنند. این یک ویژگی است که اگر روابط بین موجودیت‌ها وجود داشته باشد آنها را از پایگاه داده بارگیری می کند. فرض کنید یک کلاس User به یک کلاس Role اشاره دارد. هنگامی که User را از پایگاه داده دریافت می کنید، ویژگی Role (یا collection) بارگذاری نمی شود و از این بابت اگر از آن استفاده ای هم نداشته باشید بی جهت اطلاعات اضافی ای را بارگذاری نکردید اما هنگامی که برای اولین بار ویژگی Role(navigation property) را می خوانید در اینجا ORM در پس زمینه دیتاهای مرتبط را بارگذاری می کند اماشما متوجه این فرآیند نمی شوید! بنابراین، اگر چنین Entity را به لایه Presentation برگردانید، باعث می شود که با اجرای پرس و جوهای اضافی، موجودیت های اضافی را از پایگاه داده بازیابی کند. اگر یک ابزار سریال‌سازی موجودیت را بخواند، همه ویژگی‌ها را به صورت بازگشتی می‌خواند و دوباره کل پایگاه داده شما می‌تواند بازیابی شود.
اگر از Entities در لایه Presentation استفاده کنید، ممکن است مشکلات بیشتری ایجاد شود. بهتر است در لایه Presentation به مجموعه لایه های domain اشاره نکنید.



بیشتر بخوانید : پیاده سازی Application Services در DDD

بیشتر بخوانید : Implementing DDD - Clean Architecture

بیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core

https://zarinp.al/farshidazizi

dtodata transfer objectasp net coreapplication layerddd
Software Engineer
شاید از این پست‌ها خوشتان بیاید