Depois de algumas respostas úteis sobre se devo usar objeto de domínio ou um ID exclusivo como parâmetro de método / função aqui Identificador x objeto de domínio como parâmetro de método , eu tenho uma pergunta semelhante re: members (a discussão das perguntas anteriores não conseguiu cobrir isso). Quais são os prós e os contras do uso de IDs exclusivos como membro versus objeto como membro. Estou perguntando em referência a linguagens fortemente tipadas, como Scala / C # / Java. Eu deveria ter (1)
User( id: Int, CurrentlyReadingBooksId: List[Int])
Book( id: Int, LoanedToId: Int )
ou (2), preferida a (1) Após analisar: Devemos definir tipos para tudo?
User( id: UserId, CurrentlyReadingBooksId: List[ BookId] )
Book( id: BookId, LoanedToId: UserId )
ou (3)
User( id: Int, CurrentlyReadingBooks: List[Book])
Book( id: Int, LoanedTo: User)
Embora eu não consiga pensar nos benefícios de ter o objeto (3), um dos benefícios de ter os IDs (2) e (1) é que, quando estou criando o objeto Usuário a partir do DB, não preciso criar o objeto Livro, que por sua vez, pode depender do próprio objeto Usuário, criando uma cadeia sem fim. Existe uma solução genérica para esse problema para RDBMS e No-SQL (se forem diferentes)?
Com base em algumas respostas até agora, reformulando minha pergunta: (com o uso de IDs supostamente em tipos agrupados) 1) Sempre usar IDs? 2) Sempre usar objetos? 3) Use IDs quando houver risco de recursão na serialização e desserialização, mas use objetos de outra forma? 4) Mais alguma coisa?
EDIT: Se você responder que os Objetos devem ser usados sempre ou, em alguns casos, certifique-se de responder à maior preocupação que outros respondentes postaram => Como obter dados do DB
fonte
Respostas:
Objetos de domínio como ids criam alguns problemas sutis / complexos:
Serialização / desserialização
Se você armazenar objetos como chaves, a serialização do gráfico será extremamente complicada. Você receberá
stackoverflow
erros ao fazer uma serialização ingênua para JSON ou XML por causa da recursão. Você precisará escrever um serializador personalizado que converta os objetos reais para usar seus IDs, em vez de serializar a instância do objeto e criar a recursão.Passe objetos para segurança de tipo, mas apenas armazene IDs, e você poderá ter um método de acesso que carrega preguiçosamente a entidade relacionada quando é chamada. O cache de segundo nível cuidará das chamadas subseqüentes.
Vazamentos sutis de referência:
Se você usar objetos de domínio em construtores como você, criará referências circulares que serão muito difíceis de permitir que a memória seja recuperada para objetos que não estão sendo usados ativamente.
Situação ideal:
IDs opacos vs int / long:
Um
id
deve ser um identificador completamente opaco que não carrega informações sobre o que identifica. Mas deve oferecer alguma verificação de que é um identificador válido em seu sistema.Os tipos brutos quebram isso:
int
,long
EString
são os tipos de matérias mais comumente usados para identificadores no sistema RDBMS. Há uma longa história de razões práticas que datam de décadas e todas elas são compromissos que se encaixam na economiaspace
ou na economiatime
ou em ambas.Os IDs seqüenciais são os piores criminosos:
Ao usar um ID seqüencial, você está compactando informações semânticas temporais no ID por padrão. O que não é ruim até que seja usado. Quando as pessoas começam a escrever uma lógica comercial que classifica ou filtra a qualidade semântica do ID, elas estão criando um mundo de dor para futuros mantenedores.
String
os campos são problemáticos porque designers ingênuos agrupam informações no conteúdo, geralmente semântica temporal também.Isso torna impossível criar também um sistema de dados distribuídos, porque não
12437379123
é único globalmente. As chances de outro nó em um sistema distribuído criar um registro com o mesmo número são praticamente garantidas quando você obtém dados suficientes em um sistema.Em seguida, os hacks começam a contorná-lo e tudo se transforma em uma pilha de bagunça fumegante.
Ignorar grandes sistemas distribuídos ( clusters ) torna-se um pesadelo completo quando você começa a tentar compartilhar os dados com outros sistemas também. Especialmente quando o outro sistema não está sob seu controle.
Você acaba com o mesmo problema: como tornar sua identificação globalmente única.
O UUID foi criado e padronizado por um motivo:
UUID
pode sofrer de todos os problemas listados acima, dependendo de qualVersion
você usar.Version 1
usa um endereço MAC e tempo para criar um ID exclusivo. Isso é ruim porque carrega informações semânticas sobre localização e hora. Isso não é, por si só, um problema, é quando desenvolvedores ingênuos começam a confiar nessas informações para a lógica de negócios. Isso também vaza informações que podem ser exploradas em qualquer tentativa de invasão.Version 2
O uso de usuáriosUID
ouGID
domianUID
ouGUI
no lugar a partirVersion 1
disso é tão ruim quantoVersion 1
para vazamento de dados e o risco de essas informações serem usadas na lógica de negócios.Version 3
é semelhante, mas substitui o endereço MAC e o tempo por umMD5
hash de alguma matriz debyte[]
algo que definitivamente tem significado semântico. Não há vazamento de dados para se preocupar, obyte[]
não pode ser recuperado a partir doUUID
. Isso fornece uma boa maneira de criar deterministicamente oUUID
formulário de instâncias e a chave externa de algum tipo.Version 4
baseia-se apenas em números aleatórios, o que é uma boa solução, não carrega absolutamente nenhuma informação semântica, mas não é deterministicamente recriada.Version 5
é comoVersion 4
mas usa emsha1
vez demd5
.Chaves de domínio e chaves de dados transacionais
Minha preferência por IDs de objetos de domínio é usar
Version 5
ou,Version 3
se houver restrição,Version 5
por algum motivo técnico.Version 3
é ótimo para dados de transações que podem estar espalhados por muitas máquinas.A menos que você esteja limitado pelo espaço, use um UUID:
Eles têm garantia única, despejando dados de um banco de dados e recarregando em outro. Você nunca precisou se preocupar com IDs duplicados que realmente fazem referência a dados de domínio diferentes.
Version 3,4,5
são completamente opacos e é assim que deveriam ser.Você pode ter uma única coluna como chave primária com a
UUID
e, em seguida, pode ter índices exclusivos compostos para o que teria sido uma chave primária composta natural.O armazenamento também não precisa ser
CHAR(36)
. Você pode armazenar o campoUUID
em um byte / bit / número nativo para um determinado banco de dados, desde que ele ainda seja indexável.Legado
Se você tem tipos brutos e não pode alterá-los, ainda pode abstraí-los no seu código.
Usar um
Version 3/5
deUUID
vocês pode passar oClass.getName()
sinal de +String.valueOf(int)
como abyte[]
e ter uma chave de referência opaca que é recreativa e determinística.fonte
C-> A -> B -> A
eB
é colocado em umCollection
entãoA
e todos os seus filhos ainda são alcançáveis, essas coisas não são completamente óbvias e podem levar a vazamentos sutis .GC
Como o menor dos problemas, serialização e desserialização do gráfico é um pesadelo de complexidade.Sim, existem benefícios para ambos os lados e também há um compromisso.
List<int>
:User
Users
tabelaList<Book>
:Se você não tiver problemas de memória ou CPU, eu continuaria com
List<Book>
o código que usa asUser
instâncias.Compromisso:
Ao usar o Linq2SQL, o código gerado para o usuário da entidade terá um
EntitySet<Book>
carregamento lento quando você o acessar. Isso deve manter seu código limpo e a instância de Usuário pequena (tamanho da memória).fonte
Regra curta e simples:
Os IDs são usados nos DTOs .
As referências a objetos são geralmente usadas nos objetos de lógica de domínio / lógica de negócios e camada de interface do usuário.
Essa é a arquitetura comum em projetos maiores e empresariais o suficiente. Você terá mapeadores que se traduzem para esses dois tipos de objetos.
fonte
BookRepository
e umUserRepository
. Você sempre chamarámyRepository.GetById(...)
ou semelhante, e o repositório criará o objeto e carregará seus valores de um armazenamento de dados ou o obterá de um cache. Além disso, os objetos filhos geralmente são carregados preguiçosamente, o que também evita a necessidade de lidar com referências circulares diretas no momento da construção.