Kami memiliki ASP.NET Web API yang menyediakan REST API untuk Aplikasi Satu Halaman kami. Kami menggunakan DTO / POCO untuk meneruskan data melalui API ini.
Masalahnya sekarang, bahwa DTO ini semakin besar dari waktu ke waktu, jadi sekarang kami ingin memperbaiki DTO.
Saya mencari "praktik terbaik" bagaimana merancang DTO: Saat ini kami memiliki DTO kecil yang hanya terdiri dari bidang tipe nilai, misalnya:
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
DTO lain menggunakan UserDto ini dengan komposisi, misalnya:
public class TaskDto
{
public int Id { get; set; }
public UserDto AssignedTo { get; set; }
}
Juga, ada beberapa DTO yang diperluas yang didefinisikan dengan mewarisi dari yang lain, misalnya:
public class TaskDetailDto : TaskDto
{
// some more fields
}
Karena beberapa DTO telah digunakan untuk beberapa titik akhir / metode (misalnya GET dan PUT), mereka telah diperluas secara bertahap oleh beberapa bidang seiring waktu. Dan karena warisan dan komposisi DTO lainnya juga menjadi lebih besar.
Pertanyaan saya sekarang adalah apakah warisan dan komposisi bukan praktik yang baik? Tetapi ketika kita tidak menggunakannya kembali, rasanya seperti menulis kode yang sama beberapa kali. Apakah praktik yang buruk untuk menggunakan DTO untuk beberapa titik akhir / metode, atau haruskah ada DTO yang berbeda, yang hanya berbeda dalam beberapa nuansa?
Jawaban:
Sebagai praktik terbaik, cobalah dan buat DTO Anda sesingkat mungkin. Kembalikan hanya apa yang Anda butuhkan untuk kembali. Hanya gunakan apa yang perlu Anda gunakan. Jika itu berarti DTO tambahan, biarlah.
Dalam contoh Anda, tugas berisi pengguna. Seseorang mungkin tidak memerlukan objek pengguna penuh di sana, mungkin hanya nama pengguna yang ditugaskan tugas. Kami tidak membutuhkan properti pengguna lainnya.
Katakanlah Anda ingin menetapkan kembali tugas ke pengguna yang berbeda, seseorang mungkin tergoda untuk melewatkan objek pengguna penuh dan objek tugas penuh selama pos penugasan ulang. Tapi, yang benar-benar dibutuhkan hanyalah tugas Id dan ID pengguna. Itu adalah dua informasi yang diperlukan untuk menetapkan kembali tugas, jadi buat model DTO seperti itu. Ini biasanya berarti ada DTO terpisah untuk setiap panggilan istirahat jika Anda berjuang untuk model DTO ramping.
Juga, kadang-kadang seseorang mungkin membutuhkan warisan / komposisi. Katakanlah seseorang memiliki pekerjaan. Suatu pekerjaan memiliki banyak tugas. Dalam hal itu, mendapatkan pekerjaan juga dapat mengembalikan daftar tugas untuk pekerjaan itu. Jadi, tidak ada aturan terhadap komposisi. Itu tergantung pada apa yang dimodelkan.
sumber
Kecuali jika sistem Anda hanya didasarkan pada operasi CRUD, DTO Anda terlalu terperinci. Coba buat titik akhir yang mewujudkan proses bisnis atau artefak. Pendekatan ini memetakan dengan baik ke lapisan logika bisnis, dan ke "desain berbasis domain" Eric Evans.
Misalnya, Anda memiliki titik akhir yang mengembalikan data untuk faktur sehingga dapat ditampilkan di layar atau formulir ke pengguna akhir. Dalam model CRUD, Anda perlu beberapa panggilan ke titik akhir Anda untuk mengumpulkan informasi yang diperlukan: nama, alamat penagihan, alamat pengiriman, item baris. Dalam konteks transaksi bisnis, DTO tunggal dari titik akhir tunggal dapat mengembalikan semua informasi ini sekaligus.
sumber
Sulit untuk menetapkan praktik terbaik untuk sesuatu yang "fleksibel" atau abstrak sebagai DTO. Pada dasarnya, DTO hanya objek untuk transfer data tetapi tergantung pada tujuan atau alasan transfer, Anda mungkin ingin menerapkan "praktik terbaik" yang berbeda.
Saya merekomendasikan membaca Pola Arsitektur Aplikasi Perusahaan oleh Martin Fowler . Ada seluruh bab yang didedikasikan untuk pola, di mana DTO mendapatkan bagian yang sangat rinci.
Awalnya, mereka "dirancang" untuk digunakan dalam panggilan jarak jauh yang mahal, di mana Anda mungkin membutuhkan banyak data dari berbagai bagian logika Anda; DTO akan melakukan transfer data dalam satu panggilan.
Menurut penulis, DTO tidak dimaksudkan untuk digunakan di lingkungan lokal, tetapi beberapa orang menemukan kegunaannya. Biasanya mereka digunakan untuk mengumpulkan informasi dari POCO yang berbeda menjadi satu kesatuan untuk GUI, API, atau lapisan yang berbeda.
Sekarang, dengan pewarisan, penggunaan kembali kode lebih seperti efek samping dari warisan daripada tujuan utamanya; komposisi, di sisi lain, diimplementasikan dengan penggunaan kembali kode sebagai tujuan utama.
Beberapa orang merekomendasikan penggunaan komposisi dan pewarisan bersama, menggunakan kekuatan keduanya dan mencoba mengurangi kelemahan mereka. Berikut ini adalah bagian dari proses mental saya ketika memilih atau membuat DTO baru, atau kelas / objek baru dalam hal ini:
Beberapa dari mereka mungkin bukan praktik "terbaik", mereka bekerja cukup baik untuk proyek yang saya kerjakan tetapi Anda harus ingat bahwa tidak ada ukuran yang cocok untuk semua. Dalam hal DTO universal Anda harus berhati-hati, tanda tangan metode saya terlihat seperti ini:
Jika ada metode yang membutuhkan DTO sendiri, saya melakukan pewarisan dan biasanya satu-satunya perubahan yang perlu saya lakukan adalah parameter, meskipun kadang-kadang saya perlu menggali lebih dalam untuk kasus-kasus tertentu.
Dari komentar Anda, saya mengerti bahwa Anda menggunakan DTO bersarang. Jika DTO bersarang Anda hanya terdiri dari daftar DTO lain, saya pikir hal terbaik untuk dilakukan adalah membuka daftar.
Bergantung pada jumlah data yang perlu Anda tampilkan atau kerjakan, mungkin merupakan ide bagus untuk membuat DTO baru yang membatasi data; misalnya, jika UserDTO Anda memiliki banyak bidang dan Anda hanya perlu 1 atau 2, mungkin lebih baik memiliki DTO dengan hanya bidang tersebut. Mendefinisikan layer, konteks, penggunaan dan utilitas DTO akan banyak membantu ketika mendesainnya.
sumber
Menggunakan komposisi dalam DTO adalah praktik yang sangat bagus.
Warisan di antara jenis DTO konkret adalah praktik yang buruk.
Untuk satu hal, dalam bahasa seperti C #, properti yang diimplementasikan otomatis memiliki overhead pemeliharaan yang sangat kecil sehingga menduplikasi mereka (saya benar-benar benci duplikasi) tidak berbahaya seperti seringkali.
Salah satu alasan untuk tidak menggunakan warisan konkret di antara DTO adalah bahwa alat tertentu akan dengan senang hati memetakannya ke jenis yang salah.
Misalnya, jika Anda menggunakan utilitas basis data seperti Dapper (saya tidak merekomendasikannya tetapi ini populer) yang melakukan
class name -> table name
inferensi, Anda bisa dengan mudah menyimpan tipe turunan sebagai tipe basis beton di suatu tempat dalam hierarki dan dengan demikian kehilangan data atau lebih buruk .Alasan yang lebih dalam untuk tidak menggunakan warisan di antara DTO adalah bahwa itu tidak boleh digunakan untuk berbagi implementasi antara tipe yang tidak memiliki hubungan "is a" yang jelas. Menurut saya,
TaskDetail
tidak terdengar seperti subtipe dariTask
. Itu bisa dengan mudah menjadi properti dariTask
atau, lebih buruk lagi, itu bisa menjadi tipe atasTask
.Sekarang, satu hal yang Anda khawatirkan adalah menjaga konsistensi antara nama dan jenis properti dari berbagai DTO terkait.
Sementara pewarisan tipe konkret akan, sebagai konsekuensinya, membantu memastikan konsistensi seperti itu, jauh lebih baik menggunakan antarmuka (atau kelas dasar virtual murni dalam C ++) untuk mempertahankan konsistensi semacam itu.
Pertimbangkan DTO berikut ini
sumber