Recentemente, um aluno questionou no nosso fórum interno sobre abordagens como Database-server-per-service, Database-per-service, Schema-per-service e Private-tables-per-service no contexto de microsserviços.
Esse é um tema recorrente quando o assunto é microsserviços e não há resposta satisfatória.
A resposta para essa pergunta sempre será uma pedra no calcanhar.
Ao primeiro olhar, parece exagero, um tipo de purismo desnecessário. Principalmente por inviabilizar algumas facilidades presentes em monolitos que compartilham seus objetos (tabelas e views) de banco e fazem integração entre serviços via esses objetos.
Sim, esse é um tema espinhoso, difícil de engolir e por isso vamos abordá-lo hoje.
No nosso discord privado do Cloud Native .NET e Mensageria .NET temos um fórum para os alunos e eventualmente surgem algumas perguntas memoráveis.
Essa foi uma delas do Rodrigo:
Rodrigo Alves de Oliveira
Então, vamos à resposta:
A gente não pode olhar para essa demanda de isolamento sem entender seu motivo. O argumento central ou os argumentos centrais que produzem essa demanda.
Sem isso, podemos sabotar a estratégia.
O isolamento dos objetos de banco de um microsserviço se dá para que, somente o próprio microsserviço produza dependência com esses objetos. Ou seja, somente o próprio microsserviço sabe quais são as tabelas, colunas e relacionamentos.
O motivo para isso, é assegurar que o microsserviço poderá MUDAR essas estruturas para atender novas demandas. Sem gerar impacto a ninguém. Basta manter o contrato nas API’s lá na camada mais alta, que todo mundo que depende dele, não saberá e não precisará saber se e caso uma coluna nova for adicionada, ou uma coluna virar uma tabela inteira nova.
Quando movemos o nível da dependência para a API, temos a chance de fazer mudanças internas. Como fragmentar o banco em um banco relacional com dados complementares e históricos em bancos não relacionais.
Tudo se torna possível, e o mais importante é, antes de mais nada, evitar os puxadinhos que miram em “não fazer o que precisava ser feito para não impactar outros serviços que dependem das minhas tabelas.“
Sabe a tal independência do Microsserviço?
A capacidade de ter um roadmap próprio, sem afetar outros microsserviços?
A capacidade de reestruturar para atender novas demandas sem impactar suas dependências?
É sobre isso! É sobre o alicerce, sobre o pilar central da necessidade de microsserviços:
Entrega de negócio rápida independente dos demais serviços.
Se você entender isso, aceitar isso, pode rasgar uma pá de livros e deletar dos favoritos mais de 90% dos conteúdos em texto e vídeo sobre microsserviços.
Agora, com esse isolamento, você tem a chance de fazer o certo.
Aí você pode dizer: mas fulano depende desse dado, nessa estrutura. Ok, crie uma API compatível com o formato antigo. Dificilmente o dado deixou de existir, ele geralmente muda de estrutura interna de armazenamento. Mas não se perde.
Esse isolamento permite que quem estiver evoluindo o microsserviço possa pensar em fazer o correto e necessário em vez de fazer o que dá para ser feito, dada as dependências de baixo nível.
Em última análise, tem relação com evitar fazer gambiarra. Evitar mudanças difíceis, só porque alguém depende de algo que é interno ao meu microsserviço.
Dito isso, não importa como você faça:
- Desde que sejam connectionstrings diferentes, uma para cada microsserviço.
- Desde que seja fisicamente impossível (no ambiente de desenvolvimento local) criar um simples join entre tabelas de 2 microsserviços,
- Desde que seja impossível fazer uma única transação que englobe operações em 2 microsserviços.
Se você consegue usar um só database, com um só schema, mas consegue via usuários e permissões de banco, não deixar o dev fazer sequer um JOIN que ultrapasse os limites do próprio microsserviço, já atende!
Eu particularmente não gosto dessa abordagem e desaconselho uma abordagem tão frágil.
Considero essa abordagem algo como amarrar cachorro com linguiça. Na máquina do dev, com poderes ilimitados, se o dev for minimamente descolado, se ele pode, ele vai criar um terceiro usuário na máquina dele, só para poder fazer esse join, só para ficar mais fácil.
Porque, na cabeça dele, é “incoerente” não poder fazer esse join se as tabelas que ele precisa estiverem lado-a-lado, no mesmo schema, ou no mesmo database.
Então, minha estratégia se pauta em ISOLAR no mínimo por database (na mesma instância de servidor) em DEV. Isso me assegura que esses joins não sejam sequer possíveis tecnicamente (no postgres) https://wiki.postgresql.org/wiki/FAQ#How_do_I_perform_queries_using_multiple_databases.3FFAQ.
Assim, se eu consigo assegurar que em dev, ele respeitou esse isolamento, eu tenho todos os patterns disponíveis em produção e usar qualquer um deles vira uma decisão de deploy, de infra.
Se ele respeitar essa premissa de isolamento,
Podemos usar 1 só database com 1 só schema, (desde que não haja conflito).
Assim como podemos usar um schema por microsserviço.
Ou podemos usar databases por microsserviço.
e, em última análise, até instâncias de servidor por microsserviço.
E essa decisão vira uma decisão de infra.
Agora vamos falar de gosto. Na escala temos:
- Mesmo Server / Mesmo Database/ Mesmo Schema
- Mesmo Server / Mesmo Database/ Schema diferente
Mesmo Server / Database Diferente
- Server Diferente
Olhando para o postgres, sql server e mysql tendo a optar por Database-per-service.
Um único servidor postgres com vários databases, onde cada database tem um único dono, pertence a um único microsserviço. E malandramente, com usuários de banco diferentes com senhas diferentes para produzir connectionstrings diferentes, de forma que não seja possível ferir o isolamento.
Sigo a regra de que: O certo tem de ser fácil, e o errado tem de ser fisicamente impossível.
B) A pergunta que você precisa se fazer para saber se tem um microsserviço mesmo é:
Se mantivermos os contratos das API’s (endpoints e estruturas de dados, eventos e comandos) ainda assim consigo evoluir meu microsserviço (criando novas tabelas e mudando as anteriores) sem quebrar ninguém?
Porque no final do dia é isso que importa.
Assegurar que se uma gambiarra for feita, se um puxadinho for feito visando não atrapalhar as dependências é por um erro individual e pessoal de uma pessoa e não por uma situação mais ampla em que a arquitetura empurrou o dev. No final do dia é sobre fazer o que precisa ser feito, em vez de fazer o que dá porque há dependências.
Todas as estratégias que isolem essas mudanças pelo aspecto funcional são válidas.
Mas tem questões práticas que precisamos nos atentar.
Se você usa migrations, lembre-se que Migrations possui a necessidade de uma tabela de controle de versão. Portanto, Private-tables-per-service vai exigir configuração adicional para criar múltiplas tabelas de controle no mesmo schema de banco, uma para cada microsserviço.
Private-tables-per-service inevitavelmente fisicamente permite que faça um join que atravesse múltiplos microsserviços. Aqui temos um problema adicional. Se você implementar uma política de dogfooding onde os devs partilham do dia-a-dia de produção provando da própria comida de cachorro, se ele usa esse tipo de JOIN para validações, ele vai atrasar o máximo possível para realizar uma eventual fragmentação em múltiplos data stores diferentes.
Só quando for impossível lidar com essas estruturas em um só banco, ele vai cogitar essa mudança. Enquanto isso, será o cara que defenderá com unhas e dentes que a abordagem é desnecessária. Principalmente por tirar dele a simplicidade do troubleshooting simplificado.
Ou seja, se você deixar que alguém possa se acomodar com um viés monolítico, esse comodismo pode custar caro.
Então eu nunca deixaria uma oportunidade de fazer esse JOIN fisicamente, porque quando você está distraído, quando as coisas estão funcionando de boa. Quando você está confortável e feliz com o resultado, é quando você baixa a guarda e reduz a tensão, e é quando tudo desmorona porque alguém tomou uma decisão estúpida. Eu tomaria necessariamente a decisão mais barata que inviabilizasse esse acoplamento.
Então, eu tendo a usar database dentro do mesmo servidor. Essa é a opção que me permite mover com facilidade em caso de crescimento. E gera independência para inclusive ter parâmetros diferentes de database para database. O que me permite ter um controle bem interessante. Mesmo que use um mesmo database server.
Respondido?
(fiz algumas adaptações na escrita)
0 comentários