Idea malsana: stessi dati (a livello logico) spalmati su più entità "fisiche".

rated by 0 users
This post has 21 Replies | 3 Followers

Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.922
petrux Posted: 05-13-2011 14.33

Ciao a tutti,

 

mi sta frullando per la testa questa idea (malsana). Ho un modello che contiene:

class Employee { ... } //dipendente
class Friend {

    Employee Owner { ... }

 } //amico di un Employee

 

l'applicazione gestisce una rubrica telefonica E messaggi vocali. Un messaggio vocale può essere lasciato sia da un Employee che da un Friend. Stavo ragionando: se per rendere "trasparente" il mittente del messaggio vocale potessi fare una cosa del tipo:

public interface IMessageSender { Guid SenderId { ... } }

public VoiceMailMessage {

    ...

    IMessageSender Sender { get; }

}

 

poi modificare le classi di cui sopra:

class Employee : IMessageSender { ... }

class Friend : IMessageSender { ... }

 

e mappare IMessageSender su una view che è una union tra le tabelle che mappano employee e friend.
Se non ho capito male il concetto di aggregate root, questo significherebbe avere come root Employee, Friend e VoiceMailMessage.

 

Che ne pensate? 

ciao e grazie,

petrux

 

p.s. per chiarezza: l'idea è di usa NHibernate

--

Top 10 Partecipanti
Maschio
Post 244
Punteggio 3.403

Il concetto di aggregate root è differente, indica le entità di dominio che sono accessibili tramite repository, in questo caso tu stai invece utilizzando il polimorfismo dichiarando che una classe VoiceMailMessage dipende da un qualcosa che implementa IMessageSender.

Per il discorso View, potrebbe questo complicare il tutto, si potrebbe usare una classe base chiamata BaseMessage Sender, fare ereditare Employee e Friedn da questa classe per poi utilizzare una strategia di mapping per classi derivate (table per concrete class, table per hierarchy, etc) in questo modo mi sembrerebbe un po più usabile, invece di passare per delle view.

alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.922

Ciao Alk,

 

Gian Maria Ricci:

Il concetto di aggregate root è differente, indica le entità di dominio che sono accessibili tramite repository, in questo caso tu stai invece utilizzando il polimorfismo dichiarando che una classe VoiceMailMessage dipende da un qualcosa che implementa IMessageSender.

Ok, grazie per il chiarimento. 

Gian Maria Ricci:

Per il discorso View, potrebbe questo complicare il tutto, si potrebbe usare una classe base chiamata BaseMessage Sender, fare ereditare Employee e Friedn da questa classe per poi utilizzare una strategia di mapping per classi derivate (table per concrete class, table per hierarchy, etc) in questo modo mi sembrerebbe un po più usabile, invece di passare per delle view.

 

...e qui arriva il problema: le classi Firend e Employee appartengono *anche* ad altre gearachie di classi/interfacce che andrebbero "mappate" con la stessa tecnica (dato che non c'è ereditarietà multipla). :-(

 

Ciao,

Giulio

 

-- 

 

  • | Punteggio Post: 35
Top 10 Partecipanti
Maschio
Post 244
Punteggio 3.403

Lo sospettavo :) perchè ero sicuro che in quel caso avresti usato l'ereditarietà :D veniva troppo naturale. 

Una possibilità ,se non ti cali delle prestazioni è usare il mapping <any>.

alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.922

Ciao Alk,

 

Gian Maria Ricci:

Lo sospettavo :) perchè ero sicuro che in quel caso avresti usato l'ereditarietà :D veniva troppo naturale. 

eh eh eh... effettivamente sono un noto abusatore di joined-subclass! :-)

Gian Maria Ricci:

Una possibilità ,se non ti cali delle prestazioni è usare il mapping <any>.

 

Guarda, in teoria problemi di performance non dovrebbero esserci. Devo solo verificare che effettivamente funzioni usando una colonna che non sia la chiave primaria (ma che sia comunque unique, ecc ecc...). Spero di tirar fuori qualcosa di interessante!

 

Ciao e grazie,

Giulio

 

-- 

 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 244
Punteggio 3.403

Il mapping any ti consente di dire, questa entità ha un riferimento a qualche cosa che potrebbe essere una di queste X classi. Il gioco è appunto fare si che queste classi implementino una interfaccia comune (come hai già fatto tu) e poi fare un mapping di questo tipo per legare il tutto

 

		<any name="Sender" id-type="Guid" meta-type="System.String" cascade="all">
			<meta-value value="Friend" class="Friend"/>
			<meta-value value="Emploiye" class="Emploiye"/>
<column name="sendertype" /> <column name="senderid" /> </any>
Sostanzialmente lui ti crea una tabella di bridge in cui la colonna sendertype utilizza una stringa in cui scrive il metavalue (friend, employe) ad ogni meta value associa una classe
ed una ulteriore colonna in cui ci sta l'id di quella classe. Ho scritto il mapping al volo, per cui non ti fidare più di tanto :) ricontrolla la sintassi. 
Le performance si perdono perchè non puoi fare fetch join (non hai una tabella con cui fare join :( ) principalmente e anche perchè fai più letture multiple, ma è veramente flessibile.

Alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.922

Ciao Alk,

 

grazie per la risposta. :-)

Ti spiego in sintesi qual è il problema (che mi appresto a verificare). Questo funzionamento prevede che l'ID delle due classi (Friend ed Employee) sia dello stesso tipo. Ora, il problema è che essendo queste dentro ad altre gerarchie, probabilmente i loro ID saranno di tipi differenti. Ecco perché mi interessava poter avere una interfaccia ISender con un campo SenderIdentifier di tipo GUID e poter usare questo (e dalla documentazione di NH mi pare che non si possibile).

Qualche idea? ;-)

 

Ciao e grazie,

petrux

 

-- 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 244
Punteggio 3.403

Che intendi per id dello stesso tipo? Solitamente in un dominio dovrebbero tutti gli id avere la stessa strategia, se cosi non è è purtroppo più difficile ottenere quello che ti serve.. bisognerebbe vedere un esempio concreto ;)

alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.922

Ciao Alk,

Gian Maria Ricci:

Che intendi per id dello stesso tipo? Solitamente in un dominio dovrebbero tutti gli id avere la stessa strategia, se cosi non è è purtroppo più difficile ottenere quello che ti serve.. bisognerebbe vedere un esempio concreto ;)

Aspetta... questa mi è nuova! :-) Quindi dici che tutte le entità di un dominio dovrebbero avere tutte lo stesso tipo di ID? Perché nel 99% dei casi in cui mi sono trovato questo era praticamente impossibile... (penso ad esempio a scenari in cui devi integrare una anagrafica esistente che ha una stringa come ID, con altre cose che hanno un identity, ecc ecc...).

L'esempio concreto è proprio quello che ti ho fatto (per inciso: sulle mailing list di NH è silenzio totale :-)).

 

Ciao e grazie,

petrux

 

-- 

 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 244
Punteggio 3.403

Io uso sempre id surrogati, a meno che il database non sia legacy. Quindi ho sempre tutte entità con id guid o interi. Uso pochissimo gli id composti (spesso mi si sono ritorti contro). Ma sostanzialmente non mi capita di lavorare con db legacy spesso, per cui posso permettermi di fare questo.

Se hai id differenti non puoi fare miracoli, o puoi cambiare il db (aggiungi una chiave guid che poi userai solo tu) oppure devi risolverlo nel dominio, ovvero la classe che dipende dalla ISender si andrà a caricare uno per uno tutti i possibili sender.

alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.922

Ciao Alk,

 

Gian Maria Ricci:

Io uso sempre id surrogati, a meno che il database non sia legacy. Quindi ho sempre tutte entità con id guid o interi. Uso pochissimo gli id composti (spesso mi si sono ritorti contro). 

 

Capito.

 

Gian Maria Ricci:

Ma sostanzialmente non mi capita di lavorare con db legacy spesso, per cui posso permettermi di fare questo.

Eh, fortunello... :-D

Gian Maria Ricci:

Se hai id differenti non puoi fare miracoli, o puoi cambiare il db (aggiungi una chiave guid che poi userai solo tu) oppure devi risolverlo nel dominio, ovvero la classe che dipende dalla ISender si andrà a caricare uno per uno tutti i possibili sender.

 

La prima strategia mi è piuttosto chiara: cambio il db, aggiungo una colonna che fa da chiave  primaria alle mie entità. So far, so good. Il secondo approccio, proprio non l'ho capito. Puoi farmi un esempio?

 

Grazie e buon appetito,

petrux

 

-- 

 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 244
Punteggio 3.403

Il secondo esempio è possibile quando le entità del dominio posso accedere al repository. In questo caso la lista interna di ISender sarà qualche cosa di personalizzato che viene popolata dall'entità stessa, che conoscendo i possibili ISender li va a recuperare in giro. L'alternativa è farlo dall'esterno, ovvero per ogni entità che ha una lista di ISender si chiedono ai vari repository le classi e poi si mettono dentro.

Nessuno ti vieta di fare una ISenderLazyCollection personalizzata, che nel costruttore accetta un id dell oggetto che la usa, ed internamente incapsula la logica per creare i vari ISender.

Possono venire anche altre idee, supponi che hai employee e friend, etnrambi implementano ISender, ma hanno id differenti. Tu crei una class base Sender, con id guid, poi ne fai ereditare una chiamata EmployeeSEnder ed una FriendSender, la prima punta ad un emplyee (many to one), l'altra punta a friend. A questo punto la classe che ha necessità di una collection di ISEdner carica una collezione di SEnder, i quali internamente vanno a prendere i dati nelle tabelle reali... non la ho mai provata e mi è venuta in mente da questa discussione, per cui non so quanto sia fattibile/performante :)

alk.

 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.922

Ciao Alk,

Gian Maria Ricci:

Il secondo esempio è possibile quando le entità del dominio posso accedere al repository. In questo caso la lista interna di ISender sarà qualche cosa di personalizzato che viene popolata dall'entità stessa, che conoscendo i possibili ISender li va a recuperare in giro. L'alternativa è farlo dall'esterno, ovvero per ogni entità che ha una lista di ISender si chiedono ai vari repository le classi e poi si mettono dentro.

Capito. Onestamente la possibilità di avere un riferimento Entità->Repository non è che mi entusiasmi... 

Gian Maria Ricci:

Nessuno ti vieta di fare una ISenderLazyCollection personalizzata, che nel costruttore accetta un id dell oggetto che la usa, ed internamente incapsula la logica per creare i vari ISender.

Approccio comunque interessante. :-)

Gian Maria Ricci:

Possono venire anche altre idee, supponi che hai employee e friend, etnrambi implementano ISender, ma hanno id differenti. Tu crei una class base Sender, con id guid, poi ne fai ereditare una chiamata EmployeeSEnder ed una FriendSender, la prima punta ad un emplyee (many to one), l'altra punta a friend. A questo punto la classe che ha necessità di una collection di ISEdner carica una collezione di SEnder, i quali internamente vanno a prendere i dati nelle tabelle reali... non la ho mai provata e mi è venuta in mente da questa discussione, per cui non so quanto sia fattibile/performante :)

 

Questo approccio è quello che ho seguito finora. Il problema è che la proliferazioni di classi "sporca" il dominio. Qualcuno sulla ML di NH mi aveva indicato l'utilizzo di uno IUserType ma onestamente non saprei proprio come fare quello che mi serve. :-) Dovrei studiare un po' meglio la cosa ma onestamente ora come ora non ho tempo. Per ora sono riuscito a tamponare con un any type mapping, in seguito vedrò se rifattorizzare. Qualcuno di voi ha qualche idea in proposito?

Grazie e buona giornata,

Giulio

-- 

 

 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 244
Punteggio 3.403

In DDD il fatto che una entità possa chiamare il repository, o comunque il layer di persistenza è assolutamente normale :) soprattutto se il dominio non è molto anemico. 

Lo UserType può salvarti la vita in alcuni casi, in questo caso però la vedo una forzatura. Il problema è che NH è un ORM, e quindi fa il possibile su database legacy, ma non può fare miracoli. Il tool nasce per salvare oggetti, e quindi creare un database che sia ORM friendly, se tu hai un db legacy NH fa quello che può, ma nel tuo caso tu hai

1) nel mondo ad oggetti una associazione polimorfa

2) La struttura dati già fatta con id di tipo differente.

Questo ti crea il problema :) e quindi per superarlo devi cmq fare qualche giro. :) facci sapere poi come hai risolto 

alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.922

Ciao Alk,

 

Gian Maria Ricci:

In DDD il fatto che una entità possa chiamare il repository, o comunque il layer di persistenza è assolutamente normale :) soprattutto se il dominio non è molto anemico. 

Uhm... non so. Devo ancora approcciare in maniera analitica il DDD (visto che il libro di Evans mi verrà recapitato il 24 Maggio... BTW: dai uno sguardo al tuo blog :-P) ma l'idea non la metabolizzo in maniera immediata. Ma già che ci siamo una domanda la snocciolo: come inietti la dipendensa dal data layer nell'entità? inietti un repo? (la risposta - qualunque essa sia - sarà lo stimolo per l'apertura di un thread su un problema che sto affrontando in questi giorni <eg>)

Gian Maria Ricci:

Lo UserType può salvarti la vita in alcuni casi, in questo caso però la vedo una forzatura. Il problema è che NH è un ORM, e quindi fa il possibile su database legacy, ma non può fare miracoli. Il tool nasce per salvare oggetti, e quindi creare un database che sia ORM friendly, se tu hai un db legacy NH fa quello che può, ma nel tuo caso tu hai

1) nel mondo ad oggetti una associazione polimorfa

2) La struttura dati già fatta con id di tipo differente.

Questo ti crea il problema :) e quindi per superarlo devi cmq fare qualche giro. :) facci sapere poi come hai risolto 

Premesso che nel caso *attuale* sono riuscito ad avere id tutti dello stesso tipo, in altri casi specifici ho risolto in maniera abbastanza "funambolica". :-)
Faccio un esempio un po' lunghetto ma chiaro. Supponiamo che il mio db legacy abbia una tabella LegacyEmployee con ID stringa, nome e cognome (ambedue stringhe). La mia applicazione deve inserire anche un indirizzo e-mail e imporre un id di tipo Guid all'Employee. Come faccio?
1. Creo una tabella MioEmployee che ha un Id di itpo Guid, un campo EMail *E* un campo LegacyId di tipo stringa. 
2. Creo una stored procedure che lavora in due passi (con due outer join) incrociando MioEmployee e LegacyEmployee prima in questo ordine e poi in quello opposto. La prima passata mi serve per identificare i legacy che devono essere mappati nella mia tabella, la seconda invece quelli che devono essere rimossi. 
3. mappo la classe Employee su una view vEmployee che fa una join delle due tabelle sul legacy id (che non viene mappato nel dominio OO). 
4. corredo la view vEmployee con una serie di trigger per fare quello che serve.
Una volta che si ha chiaro dove andare a parare, non è nemmeno troppo complesso da realizzare. Il tempo richiesto può essere "sensibile", soprattutto se ci si aggiunge un pellegrinaggio a Loreto o qualche altro santuario. ;-)
Ciao,
petrux
-- 
 

  • | Punteggio Post: 20
Pagina 1 di 2 (22 elementi) 1 2 Avanti > | RSS
Powered by Community Server (Commercial Edition), by Telligent Systems