Como evitar interfaces de bate-papo


10

Antecedentes: estou projetando um aplicativo de servidor e criando DLLs separadas para diferentes subsistemas. Para simplificar, digamos que eu tenha dois subsistemas: 1) Users2)Projects

A interface pública do usuário possui um método como:

IEnumerable<User> GetUser(int id);

E a interface pública do Projects possui um método como:

IEnumerable<User> GetProjectUsers(int projectId);

Assim, por exemplo, quando precisamos exibir os usuários para um determinado projeto, podemos chamar GetProjectUserse isso fornecerá objetos com informações suficientes para mostrar em um datagrid ou similar.

Problema: Idealmente, o Projectssubsistema também não deve armazenar informações do usuário e apenas armazenar IDs dos usuários participantes de um projeto. Para atender GetProjectUsers, ele precisa chamar GetUsero Userssistema para cada ID de usuário armazenado em seu próprio banco de dados. No entanto, isso requer muitas GetUserchamadas separadas , causando muitas consultas sql separadas dentro do Usersubsistema. Eu realmente não testei isso, mas ter esse design falador afetará a escalabilidade do sistema.

Se eu deixar de lado a separação dos subsistemas, poderia armazenar todas as informações em um único esquema acessível por ambos os sistemas e Projectspoderia simplesmente fazer um JOINpara obter todos os usuários do projeto em uma única consulta. Projectstambém precisaria saber como gerar Userobjetos a partir dos resultados da consulta. Mas isso quebra a separação, que tem muitas vantagens.

Pergunta: Alguém pode sugerir uma maneira de manter a separação, evitando todas essas GetUserchamadas individuais durante GetProjectUsers?


Por exemplo, uma idéia que eu tinha era que os usuários dessem aos sistemas externos a capacidade de "marcar" usuários com um par de rótulo-valor e solicitar usuários com um determinado valor, por exemplo:

void AddUserTag(int userId, string tag, string value);
IEnumerable<User> GetUsersByTag(string tag, string value);

Em seguida, o sistema Projetos pode marcar cada usuário à medida que são adicionados ao projeto:

AddUserTag(userId,"project id", myProjectId.ToString());

e durante GetProjectUsers, ele poderia solicitar todos os usuários do projeto em uma única chamada:

var projectUsers = usersService.GetUsersByTag("project id", myProjectId.ToString());

a parte que eu não tenho certeza disso é: sim, os usuários são independentes de projetos, mas, na verdade, as informações sobre a participação no projeto são armazenadas no sistema de usuários, não em projetos. Eu simplesmente não me sinto natural, então estou tentando determinar se há uma grande desvantagem aqui que estou perdendo.

Respostas:


10

O que está faltando no seu sistema é o cache.

Você diz:

No entanto, isso requer muitas GetUserchamadas separadas , causando muitas consultas sql separadas dentro do Usersubsistema.

O número de chamadas para um método não precisa ser o mesmo que o número de consultas SQL. Você obtém as informações sobre o usuário uma vez. Por que você consultaria as mesmas informações novamente se elas não foram alteradas? Muito provavelmente, você pode até armazenar em cache todos os usuários na memória, o que resultaria em zero consultas SQL (a menos que um usuário seja alterado).

Por outro lado, ao fazer com que o Projectssubsistema consulte os projetos e os usuários com um INNER JOIN, você apresenta um problema adicional: você está consultando a mesma informação em dois locais diferentes do código, dificultando a invalidação do cache. Como consequência:

  • Você não introduzirá o cache posteriormente,

  • Ou você passará semanas ou meses estudando o que deve ser invalidado quando uma informação é alterada,

  • Ou você adicionará a invalidação do cache em locais simples, esquecendo os outros e resultando em bugs difíceis de encontrar.


Relendo sua pergunta, noto uma palavra-chave que perdi na primeira vez: escalabilidade . Como regra geral, você pode seguir o próximo padrão:

  1. Pergunte a si mesmo se o sistema está lento (ou seja, viola um requisito não funcional de desempenho ou é simplesmente um pesadelo para usar).

    Se o sistema não estiver lento, não se preocupe com o desempenho. Preocupe-se com código limpo, legibilidade, manutenção, teste, cobertura de filial, design limpo, documentação detalhada e fácil de entender, bons comentários sobre o código.

  2. Se sim, procure o gargalo. Você faz isso não adivinhando, mas criando um perfil . Ao criar um perfil, você determina a localização exata do gargalo (considerando que, quando você adivinha , pode quase sempre errar) e agora pode se concentrar nessa parte do código.

  3. Uma vez encontrado o gargalo, procure soluções. Você faz isso adivinhando, comparando, criando perfis, escrevendo alternativas, entendendo as otimizações do compilador, entendendo as otimizações que você decide, fazendo perguntas sobre o estouro de pilha e passando para idiomas de baixo nível (incluindo Assembler, quando necessário).

Qual é o problema real do Projectssubsistema solicitando informações ao Userssubsistema?

O eventual problema futuro de escalabilidade? Isto não é um problema. A escalabilidade pode se tornar um pesadelo se você começar a mesclar tudo em uma solução monolítica ou procurar os mesmos dados em vários locais (como explicado abaixo, devido à dificuldade de introduzir o cache).

Se houver um problema de desempenho perceptível, na etapa 2, procure o gargalo.

Se parece que, de fato, o gargalo existe e se deve ao fato de Projectssolicitações de usuários através do Userssubsistema (e está situado no nível de consulta ao banco de dados), somente então você deve procurar uma alternativa.

A alternativa mais comum seria implementar o cache, reduzindo drasticamente o número de consultas. Se você estiver em uma situação em que o cache não ajuda, a criação de novos perfis pode mostrar que você precisa reduzir o número de consultas, adicionar (ou remover) índices de banco de dados, lançar mais hardware ou reprojetar completamente todo o sistema. .


A menos que eu esteja entendendo mal você, você está dizendo "mantenha as chamadas individuais de GetUser, mas use o cache para evitar ida e volta ao banco de dados".
Eren Ersönmez

@ ErenErsönmez:, em GetUservez de consultar o banco de dados, procurará no cache. Isso significa que, na verdade, não importa quantas vezes você chamará GetUser, pois carregará dados da memória em vez do banco de dados (a menos que o cache tenha sido invalidado).
Arseni Mourzenko

essa é uma boa sugestão, já que não fiz um bom trabalho destacando o principal problema, que é "livrar-se das chatsidades sem mesclar sistemas em um único sistema". Meu exemplo de usuários e projetos naturalmente o levaria a acreditar que há um número relativamente pequeno de usuários que raramente muda. Talvez um exemplo melhor tivesse sido Documentos e Projetos. Imagine que você tem alguns milhões de documentos, milhares sendo adicionados todos os dias e o sistema Project usa o sistema Document para armazenar seus documentos. Você ainda recomendaria o armazenamento em cache então? Provavelmente não, certo?
Eren Ersönmez

@ ErenErsönmez: quanto mais dados você tiver, mais o cache crítico será exibido. Como regra geral, compare o número de leituras com o número de gravações. Se "milhares" de documentos forem adicionados por dia e houver milhões de selectconsultas por dia, é melhor usar o cache. Por outro lado, se você estiver adicionando bilhões de entidades a um banco de dados, mas obtiver apenas alguns milhares de selects com s muito seletivos where, o armazenamento em cache poderá não ser tão útil.
Arseni Mourzenko

você provavelmente está certo - provavelmente estou tentando corrigir um problema que ainda não tenho. Provavelmente implementarei como está e tentarei melhorar mais tarde, se necessário. Se o armazenamento em cache não for apropriado, porque, por exemplo, é provável que as entidades sejam lidas apenas 1-2 vezes após serem adicionadas, você acha que a possível solução que eu anexei à pergunta pode funcionar? Você vê um grande problema com isso?
Eren Ersönmez
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.