Membro: usa ID univoci vs oggetto dominio


10

Dopo un paio di risposte utili sull'opportunità di utilizzare un oggetto dominio o un ID univoco come parametro metodo / funzione qui Identificatore vs oggetto dominio come parametro metodo , ho una domanda simile in merito a: membri (la discussione delle domande precedenti non è riuscita a coprire questo). Quali sono i pro e i contro dell'utilizzo di ID univoci come membro rispetto all'oggetto come membro. Sto chiedendo in riferimento a linguaggi fortemente tipizzati, come Scala / C # / Java. Dovrei avere (1)

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

o (2), preferito a (1) Dopo aver esaminato: dovremmo definire i tipi per tutto?

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

o (3)

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

Mentre non riesco a pensare ai vantaggi di avere l'oggetto (3), uno dei vantaggi di avere ID (2) e (1) è che quando creo l'oggetto Utente da DB, non devo creare l'oggetto Book, che può a sua volta dipendere dall'oggetto Utente stesso, creando una catena infinita. Esiste una soluzione generica a questo problema sia per RDBMS che per No-SQL (se diversi)?

Sulla base di alcune risposte finora, riformulando la mia domanda: (con l'uso di ID che dovrebbero essere in tipi di wrapping) 1) Usa sempre ID? 2) Usa sempre gli oggetti? 3) Utilizzare gli ID quando esiste il rischio di ricorsione nella serializzazione e nella deserializzazione, ma utilizzare oggetti altrimenti? 4) Qualcos'altro?

EDIT: se rispondi al fatto che gli oggetti dovrebbero essere usati sempre o per alcuni casi, assicurati di rispondere alla preoccupazione più grande che altri rispondenti hanno pubblicato => Come ottenere i dati dal DB


1
Grazie per la buona domanda, non vedo l'ora di seguirlo con interesse. Peccato che il tuo nome utente sia "user18151", le persone con questo tipo di nome utente vengono ignorate da alcuni :)
bjfletcher,

@bjfletcher Grazie. Anch'io avevo quella fastidiosa percezione, ma non mi è mai venuto in mente il perché!
0

Risposte:


7

Gli oggetti di dominio come ID creano alcuni problemi complessi / sottili:

Serializzazione / deserializzazione

Se si memorizzano oggetti come chiavi, la serializzazione del grafico degli oggetti sarà estremamente complicata. Riceverai stackoverflowerrori quando esegui una serializzazione ingenua in JSON o XML a causa della ricorsione. Dovrai quindi scrivere un serializzatore personalizzato che converta gli oggetti effettivi per utilizzare i loro ID invece di serializzare l'istanza dell'oggetto e creare la ricorsione.

Passa gli oggetti per la sicurezza dei tipi ma archivia solo gli ID, quindi puoi avere un metodo di accesso che pigra l'entità correlata quando viene chiamata. La memorizzazione nella cache di secondo livello si occuperà delle chiamate successive.

Perdite di riferimento sottili:

Se usi oggetti di dominio in costruttori come quelli che hai lì creerai riferimenti circolari che saranno molto difficili da consentire alla memoria di essere recuperata per oggetti che non vengono utilizzati attivamente.

Situazione ideale:

ID opachi vs int / long:

An iddovrebbe essere un identificatore completamente opaco che non contiene informazioni su ciò che identifica. Ma dovrebbe offrire una verifica che sia un identificatore valido nel suo sistema.

I tipi grezzi rompono questo:

int, longE Stringsono i tipi prime più comunemente utilizzati per gli identificatori nel sistema RDBMS. C'è una lunga storia di ragioni pratiche che risalgono a decenni fa e sono tutti compromessi che si adattano al risparmio spaceo al risparmio timeo entrambi.

Gli ID sequenziali sono i peggiori trasgressori:

Quando si utilizza un ID sequenziale, si impacchettano le informazioni semantiche temporali nell'ID per impostazione predefinita. Il che non è male fino a quando non viene utilizzato. Quando le persone iniziano a scrivere una logica aziendale che ordina o filtra la qualità semantica dell'id, allora stanno creando un mondo di dolore per i futuri manutentori.

String i campi sono problematici perché i progettisti ingenui comprimeranno le informazioni nei contenuti, di solito anche la semantica temporale.

Ciò rende impossibile creare anche un sistema di dati distribuito, perché non12437379123 è univoco a livello globale. Le probabilità che un altro nodo in un sistema distribuito crei un record con lo stesso numero è praticamente garantita quando si ottengono abbastanza dati in un sistema.

Quindi gli hack iniziano a aggirarlo e l'intera cosa si trasforma in un mucchio di caos fumante.

Ignorare enormi sistemi distribuiti ( cluster ) diventa un incubo completo quando si inizia a provare a condividere i dati anche con altri sistemi. Soprattutto quando l'altro sistema non è sotto il tuo controllo.

Ti ritrovi con lo stesso identico problema, come rendere il tuo ID unico a livello globale.

UUID è stato creato e standardizzato per un motivo:

UUIDpuò soffrire di tutti i problemi sopra elencati a seconda di quale Versionsi utilizza.

Version 1utilizza un indirizzo MAC e l'ora per creare un ID univoco. Ciò è negativo perché contiene informazioni semantiche su posizione e tempo. Questo non è di per sé un problema, è quando gli sviluppatori ingenui iniziano a fare affidamento su tali informazioni per la logica aziendale. Ciò perde anche informazioni che potrebbero essere sfruttate in qualsiasi tentativo di intrusione.

Version 2utilizza un utente UIDo GIDdomian UIDo GUIal posto del tempo da Version 1questo è altrettanto grave come Version 1per la perdita di dati e il rischio di queste informazioni da utilizzare nella logica aziendale.

Version 3è simile ma sostituisce l'indirizzo MAC e l'ora con un MD5hash di una matrice di byte[]qualcosa che ha sicuramente un significato semantico. Non vi è alcuna perdita di dati di cui preoccuparsi, il byte[]non può essere recuperato dal UUID. Questo ti dà un buon modo per creare in modo deterministico UUIDforma istanze e chiave esterna di qualche tipo.

Version 4 si basa solo su numeri casuali che rappresentano una buona soluzione, non trasporta assolutamente alcuna informazione semantica, ma non è deterministicamente ricostruibile.

Version 5è come Version 4ma usa sha1invece di md5.

Chiavi di dominio e chiavi di dati transazionali

La mia preferenza per gli ID oggetto di dominio è di utilizzare Version 5o, Version 3se limitato, Version 5per qualche motivo tecnico.

Version 3 è ottimo per i dati di transazione che potrebbero essere distribuiti su molte macchine.

A meno che tu non sia limitato dallo spazio, usa un UUID:

Sono garantiti univoci, scaricando dati da un database e ricaricandoli in un altro, senza mai doverti preoccupare di ID duplicati che fanno effettivamente riferimento a dati di dominio diversi.

Version 3,4,5 sono completamente opachi ed è così che dovrebbero essere.

Puoi avere una singola colonna come chiave primaria con un UUIDe quindi puoi avere indici univoci composti per quella che sarebbe stata una chiave primaria composita naturale.

Bagagli non non deve essere CHAR(36)neanche. È possibile archiviare il campo UUIDin un byte / bit / numero nativo per un determinato database purché sia ​​ancora indicizzabile.

eredità

Se hai tipi non elaborati e non puoi modificarli, puoi comunque estrarli nel tuo codice.

Usando uno Version 3/5di UUIDvoi puoi passare il Class.getName()+ String.valueOf(int)come a byte[]e avere una chiave di riferimento opaca che è ricreabile e deterministica.


Mi dispiace molto se non sono stato chiaro nella mia domanda e mi sento peggio (o effettivamente buono) perché questa è una risposta così grande e ponderata e chiaramente ci hai passato del tempo. Sfortunatamente non si adatta alla mia domanda, forse merita una domanda tutta sua? "Cosa devo tenere a mente quando creo un campo ID per il mio oggetto dominio"?
0

Ho aggiunto una spiegazione esplicita.

Ho capito adesso. Grazie per aver dedicato del tempo alla risposta.
0

1
A proposito, i raccoglitori di rifiuti generazionali AFAIK (che credo sia il sistema GC dominante in questi giorni) non dovrebbero avere troppe difficoltà nei riferimenti circolari di GC.
0

1
se C-> A -> B -> Ae Bviene messo in un Collectionallora Ae tutti i suoi figli sono ancora raggiungibili, queste cose non sono completamente ovvie e possono portare a sottili perdite . GCè l'ultimo dei problemi, serializzazione e deserializzazione del grafico è un incubo di complessità.

2

Sì, ci sono vantaggi in entrambi i modi e c'è anche un compromesso.

List<int>:

  • Salva memoria
  • Inizializzazione più rapida del tipo User
  • Se i tuoi dati provengono da un database relazionale (SQL), non è necessario accedere a due tabelle per ottenere utenti, solo la Userstabella

List<Book>:

  • L'accesso a un libro è più rapido da parte dell'utente, il libro è stato precaricato in memoria. Questo è bello se puoi permetterti di avere un avvio più lungo al fine di ottenere operazioni successive più veloci.
  • Se i tuoi dati provengono da un database dell'archivio documenti come HBase o Cassandra, allora i valori dei libri letti sono probabilmente nel record dell'Utente, quindi avresti potuto facilmente ottenere i libri "mentre eri lì a ottenere l'utente".

Se non avessi problemi di memoria o CPU che vorrei affrontare List<Book>, il codice che utilizza le Useristanze sarà più pulito.

Compromesso:

Quando si utilizza Linq2SQL, il codice generato per l'entità Utente avrà un EntitySet<Book>carico pigro quando si accede ad esso. Ciò dovrebbe mantenere pulito il codice e l'istanza utente ridotta (footprint di memoria saggio).


Supponendo una sorta di memorizzazione nella cache, il vantaggio del precaricamento sarebbe nullo. Non ho usato Cassandra / HBase, quindi non posso parlarne, ma Linq2SQL è un caso molto specifico (anche se non vedo come il caricamento pigro impedirà il caso di concatenamento infinito anche in questo caso specifico e nel caso generale)
0

Nell'esempio di Linq2SQL non si ottiene alcun vantaggio in termini di prestazioni, solo un codice più pulito. Quando si ottengono da una a molte entità da un archivio di documenti come Cassandra / HBase, la maggior parte del tempo di elaborazione viene impiegato per trovare il record, quindi è possibile ottenere tutte le numerose entità mentre ci si trova (i libri, in questo esempio).
ytoledano,

Sei sicuro? Anche se conservo libri e utenti normalizzati separatamente? A me sembra che dovrebbe essere solo un costo aggiuntivo di latenza di rete. In ogni caso, come si gestisce genericamente il caso RDBMS? (Ho modificato la domanda per menzionarlo chiaramente)
0

1

Breve e semplice regola empirica:

Gli ID vengono utilizzati nei DTO .
I riferimenti agli oggetti vengono generalmente utilizzati negli oggetti livello logico di dominio / business logic e UI.

Questa è l'architettura comune in progetti più grandi e abbastanza intraprendenti. Avrai mappatori che traducono in questi due tipi di oggetti.


Grazie per esserti fermato e aver risposto. Sfortunatamente, mentre capisco la distinzione grazie al link wiki, non l'ho mai visto in pratica (purché non abbia mai lavorato con grandi progetti a lungo termine). Avresti un esempio in cui lo stesso oggetto veniva rappresentato in due modi per due scopi diversi?
0

ecco una vera domanda sulla mappatura: stackoverflow.com/questions/9770041/dto-to-entity-mapping-tool - e ci sono articoli critici come questo: rogeralsing.com/2013/12/01/…
herzmeister

Davvero utile, grazie. Purtroppo non capisco ancora come funzionerebbe il caricamento dei dati con riferimenti circolari? ad es. se un utente fa riferimento a un libro e il libro fa riferimento allo stesso utente, come creeresti questo oggetto?
0

Guarda nel modello di repository . Avrai a BookRepositorye a UserRepository. Chiamerai sempre myRepository.GetById(...)o simili, e il repository creerà l'oggetto e caricherà i suoi valori da un archivio dati o lo riceverà da una cache. Inoltre, gli oggetti figlio sono per lo più carichi pigri, il che impedisce anche di avere a che fare con riferimenti circolari diretti in fase di costruzione.
Herzmeister,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.