Tenho apreciado a diversidade da arquitetura há 20 anos e quero compartilhar minhas ideias





A princípio eu queria escrever um comentário sobre o artigo " Sofri com arquiteturas terríveis em C # por dez anos ... ", mas percebi duas coisas:



  1. Existem muitos pensamentos para compartilhar.
  2. Para tal volume, o formato do comentário é inconveniente para a escrita ou para a leitura.
  3. Há muito tempo que leio o Habr, às vezes comento, mas nunca escrevi artigos.
  4. Não sou bom em listas numeradas.


Aviso: não estou criticando @pnovikov ou sua ideia em geral. O texto é de alta qualidade (sinto-me um editor experiente), partilho algumas das minhas reflexões. Existem muitas arquiteturas, mas tudo bem (sim, parece o título de um filme coreano). 



No entanto, vamos em ordem. Primeiro, minha opinião sobre o que afeta a arquitetura, depois sobre os pontos polêmicos do artigo sobre "consertar arquiteturas". Também direi o que funciona bem para nós - talvez seja útil para alguém.



E, claro, tudo o que é dito aqui é opinião pessoal baseada em minha experiência e preconceitos cognitivos.



Sobre minha opinião



Costumo tomar decisões arquitetônicas. Uma vez grande, uma vez pequena. Ocasionalmente, eu venho com uma arquitetura do zero. Bem, como se do zero - com certeza tudo foi inventado antes de nós, mas não sabemos de algo, então temos que inventar. E não por amor à construção de bicicletas (digamos, não apenas por amor a ela), mas porque para algumas tarefas não havia uma solução pronta que atendesse a todos os parâmetros.



Por que eu acredito que arquiteturas completamente diferentes têm o direito de existir? Pode-se especular que programar é uma arte, não um ofício, mas não vou. Minha opinião: uma vez uma arte, uma vez um artesanato. Não é sobre isso. O principal é que as tarefas são diferentes. E pessoas. Para esclarecer, as tarefas são requisitos de negócios.



Se algum dia minhas tarefas se tornarem do mesmo tipo, escreverei ou pedirei a alguém que escreva uma rede neural (ou talvez um script seja o suficiente) que me substituirá. E eu mesmo farei algo menos sombrio. Até que meu e, espero, seu apocalipse pessoal não chegue, vamos pensar sobre como as tarefas e outras condições afetam a variedade de arquiteturas. TL&DR; - variado .



Desempenho versus escalabilidade



Este é talvez o motivo mais correto para alterar a arquitetura. A menos, é claro, que seja mais fácil adaptar a arquitetura antiga aos novos requisitos. Mas aqui é difícil dizer brevemente algo útil.



Cronometragem



Digamos que os termos (verifiquei duas vezes que escrevi com um "o") são muito restritos. Então não temos tempo para escolher, muito menos criar uma arquitetura - pegue ferramentas familiares e cave. Mas há uma nuance - às vezes, projetos complexos podem ser feitos no prazo apenas aplicando (e, talvez, inventando) algo fundamentalmente novo. Alguém pode dizer que convidar um cliente para uma casa de banho é uma técnica antiga, mas estou falando sobre arquitetura agora ...



Quando o momento é confortável - muitas vezes acaba sendo uma situação paradoxal - parece que você pode inventar algo novo, mas por quê? É verdade que muitos sucumbem à tentação de assumir outro projeto mais urgente e reduzir a situação ao anterior.



Na minha prática, o tempo raramente leva a revoluções na arquitetura, mas acontece. E isso é ótimo.



Velocidade e qualidade de desenvolvimento



Acontece que - a equipe (ou alguém da gerência) percebe que a velocidade de desenvolvimento diminuiu ou que muitos bugs ocorreram na iteração. Freqüentemente, a "arquitetura errada" é culpada por isso. Às vezes - merecidamente. Mais frequentemente - apenas como o acusado mais conveniente (especialmente se a equipe não tiver seu “pai”).



Em princípio, em alguns casos, tudo se resume ao fator tempo. E em outros - a sustentabilidade, sobre isso mais tarde.



Capacidade de Manutenção



Um tópico ambíguo. Porque tudo é muito subjetivo e depende muito do quê. Por exemplo - desde a equipe, linguagem de programação, processos na empresa, o número de adaptações para diferentes clientes. Vamos falar sobre o último fator, que me parece o mais interessante.



Agora você fez um projeto personalizado. Com sucesso, dentro do prazo e do orçamento, o cliente está satisfeito com tudo. Eu também tive isso. Agora você olha o que você usou e pensa - então aqui está - uma mina de ouro! Agora estamos usando todos esses desenvolvimentos, vamos criar rapidamente um produto B2B e ... No início está tudo bem. O produto foi feito, vendido algumas vezes. Contratei mais fornecedores e desenvolvedores ("mais ouro necessário"). Os clientes ficam satisfeitos, pagam pelo suporte, novas vendas acontecem ...



E então um dos clientes diz em uma voz humana - "Eu teria feito isso de uma maneira completamente diferente - quanto pode custar?" Bem, apenas pense - coloque alguns if'chiks com código diferente (digamos, não houve tempo para ferrar o DI), o que de ruim pode acontecer?



E na primeira vez, realmente nada de ruim vai acontecer. Eu nem mesmo aconselharia em tal situação a cercar algo especial. A complicação prematura da arquitetura é semelhante à otimização prematura. Mas quando isso acontece pela segunda e terceira vez, esse é um motivo para lembrar coisas como DI, o padrão de "estratégia", Alternância de recursos e outros semelhantes. E, por um tempo, vai ajudar.



E então chega o dia em que você olha para as configurações do projeto (apenas algumas centenas de opções) para uma bancada de teste específica ... Lembre-se de como contar o número de combinações e pense - como, sua mãe, isso pode ser testado? É claro que em um mundo ideal isso é simples - afinal, cada recurso é projetado e implementado de forma que não afeta o outro de forma alguma e, se afetar, tudo isso está previsto e, em geral, nossos desenvolvedores nunca se enganaram.



Claro, engrossei as cores - você pode destacar alguns conjuntos de recursos que são usados ​​por clientes reais, escrever mais testes (como e quais são um assunto para outra conversa) e simplificar um pouco a tarefa. Mas pense nisso - cada versão principal precisa ser testada para todos os clientes. Deixe-me lembrá-lo de que não se trata de B2C, onde você pode dizer “lançar um recurso para 5% dos usuários e coletar feedback” - para B2B, você pode começar a coletar feedback dos tribunais ...



Soluções? Por exemplo, divida o produto em módulos com um ciclo de vida separado (não esquecendo de testar sua interação). Isso reduzirá a complexidade da manutenção, embora complique o desenvolvimento. E agora eu não estou falando sobre o tópico fértil para holívares “monólito vs. microsserviços "- em um monólito, você também pode organizar um semelhante (embora mais complicado, na minha opinião).



E, veja bem, do ponto de vista pragmático, em todas as fases, tínhamos uma boa arquitetura.



E para que serve tudo isso?



Não quero cansar você (e a mim mesmo) listando outras razões para mudanças na arquitetura. Agora vamos concordar que as arquiteturas tendem a mudar com o tempo, dependendo de muitos fatores. Isso significa: a arquitetura ideal que resolve "bem, todos os problemas" não existe.



Se eu ainda não o convenci disso, olhe para a variedade de linguagens de programação e frameworks (mas não no front-end - não abra este tópico). Se alguém disser que isso é ruim, sugiro fazer um experimento mental - imagine um mundo em que exista uma linguagem de programação específica. Com uma condição importante - você não gosta. Por exemplo, porque você nunca o usou e nunca teve a intenção de usá-lo.



E, eu admito, há outra boa razão - inventar algo novo, otimizar alguns parâmetros, brincar com compromissos - é muito divertido. Agora que todos nós (certo?) Concordamos que a diversidade na arquitetura está bem ...



Discussão do artigo sobre "consertar arquiteturas"



E quanto ao IoC?



Sobre IoC, concordo que os footcloths têm um lugar no exército e os módulos são universalmente bons. Mas aqui está o resto ...



Se, é claro, você ouvir alguns apologistas do "código limpo", então você pode codificar uma montanha de serviços, cada um dos quais terá uma média de um método e meio, e no método - duas linhas e meia. Mas por que? Honestamente, você definitivamente deseja seguir os princípios que o ajudarão a lidar com problemas improváveis ​​em um futuro distante, mas espalhar até mesmo a lógica simples em dezenas de arquivos? Ou é o suficiente para você escrever um código que funcione decentemente agora? 



A propósito, agora estou trabalhando em um módulo que definitivamente será usado em diferentes produtos e, provavelmente, ativamente "ajustará". Então aí eu tento não ser "superficial". Não compensa. Embora nele eu use a única implementação de interfaces com mais frequência do que o normal.



Portanto, se temos módulos e não somos "mesquinhos", de onde vêm os problemas de desempenho de IoC ou "coberturas de configurações de IoC" sem suporte? Eu não encontrei. 



No entanto, vou esclarecer nossas condições de trabalho:



  • Nossos módulos não são aqueles que "são fornecidos por quase qualquer estrutura IoC", mas "módulos diretos" - que se comunicam entre si remotamente por meio da API (às vezes, por motivos de desempenho, você pode colocá-los em um processo, mas o esquema de trabalho não mudará).

  • IoC é usado da forma mais simples e simples possível - as dependências estão presas aos parâmetros do construtor.

  • Sim, agora temos uma arquitetura de microsserviço, mas aqui tentamos não ser muito pequenos. 



Dica: as interfaces podem ser mantidas no mesmo arquivo da classe - é conveniente (se, é claro, você usar um IDE normal e não o bloco de notas). Eu faço exceções quando as interfaces (ou comentários a elas) crescem. Mas tudo isso é bom gosto, é claro.



O que há de errado com o ORM e por que acesso direto ao banco de dados? 



Sim, eu mesmo direi o que está errado - muitos deles estão muito longe do SQL. Mas nem todos. Portanto, em vez de “resistir ao O / RM excluindo 3000 objetos” ou inventar outro, encontre um que seja adequado para você.



Dica: experimente LINQ to DB . É bem equilibrado, existem métodos Atualizar / Excluir para várias linhas. Basta ter cuidado - viciante. Sim, não há recursos da EF e um conceito um pouco diferente, mas gostei muito mais da EF.



A propósito, é bom que este seja um desenvolvimento dos nossos compatriotas. Igor Tkachev - respeito (não achei no Habré).



UPD: RouRobservou nos comentários que há uma extensão para EF Core que permite operações em massa. Não vou desistir do LINQ to DB de qualquer maneira, porque é bom .



O que há de errado com os testes de banco de dados?



Sim, eles serão mais lentos do que os dados na memória. É fatal? Não, claro que não. Como resolver este problema? Aqui estão duas receitas que devem ser usadas ao mesmo tempo.



Receita número 1. Você pega um desenvolvedor legal que adora fazer todo tipo de coisa legal e discute com ele como resolver esse problema lindamente. Tenho sorte porqueforçaresolveu o problema mais rápido do que parecia (nem me lembro se discutimos isso ou não). Como? Fiz (em um dia, ao que parece) uma fábrica de testes para ORM, que substitui o subconjunto principal de operações por arrays de acesso.



Perfeito para testes de unidade simples. Uma opção alternativa é usar SQLite ou algo semelhante em vez de bancos de dados "grandes".

Comentário por força: . -, , ORM, , SQL . -, , , , , .. . .



Receita número 2. Prefiro testar cenários de negócios em bancos de dados reais. E se o projeto declarar a capacidade de oferecer suporte a vários DBMS, testes serão realizados para vários DBMS. Por quê? É simples. Na afirmação "Não quero testar o servidor de banco de dados", infelizmente, há uma substituição de conceitos. Eu não estou, você sabe, testando se juntar obras ou solicitar por.



Estou testando meu código trabalhando com DB. E sabendo que mesmo versões diferentes do mesmo SGBD podem produzir resultados diferentes nas mesmas consultas ( prova ), quero verificar os scripts principais desses bancos de dados com os quais este código funcionará.



Normalmente, esses testes para mim são assim:



  • Para um grupo de testes (Fixture) ele é gerado do zero de acordo com os metadados do banco de dados. Se necessário, os livros de referência necessários são preenchidos.

  • Cada script adiciona os dados necessários durante a passagem (os usuários também fazem isso). Não é assim em testes de desempenho, mas essa é uma história completamente diferente ...

  • Após cada teste, os dados em excesso (exceto para livros de referência) são excluídos.



Conselho: se esses testes são realizados para você objetivamente por um longo tempo (e não porque é hora de otimizar as consultas ao banco de dados), faça um build que irá executá-los com menos frequência (categorias de teste ou um projeto separado para ajudar). Caso contrário, os desenvolvedores não vão querer executar o resto deles - testes rápidos.



Transação e email



Vou apenas acrescentar à história "a transação no banco de dados por algum motivo caiu e o e-mail foi embora". E que divertido será quando uma transação esperar por um servidor de e-mail indisponível, comprometer todo o sistema por causa de alguma notificação que o usuário então envia para a cesta sem ler ... É



verdade, eu sempre acreditei que apenas junho na selva enviava cartas em uma transação na ausência de uma revisão. Em nossa equipe, por tal candelabro eles venceram (até agora virtual).



Resultado



Em geral, se @pnovikov não tem planos de dominar o mundo com a ajuda da única verdadeira ideologia da arquitetura, não encontrei nenhuma outra diferença digna de menção. Para algumas tarefas, é claro, os princípios que ele expressou são adequados. Terei todo o gosto em ler os artigos e comentários seguintes, talvez encontre algumas ideias úteis para mim.



Dificilmente usarei a estrutura proposta. O motivo é simples - já temos uma arquitetura ideal ...



PS Se você tem vontade de discutir algo nos comentários, terei o maior prazer em participar.



All Articles