Indirizzando il fatto che le chiavi primarie non fanno parte del tuo dominio aziendale

25

In quasi tutte le circostanze, le chiavi primarie non fanno parte del tuo dominio aziendale. Certo, potresti avere alcuni importanti oggetti rivolti all'utente con indici univoci ( UserName per gli utenti o OrderNumber per gli ordini) ma nella maggior parte dei casi, non c'è bisogno di business per identificare apertamente gli oggetti del dominio un singolo valore o un insieme di valori, per chiunque ma forse un utente amministrativo. Anche in questi casi eccezionali, specialmente se utilizzi identificatori univoci globali (GUID) , ti piacerebbe o vorresti utilizzare un chiave alternativa invece di esporre la chiave primaria stessa.

Quindi, se la mia comprensione della progettazione basata sul dominio è accurata, le chiavi primarie non sono necessarie e quindi non dovrebbe essere esposto, e una buona liberazione. Sono brutti e strattonano il mio stile. Ma se scegliamo di non includere le chiavi primarie nel modello di dominio, ci sono conseguenze:

  1. Naively, DTO (data transfer objects) che derivano esclusivamente da combinazioni di modelli di dominio non avranno chiavi primarie
  2. I DTO in arrivo non avranno una chiave primaria

Quindi, è sicuro dire che se si vuole rimanere puri ed eliminare le chiavi primarie nel proprio modello di dominio, si dovrebbe essere pronti a gestire ogni richiesta in termini di indici univoci su quella chiave primaria?

In altre parole, quale delle seguenti soluzioni rappresenta l'approccio corretto per gestire l'identificazione di oggetti particolari dopo la rimozione di PK nei modelli di dominio?

  1. Essere in grado di identificare gli oggetti che devi affrontare con altri attributi
  2. Restituire la chiave primaria nel DTO; cioè, eliminando il PK quando si esegue il mapping dalla persistenza al dominio, quindi ricombinando il PK quando si esegue il mapping dal dominio al DTO?

MODIFICA: rendiamolo concreto.

Il mio modello di dominio è VoIPProvider che include campi come Name , Description , URL , nonché riferimenti come ProviderType , PhysicalAddress e Transactions .

Ora diciamo che voglio creare un servizio Web che consenta agli utenti con privilegi di gestire VoIPProvider s.

Forse un ID user-friendly è inutile in questo caso; dopo tutto, i provider VoIP sono aziende i cui nomi tendono ad essere distinti nel senso del computer e persino abbastanza distinti dal punto di vista umano per ragioni di business. Quindi potrebbe essere sufficiente dire che un VoIPProvider unico è completamente determinato da (Name, URL) . Quindi ora diciamo che ho bisogno di un metodo PUT api/providers/voip in modo che gli utenti con privilegi possano aggiornare VoIP provider. Invia un VoIPProviderDTO , che include molti ma non tutti i campi da VoIPProvider , incluso un po 'di appiattimento potenzialmente. Tuttavia, non posso leggere le loro menti, e hanno ancora bisogno di dirmi di quale fornitore stiamo parlando.

Sembra che abbia 2 (forse 3) opzioni:

  1. Includere una chiave primaria o una chiave alternativa nel mio modello di dominio e inviarla al DTO e viceversa
  2. Identifica il provider che ci interessa tramite l'indice univoco, come (Name, Url)
  3. Introduci una sorta di oggetto intermedio che può sempre mappare tra livello di persistenza, dominio e DTO in un modo che non espone dettagli di implementazione sul livello di persistenza, ad esempio introducendo un identificatore temporaneo in memoria quando si passa da dominio a DTO e ritorno,
posta tacos_tacos_tacos 27.08.2014 - 11:19
fonte

7 risposte

31

Questo è il modo in cui risolviamo questo problema (da più di 15 anni, quando nemmeno il termine "domain driven design" non è stato inventato):

  • quando si esegue il mapping del modello di dominio a un'implementazione di database o un modello di classe in un linguaggio di programmazione specifico, si dispone di una regola semplice e coerente come "per ogni oggetto dominio mappato a una tabella relazionale, la chiave primaria è" TablenameID ".
  • questa chiave primaria è completamente artificiale, ha sempre lo stesso tipo e nessun significato commerciale - solo un chiave surrogata
  • la "versione grafica" del tuo modello di dominio (quella che usi per parlare con gli esperti del tuo dominio) non contiene chiavi primarie. Non li esponi direttamente agli esperti (ma li esponi a chiunque stia effettivamente implementando il codice per il sistema).

Quindi ogni volta che hai bisogno di una chiave primaria per scopi tecnici (come mappare le relazioni con un database), ne hai una disponibile, ma finché non vuoi "vederla", cambia il tuo livello di astrazione in " modello di esperti di dominio ". E non devi mantenere "due modelli" (uno con PK e uno senza); invece, mantieni solo un modello senza PK e usa un generatore di codice per creare il DDL per il tuo DB, che aggiunge automaticamente il PK in base alle regole di mappatura.

Nota che questo non vieta di aggiungere alcuna "chiave di business" come un ulteriore "OrderNumber", oltre al surrogato OrderID . Tecnicamente queste chiavi aziendali diventano chiavi alternative durante la mappatura al database. Basta evitare di usarli per creare riferimenti ad altre tabelle, preferisci sempre utilizzare le chiavi surrogate se possibile, questo renderà le cose molto più facili.

Al tuo commento: l'uso di una chiave surrogata per identificare i record è un'operazione no aziendale, si tratta di un'operazione puramente tecnica. Per chiarire questo, guarda il tuo esempio: finché non definisci ulteriori contraffazioni univoci, sarebbe possibile avere due oggetti VoIPProvider con la stessa combinazione di (nome, url), ma diversi VoIPProviderIDs.

    
risposta data 27.08.2014 - 12:04
fonte
4

Devi essere in grado di identificare molti oggetti con un indice univoco, e non è quale è la chiave primaria (o almeno implica che uno sia presente).

Sono disponibili indici unici che consentono di limitare ulteriormente lo schema del DB, non come sostituzione all'ingrosso per PK. Se non esponi i PK perché sono brutti, ma esponi una chiave unica invece ... in realtà non stai facendo nulla di diverso. (Presumo che tu non stia ottenendo un PK e una colonna di identità mescolata qui?)

    
risposta data 27.08.2014 - 11:32
fonte
4

Senza chiavi primarie nel frontend non c'è un modo facile per il back-end di sapere cosa stai inviando. Per risolvere il problema, è necessario un sacco di lavoro extra che analizzi i dati, il che farebbe male le prestazioni e probabilmente richiedere più tempo ed essere più brutto del collegamento di una chiave a ciascun articolo.

Ad esempio, diciamo che voglio modificare un messaggio in un'app; in che modo l'app dovrebbe sapere quale messaggio desidero modificare senza una chiave primaria allegata? La modifica degli oggetti avviene sempre e farlo senza chiavi è quasi impossibile. Ma se hai oggetti che non dovrebbero essere modificati, salta la chiave se pensi che sia fonte di distrazione, ma avere le chiavi primarie può migliorare le prestazioni qui.

    
risposta data 27.08.2014 - 11:57
fonte
4

Il motivo per cui utilizziamo un PK non correlato all'attività aziendale è garantire che il nostro sistema disponga di un metodo semplice e coerente per determinare ciò che l'utente desidera.

Vedo che hai risposto con un commento: MessageSender, MessageRecipient, TimeSent (per un messaggio). È possibile ANCORA avere ambiguità in questo modo (ad esempio, con messaggi generati dal sistema che si attivano su qualcosa che accade spesso). E come hai intenzione di convalidare MessageSender e MessageRecipient qui? Supponiamo che tu li convalidi utilizzando FirstName, Lastname, DateOfBirth, finirai per imbattersi in una situazione in cui due persone sono nate nello stesso giorno con lo stesso identico nome. Per non parlare del fatto che ti imbatterai in una situazione in cui hai un messaggio chiamato tacostacostacos-America-1980-Doc Brown-France-1965-23/5/2014-11:43:54.003UTC+200 . È un mostro di nome e non hai ancora alcuna garanzia che ne avrai solo uno.

il motivo per cui utilizziamo una chiave primaria è perché SAPPIAMO che sarà unico per tutta la durata del software, indipendentemente dal tipo di dati immesso, e SAPPIAMO che sarà un formato prevedibile (cosa succede se la tua chiave precedente ha un trattino in un nome utente? il tuo intero sistema va a cagare).

Non è necessario mostrare il proprio ID al proprio utente. Puoi nasconderlo (in bella vista se necessario, attraverso l'URL).

Un altro motivo per cui un PK è così utile è qualcosa che puoi dedurre da quanto sopra: un PK fa in modo che tu non debba forzare il computer a interpretare il codice generato dall'utente. Se un utente cinese utilizza il tuo codice e inserisce un gruppo di caratteri cinesi, il tuo codice non ha bisogno di essere in grado di lavorare con questi interni, ma può semplicemente utilizzare la Guida generata dal sistema. Se hai un utente arabo che inserisce la scrittura in arabo, il tuo sistema non deve affrontarlo internamente, ma fondamentalmente può ignorare che sono lì.

Come altri hanno già detto, un Guid è qualcosa che può essere immagazzinato internamente in una dimensione fissa. Sai con cosa lavori ed è qualcosa che può essere universalmente usato. Non è necessario creare regole di progettazione su come creare un determinato identificatore e salvarlo. Se il tuo sistema prende solo le prime 10 lettere di un nome, non vede alcuna differenza tra Michael Guggenheimer e Michael Gugstein, e confonderà questi 2. Se lo tagli con qualsiasi lunghezza arbitraria, puoi incorrere in confusione. Se limiti l'input dell'utente, puoi incorrere in problemi con limitazioni dell'utente.

Quando guardo i sistemi esistenti come Dynamics CRM, usano anche la chiave interna (PK) per l'utente per chiamare un singolo record. Se un utente ha una query che non coinvolge l'ID, restituisce un array di possibili risposte e lascia che l'utente scelga da esso. Se c'è qualche possibilità di ambiguità, daranno la scelta all'utente.

Infine, è anche un po 'di sicurezza attraverso l'oscurità. Se non conosci l'ID del record, l'unica opzione è indovinarlo. se l'ID è facile da indovinare (perché l'informazione di cui è fatto è pubblicamente disponibile), chiunque può cambiarlo. Puoi persino spingere l'utente a cambiarlo con i metodi classici CSRF o XSS. Ora, ovviamente, la tua sicurezza dovrebbe già avere account e mitigati prima di pubblicare la versione live, ma dovresti rendere ancora più difficile il verificarsi di potenziali abusi.

    
risposta data 27.08.2014 - 15:23
fonte
1

Quando si rilascia un identificatore per un sistema esterno, si dovrebbe fornire solo URI, o in alternativa una chiave o un set di chiavi che abbiano le stesse proprietà di un URI, piuttosto che esporre direttamente la chiave primaria del database (da qui in poi I ' Mi riferisco sia all'URI che a una chiave o un insieme di chiavi che hanno le stesse proprietà dell'URI del solo URI, in altre parole un URI sotto non significa necessariamente URI RFC 3986).

Un URI può contenere o meno la chiave primaria dell'oggetto e potrebbe essere composto da tasti alternativi. Non ha molta importanza . Ciò che importa è che solo il sistema che genera l'URI è autorizzato a suddividere o combinare l'URI per formare una comprensione di ciò che è l'oggetto di riferimento. I sistemi esterni dovrebbero sempre utilizzare l'URI come identificatore opaco. Non importa se un utente umano può identificare che una parte dell'URI è in realtà una chiave surrogata del database o che è composta da diverse chiavi aziendali raggruppate insieme, o che in realtà è una base64 di quei valori. Questi sono irrilevanti. Ciò che importa è che il sistema esterno non debba essere necessario per capire che cosa significhi l'identificatore per usare l'identificatore. Ai sistemi esterni non dovrebbe mai essere richiesto di analizzare i componenti all'interno dell'identificatore o combinare un identificatore con altri identificatori per fare riferimento a qualcosa nel proprio sistema.

L'uso del GUID soddisfa alcuni di questi criteri, tuttavia l'identificatore come GUID può essere difficile reindirizzare l'oggetto anche all'interno del sistema, quindi le chiavi che sono opache anche per il sistema come un GUID dovrebbero essere utilizzate solo se il client analizza l'URI / identificatore pone effettivamente un rischio per la sicurezza.

Tornando all'esempio VoIP, affermare che un provider VoIP può essere determinato in modo univoco da (VoIPProviderID) o da (Nome, URL) o da (GUID). Quando un sistema esterno deve aggiornare il provider VoIP, può semplicemente passare un PUT / provider / per-id / 1234 o PUT /provider/foo-voip/bar-domain.com o PUT /3F2504E0-4F89-41D3-9A0C-0305E82C3301 e il tuo sistema capirà che il sistema esterno vuole aggiornare il VoIPProvider. Questi URI sono generati dal tuo sistema e solo il tuo sistema deve capire che tutto ciò significa la stessa cosa. Il sistema esterno dovrebbe semplicemente considerare tutto ciò che è nell'URI come fondamentalmente un PUT <whatever> .

Supponiamo di avere i dati per diversi provider VoIP memorizzati in tabelle diverse con schemi diversi (quindi un set di chiavi completamente diverso identifica ciascun provider VoIP in base alla tabella in cui sono memorizzati). Quando si dispone di un URI, è possibile accedervi in modo uniforme dal sistema esterno, indipendentemente da come il sistema identifica il particolare provider VoIP. Per il sistema esterno è tutto solo puntatore opaco.

Quando il tuo sistema usa un URI per riferirsi agli oggetti in questo modo, non stai perdendo nulla riguardo a come implementa il tuo sistema. Si genera l'URI e il client semplicemente lo restituisce.

    
risposta data 27.08.2014 - 17:31
fonte
0

Dovrò indirizzare questa affermazione selvaggiamente inesatta e ingenua:

Perhaps a user-friendly ID is useless in this case; after all, VoIP providers are companies whose names tend to be distinct in the computer sense and even distinct enough in the human sense for business reasons.

I nomi sono terribili come chiavi, in quanto cambiano di frequente. Una società può avere molti nomi durante la sua vita, la società potrebbe unirsi , dividere, unire di nuovo, creare una filiale distinta a fini fiscali specifici con 0 dipendenti ma tutti i clienti che poi assumono dipendenti da una filiale completamente diversa.

Poi arriviamo al fatto che i nomi delle società non sono nemmeno lontanamente unici, come mostrato dal punto di riferimento Apple vs. Apple .

Un buon object relational mapper o framework dovrebbe astrarre le chiavi primarie e renderle invisibili, ma sono lì, e di solito saranno il solo modo per univocamente identifica un oggetto nel tuo database.

Per riferimento, preferisco come django gestisce questo:

class VoipProvider(models.Model):
    name=fields.TextField()
    address=fields.TextField()

class Customer(models.Model):
    name=fields.TextField()
    address=fields.TextField()
    voipProvider=fields.ForeignKeyField(VoipProvider)

In questo modo, i dettagli di un fornitore di un cliente possono accedere al codice usando:

myCustomer.voipProvider.name #returns the name of the customers VOIP Provider.

Mentre le chiavi Primarie / Esterne non sono visibili, sono lì e possono essere utilizzate per accedere agli elementi, ma sono astratte.

    
risposta data 28.08.2014 - 03:33
fonte
0

Penso che spesso guardiamo erroneamente questo problema dal punto di vista del DB: oh, non c'è una chiave naturale, quindi abbiamo bisogno di creare una chiave surrogata. Oh no, non possiamo esporre la chiave surrogata agli oggetti del dominio, che perde, ecc.

Tuttavia, a volte un atteggiamento migliore è questo: se un oggetto business (dominio) non ha una chiave naturale, allora forse dovrebbe dargliene uno. Si tratta di un duplice problema di dominio aziendale: in primo luogo, le cose richiedono un'identità, anche in assenza di database. In secondo luogo, anche se proviamo a fingere che la persistenza sia un'idea astratta invisibile al dominio, la realtà è che la persistenza è ancora un concetto di business. Ovviamente, ci sono problemi in cui la chiave naturale scelta non è supportata dal DB come chiave primaria (ad esempio GUID su alcuni sistemi) - in questo caso sarà necessario aggiungere una chiave surrogata.

Quindi, finisci in un posto molto simile, ad es. il tuo cliente ha un ID intero, ma invece di sentirsi male perché è passato dal DB al dominio, ti senti felice perché l'azienda ha accettato che a tutti i clienti venga assegnato un ID, e lo stai persistendo nel DB, come devi. Sei ancora libero di introdurre un surrogato, ad es. per supportare la ridenominazione dell'ID cliente.

Questo approccio significa anche che se un oggetto dominio raggiunge il livello di persistenza e non ha un ID, allora è probabilmente una sorta di oggetto valore, quindi non ha bisogno di un ID.

    
risposta data 10.07.2016 - 08:34
fonte

Leggi altre domande sui tag