2012-04-05 13 views
5

Ho un progetto GAE scritto in Java e ho alcune considerazioni sull'HRD e un problema che non sono sicuro di come risolvere.Query HRD di Google App Engine senza antenato

Fondamentalmente ho utenti nel mio sistema. Un utente è composto da un ID utente, un nome utente, un'e-mail e una password. Ogni volta che creo un nuovo utente, voglio verificare che non ci sia già un utente con lo stesso ID utente (non dovrebbe mai accadere), nome utente o e-mail.

L'ID utente è la chiave, quindi penso che ottenere un risultato sarà coerente. Tuttavia, quando faccio una query (e uso un filtro) per trovare possibili utenti con lo stesso nome utente o e-mail, non posso essere sicuro che i risultati siano coerenti. Quindi, se qualcuno ha creato un utente con lo stesso nome utente o e-mail un paio di secondi fa, potrei non trovarlo con la mia query. Capisco che gli antenati sono abituati a risolvere questo problema, ma cosa succede se non ho un antenato da utilizzare per la query? L'utente non ha un genitore.

Sarei felice di sentire i vostri pensieri su questo e su ciò che è considerato la migliore pratica in situazioni come queste. Sto usando Objectify per GAE se questo cambia qualcosa.

+1

Ho fatto una domanda che potresti trovare molto utile. (http://stackoverflow.com/questions/6584435/how-can-i-create-two-unique-queriable-fields-for-a-ga-datastore-data-model) Anch'io ho bisogno di memorizzare informazioni uniche per i miei utenti. Nel mio caso, ho dovuto memorizzare sia un'e-mail univoca che un ID utente univoco per utente. Questo è un po 'difficile con l'HRD, ma ho trovato una soluzione affidabile. ... cont ... – RLH

+0

L'unico problema con la mia situazione è che l'implementazione della creazione del mio account (vedi la mia risposta) non si adatta bene. Questo è stato OK in queste circostanze perché la mia app GAE è molto piccola e ha un lento flusso di nuovi utenti (1 o 2 al mese). Inoltre, questa informazione è in Python, ma il codice è semplice: dovresti essere in grado di -legalo a Java con relativa facilità. – RLH

+0

@RLH Grazie per il vostro contributo, sempre interessante vedere soluzioni diverse, ma non penso che la vostra soluzione funzionerebbe bene nel mio caso. – Joel

risposta

7

Non mi consiglia di utilizzare l'e-mail o qualsiasi altra chiave naturale per l'entità utente. Gli utenti cambiano i loro indirizzi e-mail e non si vuole finire di riscrivere tutti i riferimenti alle chiavi esterne nel proprio database ogni volta che qualcuno cambia la propria e-mail.

Ecco una breve descrizione su come posso risolvere questo problema:

https://groups.google.com/d/msg/google-appengine/NdUAY0crVjg/3fJX3Gn3cOYJ

creare un'entità EmailLookup separata il cui @Id è la forma normalizzata di un indirizzo e-mail (ho appena minuscola tutto - tecnicamente corretta ma salva un sacco di dolore quando gli utenti capitalizzano inavvertitamente [email protected]). Il mio EmailLookup assomiglia a questo:

@Entity(name="Email") 
public class EmailLookup { 
    /** Use this method to normalize email addresses for lookup */ 
    public static String normalize(String email) { 
     return email.toLowerCase(); 
    } 

    @Id String email; 
    @Index long personId; 

    public EmailLookup(String email, long personId) { 
     this.email = normalize(email); 
     this.personId = personId; 
    } 
} 

C'è anche una (non normalizzata) campo e-mail nella mia entità User, che io uso per l'invio di messaggi di posta elettronica in uscita (preservare caso nel caso in cui è importante per qualcuno). Quando qualcuno crea un account con una particolare e-mail, carico/creo EmailLookup e le entità utente tramite il tasto in una transazione XG. Questo garantisce che ogni singolo indirizzo email sarà unico.

La stessa strategia si applica a qualsiasi altro tipo di valore univoco; ID di Facebook, nome utente, ecc.

+1

Posso garantire per questo metodo. Raccomando gli ID generati al 100% "long" per tutte le entità "effettive" e gli id ​​stringa per consistency/query enhancers come questo –

+0

Esistono chiavi naturali perfettamente legittime da utilizzare in molte applicazioni e il loro utilizzo spesso può salvare la necessità fare query, a favore di operazioni di datastore molto più efficienti. Ad esempio, la chiave naturale dell'entità di una pagina è il suo URI. –

+1

Certo, ci sono molte entità per le quali una chiave naturale ha senso. Dichiaro solo che l'entità Utente dell'OP non è una di queste :-) – stickfigure

3

Un modo per aggirare l'HRD eventual consistency, è utilizzare get anziché query. Per essere in grado di farlo è necessario generare ID naturali, ad es. generare ID che consistono di dati che ricevi in ​​richiesta: email e username.

Dal momento che get in HRD ha una consistenza forte, sarete in grado di controllare in modo affidabile se l'utente esiste già.

Ad esempio, un leggibile ID naturale sarebbe:

String naturalUserId = userEmail + "-" + userName; 

Nota: in pratica, le email sono unici. Quindi questo è un buon ID naturale per conto proprio. Non c'è bisogno di aggiungere un nome utente inventato ad esso.

+1

Mi piace la semplicità di questa soluzione, ma non mi piace l'idea di dover cambiare la chiave se l'e-mail o il nome utente cambiano. – Joel

-1

È anche possibile abilitare le transazioni tra gruppi (vedere https://developers.google.com/appengine/docs/java/datastore/overview#Cross_Group_Transactions) e quindi in una transazione cercare l'utente e crearne uno nuovo, se questo è utile.

+1

Da quel documento: "Analogamente alle transazioni a un solo gruppo, non è possibile eseguire una query di non antenato in una transazione XG." –

+0

Bene, allora c'è un problema. Forse provare la query prima della transazione, salvare il processo, e quindi al di fuori della transazione eseguire un'altra query per verificare se c'è un solo risultato del tipo desiderato? Deve però costare molto più tempo alla CPU, questo tipo di doppio controllo, quando non è molto probabile che le persone riempiano lo stesso indirizzo e-mail. Anche questa soluzione sembra piuttosto bella: http://stackoverflow.com/a/10032511/473180 – themarketka

+0

Inoltre, non è garantito che il doppio controllo sia coerente, quindi potresti ancora duplicare senza saperlo. Fondamentalmente, le query non antenate non possono mai essere utilizzate per verificare la coerenza con l'HRD. –

-1

Si consiglia di evitare un campo indicizzato e una query, a meno che non si disponga di altri usi. Ecco cosa ho fatto prima (Python) usando key_name (dato che gli ID entità devono essere int). Facile da utilizzare il nome_chiave o l'id per altre entità che devono collegarsi all'utente:

username = self.request.get('username') 
usernameLower = username.lower() 
rec = user.get_by_key_name(usernameLower) 
if rec is None: 
    U = user(
     key_name = usernameLower, 
     username = username, 
     etc...) 
    U.put() 
else: 
    self.response.out.write(yourMessageHere) 
+0

Questo non funziona in quanto un altro utente potrebbe inserire un nuovo nome utente del record == usernameLower poco prima del put. – supercobra