1. DTO ilə tanışlıq
DTO (Data Transfer Object, "data ötürülməsi obyekti") — bu termin peşəkar proqramlaşdırmadan gəlir, xüsusilə də distributed tətbiqlər və servislər dünyasından (məsələn, bir servis digərini şəbəkə üzərindən çağıranda). Əslində, bu sadəcə bir obyekt — çox vaxt adi C# class-ı, — yeganə işi: data toplamaq və proqramın layer-ləri və ya tətbiqlər arasında təhlükəsiz ötürülməkdir.
Gəlin təsəvvür edək ki, client və server arasında chat var. İstifadəçi hər dəfə mesaj göndərəndə, client text, vaxt və istifadəçi adı ilə obyekt düzəldir, serverə göndərir, server də qərar verir ki, bu məlumatla nə etsin. Əla olardı ki, bu obyektin içində artıq heç nə olmasın: yalnız data ötürmək üçün lazım olanlar. Bax, bu da DTO-class-dır.
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Vəssalam! Heç bir biznes-məntiqi, heç bir çətin metod — sadəcə property-lər.
Yalnız data ötürmək üçün class-lar
Təsəvvür elə, pizza çatdırılması ilə məşğulsan. Sənin kuryerin (DTO) pizza bişirməyi, sifariş qəbul etməyi və ya velosiped təmir etməyi bacarmalı deyil. Ondan tələb olunan tək şey — qutunu A nöqtəsindən B-yə çatdırmaqdır. DTO-class da belədir: işi sadə datanı saxlamaqdır. O, özünü validate etməyi bacarmalı deyil, UI-də özünü necə göstərməyi bilməməlidir — sadəcə data saxlamalıdır.
- DTO — "peşəsiz adamlar" və ya "məntiqsiz obyektlər" kimidir, sadəcə data daşıyırlar.
- Onlardan istifadə edirlər ki, biznes-məntiq data ötürülməsi detalları ilə çirklənməsin: çamadan nə qədər kiçik olsa, daşımaq bir o qədər asandır.
2. Adi DTO-class-ların praktik problemləri
Elə bilirdin ki, xoşbəxtlik budur: class-ı auto-property-lərlə yazdın, rahat yaşa! Amma hər şey o qədər də asan deyil.
Data-nın mutable olması: nə pis gedə bilər?
public class ProductDto
{
public int Id { get; set; }
public string Name { get; set; }
}
İndi təsəvvür elə ki, hardasa kodda property təsadüfən dəyişdi:
ProductDto dto = new ProductDto { Id = 1, Name = "Süd" };
SomeApiProcess(dto);
dto.Name = "Kefir"; // Ayda!
Bütün property-lər public set olduğu üçün, obyekti təsadüfən korlamaq və ya daha pis, bir yerdə dəyişib, bütün sistemdə "axmağa" səbəb olmaq çox asandır. Bu, obyektlər bir çox modul və thread-lərdə istifadə olunanda əsl problemə çevrilir.
Kopyalama və müqayisə — bitməyən rutin
Tutaq ki, sən ProductDto-nun kopyasını yaratmaq istəyirsən, amma yalnız adını dəyişmək istəyirsən.
var otherDto = new ProductDto { Id = dto.Id, Name = "Kəsmik" };
Əgər property-lər çoxdursa, hər birini əl ilə kopyalamaq darıxdırıcı və potensial olaraq bug-lı prosesə çevrilir.
Bir də obyektləri müqayisə etmək lazımdırsa? Equals və GetHashCode override etməlisən, çox vaxt bunu ya pis, ya da ümumiyyətlə unudurlar.
Immutable-lıq — "sadə" class-lar üçün deyil
Çox vaxt DTO immutable olmalıdır: datanı bir dəfə alırsan — və dəyişmirsən. Amma auto-property-lərlə class bu işə yaramır: istənilən kəs istənilən property-ni dəyişə bilər.
3. Böyük layihədə bu necə görünür?
Gəlin real həyatdan bir nümunəyə baxaq.
Sən — böyük bir online mağazada developer-sən. Sənə order haqqında məlumatı şəbəkə ilə ötürmək lazımdır. Bunun üçün belə bir class yaradırsan:
public class OrderDto
{
public int Id { get; set; }
public string Customer { get; set; }
public double Total { get; set; }
}
Hər şey işləyir, ta ki, həftədə bir neçə yüz min sifariş gələnə qədər. Bir də görürsən, bir dəstə microservice DTO-ları ora-bura ötürür, kimsə unudur ki, Total hesablanmalıdır, əl ilə yazılmamalıdır, kimsə təsadüfən Customer-i göndərdikdən sonra dəyişir... Və artıq elə bug-lar yaranır ki, tapmaq çox çətindir.
Necə yaxşılaşdırmaq olar?
- Yalnız get-property-lərdən istifadə et (heç bir set olmasın).
- Yalnız yaradanda dəyərləri təyin etmək üçün xüsusi constructor yaz.
- Equals və GetHashCode override et...
Başa düşürsən, hara gedir? Sadə "çamadan" olmalı olan şey, birdən-birə anlaşılmaz bir canavara çevrilir.
4. Tipik DTO-class problemlərinə qısa baxış
Kiçik layihələrdə DTO dəyişəndə yaranan bug-ları "əllə tutmaq" olur. Böyükdə isə — bu fəlakətdir. DTO-lar authorization, pul ötürülməsi, sifarişlərin işlənməsi üçün istifadə olunur — hər hansı qeyri-qanuni data dəyişməsi pul itkisinə və ya (lənətə gəlsin!) cinayət işinə çevrilə bilər.
Aşağıda class-larla DTO yazanların demək olar ki, hamısının rastlaşdığı problemlərin "spisok"-u var:
| Problem | Təsvir |
|---|---|
| Mutable-lıq | Data istənilən yerdə asanlıqla dəyişir — bug riski artır (xüsusilə multithread-də) |
| Klonlama | Obyektləri kopyalamaq narahatdır, çox vaxt əl ilə yazılır |
| Müqayisə | Default olaraq obyektlər reference-lə müqayisə olunur, value ilə yox |
| with-copy dəstəyi | Data-nın bir hissəsini dəyişib kopyalamaq istərdik, amma əl ilə kopyalamaq lazımdır |
| Aydın olmayan nested-lik | Nested DTO-ları tam kopyalamaq lazımdır, bu da yorucudur |
5. Nümunə: tətbiqimizdə layer-lər arasında data ötürülməsi
Tutaq ki, bizdə tapşırıqları saxlamaq üçün gündəlik tətbiqi var. Əvvəllər belə bir class yazardıq:
public class TaskDto
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime DueDate { get; set; }
}
TaskDto-nu fayldan oxuyurduq, sonra kolleksiyaya əlavə edirdik, sonra ekrana çıxarırdıq. Hər şey yaxşı idi... ta ki, başqa moduldan kimsə təsadüfən DueDate-i serialization-dan əvvəl dəyişənə qədər — və istifadəçidə qarışıqlıq yaranırdı.
7. Daha yaxşısı mümkündürmü?
Bəli! Həm də yalnız mümkündür yox, mütləq lazımdır! Bu problemləri həll etmək üçün C#-da xüsusi bir konstruktor var — record. O, DTO-class-larla bağlı başağrısının böyük hissəsini demək olar ki, sehrli şəkildə həll edir.
record nə edir:
- Default olaraq value-oriented-dir: obyektlər reference yox, content-lə müqayisə olunur.
- Immutable-lıq: yalnız get-property-lər yaza bilərsən, dəyərlər constructor-da təyin olunur.
- Asan klonlama: with konstruktoru ilə dəyişikliklə kopyalama imkanı var.
- Müqayisə və çıxış üçün metodlar avtomatik generasiya olunur.
public record TaskDto(int Id, string Description, DateTime DueDate);
Süperdir? Həqiqətən! Amma bu barədə — növbəti dərsdə.
GO TO FULL VERSION