Anggota: Gunakan ID unik vs objek domain

10

Setelah beberapa jawaban yang berguna tentang apakah saya harus menggunakan objek domain atau id unik sebagai parameter metode / fungsi di sini. Identifier vs objek domain sebagai parameter metode , saya memiliki pertanyaan serupa: anggota (diskusi pertanyaan sebelumnya tidak berhasil tutup ini). Apa pro dan kontra menggunakan ID unik sebagai anggota vs objek sebagai anggota. Saya meminta referensi bahasa yang sangat diketik, seperti Scala / C # / Java. Haruskah saya (1)

User( id: Int, CurrentlyReadingBooksId: List[Int])
Book( id: Int, LoanedToId: Int )

atau (2), lebih disukai daripada (1) Setelah melalui: Haruskah kita mendefinisikan tipe untuk semuanya?

User( id: UserId, CurrentlyReadingBooksId: List[ BookId] )
Book( id: BookId, LoanedToId: UserId )

atau (3)

User( id: Int, CurrentlyReadingBooks: List[Book]) 
Book( id: Int, LoanedTo: User)

Meskipun saya tidak dapat memikirkan manfaat memiliki objek (3), satu manfaat memiliki ID (2) & (1) adalah ketika saya membuat objek Pengguna dari DB, saya tidak harus membuat objek Buku, yang pada gilirannya mungkin bergantung pada objek Pengguna itu sendiri, menciptakan rantai tanpa akhir. Apakah ada solusi umum untuk masalah ini untuk RDBMS dan No-SQL (jika mereka berbeda)?

Berdasarkan beberapa jawaban sejauh ini, ulangi pertanyaan saya: (dengan menggunakan ID yang seharusnya dalam tipe terbungkus) 1) Selalu menggunakan ID? 2) Selalu menggunakan Objek? 3) Gunakan ID ketika ada risiko rekursi dalam serialisasi dan deserialisasi, tetapi menggunakan objek sebaliknya? 4) Ada lagi?

EDIT: Jika Anda menjawab bahwa Objek harus selalu digunakan atau untuk beberapa kasus, pastikan untuk menjawab kekhawatiran terbesar yang telah diposting penjawab lain => Cara mendapatkan data dari DB

0fnt
sumber
1
Terima kasih atas pertanyaan yang bagus, nantikan untuk mengikuti ini dengan penuh minat. Sedikit memalukan bahwa nama pengguna Anda adalah "user18151", orang-orang dengan nama pengguna semacam ini diabaikan oleh beberapa :)
bjfletcher
@ bjfletcher Terima kasih. Saya memang memiliki persepsi yang mengganggu itu sendiri, tetapi tidak pernah terpikir oleh saya mengapa!
0fnt

Jawaban:

7

Objek Domain sebagai id membuat beberapa masalah kompleks / halus:

Serialisasi / Deserialisasi

Jika Anda menyimpan objek sebagai kunci, itu akan membuat serialisasi objek grafik menjadi sangat rumit. Anda akan mendapatkan stackoverflowkesalahan saat melakukan serialisasi naif ke JSON atau XML karena rekursi. Anda kemudian harus menulis serializer khusus yang mengubah objek aktual untuk menggunakan id mereka alih-alih membuat serialisasi objek contoh dan membuat rekursi.

Lewati objek untuk keamanan tipe tetapi hanya menyimpan id, maka Anda dapat memiliki metode pengakses yang malas memuat entitas terkait ketika dipanggil. Caching tingkat kedua akan menangani panggilan berikutnya.

Kebocoran referensi halus:

Jika Anda menggunakan objek domain dalam konstruktor seperti yang Anda miliki di sana, Anda akan membuat referensi melingkar yang akan sangat sulit untuk memungkinkan memori direklamasi untuk objek yang tidak digunakan secara aktif.

Situasi yang ideal:

Id buram vs int / panjang:

Seorang idharus merupakan pengidentifikasi yang sepenuhnya buram yang tidak membawa informasi tentang apa yang diidentifikasi. Tetapi harus menawarkan beberapa verifikasi bahwa itu adalah pengidentifikasi yang valid dalam sistemnya.

Jenis mentah mematahkan ini:

int, longdan Stringmerupakan jenis mentah yang paling umum digunakan untuk pengidentifikasi dalam sistem RDBMS. Ada sejarah panjang alasan praktis yang berasal dari dekade dan mereka semua adalah kompromi yang cocok dengan tabungan spaceatau tabungan timeatau keduanya.

Id berurutan adalah pelanggar terburuk:

Saat Anda menggunakan id berurutan, Anda mengemas informasi semantik temporal ke dalam id secara default. Yang tidak buruk sampai digunakan. Ketika orang mulai menulis logika bisnis yang menyortir atau memfilter pada kualitas semantik id, maka mereka membuat dunia kesakitan bagi para pengelola masa depan.

String bidang yang bermasalah karena desainer naif akan mengemas informasi ke dalam konten, biasanya semantik temporal juga.

Ini membuat tidak mungkin untuk menciptakan sebuah sistem data terdistribusi juga, karena 12437379123ini tidak unik global. Peluang bahwa node lain dalam sistem terdistribusi akan membuat catatan dengan nomor yang sama dijamin cukup banyak ketika Anda mendapatkan cukup data dalam suatu sistem.

Kemudian retas mulai bekerja di sekitarnya dan semuanya berubah menjadi tumpukan kekacauan mengepul.

Mengabaikan sistem terdistribusi besar ( cluster ) itu menjadi mimpi buruk lengkap ketika Anda mulai mencoba untuk berbagi data dengan sistem lain juga. Terutama ketika sistem lain tidak di bawah kendali Anda.

Anda berakhir dengan masalah yang sama persis, bagaimana membuat id Anda unik secara global.

UUID dibuat dan distandarisasi karena suatu alasan:

UUIDdapat mengalami semua masalah yang tercantum di atas tergantung pada yang VersionAnda gunakan.

Version 1menggunakan alamat MAC dan waktu untuk membuat id unik. Ini buruk karena membawa informasi semantik tentang lokasi dan waktu. Itu sendiri bukan masalah, itu adalah ketika pengembang naif mulai mengandalkan informasi itu untuk logika bisnis. Ini juga membocorkan informasi yang dapat dieksploitasi dalam setiap upaya intrusi.

Version 2menggunakan pengguna UIDatau GIDdan domian UIDatau GUIsebagai pengganti waktu dari Version 1ini sama buruknya dengan Version 1kebocoran data dan mempertaruhkan informasi ini untuk digunakan dalam logika bisnis.

Version 3serupa tetapi menggantikan alamat MAC dan waktu dengan MD5hash dari beberapa array byte[]dari sesuatu yang pasti memiliki makna semantik. Tidak ada kebocoran data yang perlu dikhawatirkan, byte[]tidak dapat dipulihkan dari UUID. Ini memberi Anda cara yang baik untuk secara deterministik membuat UUIDbentuk instance dan kunci eksternal semacam itu.

Version 4 didasarkan hanya pada angka acak yang merupakan solusi yang baik, sama sekali tidak membawa informasi semantik, tetapi tidak secara deterministik dapat diciptakan kembali.

Version 5hanya suka Version 4tetapi menggunakan sha1bukan md5.

Kunci Domain dan Kunci Data Transaksional

Preferensi saya untuk id objek domain, adalah menggunakan Version 5atau Version 3jika dibatasi menggunakan Version 5karena beberapa alasan teknis.

Version 3 sangat bagus untuk data transaksi yang mungkin tersebar di banyak mesin.

Kecuali jika Anda dibatasi oleh ruang, gunakan UUID:

Mereka dijamin unik, membuang data dari satu database dan memuat ulang ke yang lain Anda tidak perlu khawatir tentang duplikat id yang sebenarnya referensi data domain yang berbeda.

Version 3,4,5 benar-benar buram dan begitulah seharusnya.

Anda dapat memiliki satu kolom sebagai kunci utama dengan UUIDdan kemudian Anda dapat memiliki indeks unik gabungan untuk apa yang seharusnya menjadi kunci primer komposit alami.

Penyimpanan juga tidak harus CHAR(36). Anda dapat menyimpan UUIDdalam byte asli / bit / angka bidang untuk database yang diberikan selama masih dapat diindeks.

Warisan

Jika Anda memiliki tipe mentah dan tidak bisa mengubahnya, Anda masih bisa mengabstraksikannya dalam kode Anda.

Menggunakan salah satu Version 3/5dari UUIDAnda dapat lulus dalam Class.getName()+ String.valueOf(int)sebagai byte[]dan memiliki kunci referensi buram yang dapat dipulihkan dan ditentukan.


sumber
Saya sangat menyesal jika saya tidak jelas dalam pertanyaan saya, dan saya merasa lebih buruk (atau benar-benar baik) karena ini adalah jawaban yang sangat bagus dan dipikirkan dengan baik dan Anda jelas menghabiskan waktu untuk itu. Sayangnya itu tidak sesuai dengan pertanyaan saya, mungkin itu layak pertanyaan sendiri? "Apa yang harus saya ingat ketika membuat bidang id untuk objek domain saya"?
0fnt
Saya menambahkan penjelasan eksplisit.
Mengerti sekarang Terima kasih telah meluangkan waktu untuk jawabannya.
0fnt
1
Btw, pengumpul sampah generasi AFAIK (yang saya percaya adalah sistem GC yang dominan saat ini) seharusnya tidak memiliki terlalu banyak kesulitan dalam referensi melingkar GC'ing.
0fnt
1
jika C-> A -> B -> Adan Bdimasukkan ke dalam masa Collectionitu Adan semua anak-anaknya masih dapat dijangkau, hal-hal ini tidak sepenuhnya jelas dan dapat menyebabkan kebocoran halus . GCadalah yang paling sedikit dari masalah, serialisasi dan deserialisasi grafik adalah mimpi buruk kompleksitas.
2

Ya, ada manfaatnya, dan ada juga kompromi.

List<int>:

  • Hemat memori
  • Inisialisasi tipe yang lebih cepat User
  • Jika data Anda berasal dari database relasional (SQL), Anda tidak perlu mengakses dua tabel untuk mendapatkan pengguna, cukup Userstabel

List<Book>:

  • Mengakses buku lebih cepat dari pengguna, buku telah dimuat ke dalam memori. Ini bagus jika Anda mampu memulai lebih lama untuk mendapatkan operasi berikutnya yang lebih cepat.
  • Jika data Anda berasal dari database toko dokumen seperti HBase atau Cassandra maka nilai buku yang dibaca kemungkinan ada di catatan Pengguna, sehingga Anda bisa dengan mudah mendapatkan buku "saat Anda di sana mendapatkan pengguna".

Jika Anda tidak memiliki masalah memori atau CPU yang akan saya ikuti List<Book>, kode yang menggunakan Userinstance akan lebih bersih.

Kompromi:

Saat menggunakan Linq2SQL, kode yang dihasilkan untuk entitas Pengguna akan memiliki EntitySet<Book>yang dimuat malas ketika Anda mengaksesnya. Ini harus menjaga kode Anda tetap bersih dan instance Pengguna kecil (jejak memori bijaksana).

ytoledano
sumber
Dengan asumsi semacam caching, manfaat preloading akan menjadi nol. Saya belum pernah menggunakan Cassandra / HBase jadi tidak bisa berbicara tentang mereka tetapi Linq2SQL adalah kasus yang sangat spesifik (walaupun saya tidak melihat bagaimana lazy loading akan mencegah kasus chaining tak terbatas bahkan dalam kasus khusus ini, dan dalam kasus umum)
0fnt
Dalam contoh Linq2SQL Anda benar-benar tidak mendapatkan manfaat kinerja, hanya kode yang lebih bersih. Saat mendapatkan entitas satu-ke-banyak dari toko dokumen seperti Cassandra / HBase, sebagian besar waktu pemrosesan dihabiskan untuk menemukan catatan, jadi Anda mungkin juga mendapatkan semua entitas saat Anda berada di sana (buku, di contoh ini).
ytoledano
Apakah kamu yakin Bahkan jika saya menyimpan Buku dan Pengguna secara terpisah dinormalisasi? Bagi saya sepertinya hanya latensi jaringan biaya tambahan. Bagaimanapun, bagaimana seseorang menangani kasus RDBMS secara umum? (Saya sudah mengedit pertanyaan untuk menyebutkannya dengan jelas)
0fnt
1

Aturan praktis yang singkat dan sederhana:

ID digunakan dalam DTO s.
Referensi objek biasanya digunakan dalam Logika Domain / Logika Bisnis dan objek lapisan UI.

Itulah arsitektur umum dalam proyek-proyek yang lebih besar dan cukup kuat. Anda akan memiliki pemetaan yang menerjemahkan ke sana ke mari dari dua jenis objek ini.

herzmeister
sumber
Terima kasih telah mampir dan menjawab. Sayangnya, walaupun saya mengerti perbedaannya berkat tautan wiki, saya belum pernah melihat ini dalam praktiknya (memang saya belum pernah bekerja dengan proyek jangka panjang yang besar). Apakah Anda memiliki contoh di mana objek yang sama diwakili dalam dua cara untuk dua tujuan yang berbeda?
0fnt
berikut adalah pertanyaan aktual mengenai pemetaan: stackoverflow.com/questions/9770041/dto-to-entity-mapping-tool - dan ada artikel penting seperti ini: rogeralsing.com/2013/12/01/…
herzmeister
Sangat membantu, terima kasih. Sayangnya saya masih tidak mengerti bagaimana cara memuat data dengan referensi melingkar bekerja? mis. jika Pengguna merujuk Buku dan Buku merujuk pengguna yang sama, bagaimana Anda membuat objek ini?
0fnt
Lihatlah ke dalam pola Repositori . Anda akan memiliki BookRepositorydan UserRepository. Anda akan selalu memanggil myRepository.GetById(...)atau serupa, dan repositori akan membuat objek dan memuat nilainya dari penyimpanan data, atau mendapatkannya dari cache. Juga, objek anak-anak sebagian besar malas dimuat, yang juga mencegah harus berurusan dengan referensi melingkar langsung pada waktu konstruksi.
herzmeister