ATUALIZAÇÃO : existe agora um documento sobre estruturação de dados . Além disso, veja esta excelente postagem sobre estruturas de dados NoSQL .
O principal problema com dados hierárquicos, em oposição ao RDBMS, é que é tentador aninhar dados porque podemos. Geralmente, você deseja normalizar os dados até certo ponto (assim como faria com SQL), apesar da falta de instruções e consultas de junção.
Você também deseja desnormalizar em locais onde a eficiência de leitura é uma preocupação. Esta é uma técnica usada por todos os aplicativos de grande escala (por exemplo, Twitter e Facebook) e, embora vá contra nossos princípios DRY, geralmente é um recurso necessário para aplicativos escaláveis.
A essência aqui é que você deseja trabalhar duro nas gravações para facilitar as leituras. Mantenha os componentes lógicos que são lidos separadamente separados (por exemplo, para salas de bate-papo, não coloque as mensagens, meta-informações sobre as salas e listas de membros no mesmo lugar, se você quiser ser capaz de iterar os grupos mais tarde).
A principal diferença entre os dados em tempo real do Firebase e um ambiente SQL é a consulta de dados. Não há uma maneira simples de dizer "SELECIONE USUÁRIOS ONDE X = Y", devido à natureza em tempo real dos dados (eles estão constantemente mudando, fragmentando, reconciliando, etc, o que requer um modelo interno mais simples para manter os clientes sincronizados sob controle)
Um exemplo simples provavelmente o deixará no estado de espírito certo, então aqui vai:
/users/uid
/users/uid/email
/users/uid/messages
/users/uid/widgets
Agora, como estamos em uma estrutura hierárquica, se eu quiser iterar os endereços de e-mail dos usuários, faço algo assim:
// I could also use on('child_added') here to great success
// but this is simpler for an example
firebaseRef.child('users').once('value')
.then(userPathSnapshot => {
userPathSnapshot.forEach(
userSnap => console.log('email', userSnap.val().email)
);
})
.catch(e => console.error(e));
O problema com essa abordagem é que acabei de forçar o cliente a baixar todos os usuários messages
e widgets
também. Não é nada demais se nenhuma dessas coisas chega aos milhares. Mas é um grande problema para 10 mil usuários com mais de 5 mil mensagens cada.
Portanto, agora a estratégia ideal para uma estrutura hierárquica em tempo real se torna mais óbvia:
/user_meta/uid/email
/messages/uid/...
/widgets/uid/...
Uma ferramenta adicional extremamente útil neste ambiente são os índices. Ao criar um índice de usuários com certos atributos, posso simular rapidamente uma consulta SQL simplesmente iterando o índice:
/users_with_gmail_accounts/uid/email
Agora, se eu quiser, digamos, receber mensagens para usuários do gmail, posso fazer algo assim:
var ref = firebase.database().ref('users_with_gmail_accounts');
ref.once('value').then(idx_snap => {
idx_snap.forEach(idx_entry => {
let msg = idx_entry.name() + ' has a new message!';
firebase.database().ref('messages').child(idx_entry.name())
.on(
'child_added',
ss => console.log(msg, ss.key);
);
});
})
.catch(e => console.error(e));
Eu ofereci alguns detalhes em outro post do SO sobre desnormalização de dados, então verifique-os também . Vejo que Frank já postou o artigo de Anant, então não vou reiterar isso aqui, mas também é uma ótima leitura.