PHP Conference 2015: Construindo e mantendo aplicações multi-tenant (multi-cliente)

939 visualizações

Publicada em

O multi-tenancy é uma evolução natural para boa parte das aplicações. Startups e seus produtos precisam dele para seu modelo SaaS, empresas e seus sistemas o querem para o reuso de código. Essa palestra irá mostrar técnicas e modelos para a aplicação de multi-tenancy, e erros comuns no processo.

Publicada em: Tecnologia
0 comentários
6 gostaram
Estatísticas
Notas
  • Seja o primeiro a comentar

Sem downloads
Visualizações
Visualizações totais
939
No SlideShare
0
A partir de incorporações
0
Número de incorporações
6
Ações
Compartilhamentos
0
Downloads
22
Comentários
0
Gostaram
6
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide
  • Boa tarde, pessoal! Vamos começar? Primeiramente eu gostaria de agradecer a presença de todos vocês, e o pessoal da Tempo Real que está organizando esse evento. A PHPConf tá completando 10 anos, e é realmente notável a diferença que o engajamento da comunidade tem feito na qualidade e experiência dos desenvolvedores no Brasil. Como alguém que está sempre entrevistando e contratando gente, eu vi o nível da galera subir muito nos últimos anos, e atribuo muito disso ao engajamento e espírito de comunidade da galera do PHP. É isso aí, continuem vindo nos eventos, aprendam, repassem o que aprenderam, voltem como palestrantes, ajudem a disseminar o conhecimento que todos saímos ganhando. O mercado é gigantesco, nenhum dev bom fica sem emprego, então a gente tem motivo de sobra pra ajudar todo mundo.
  • Vou falar bem rapidamente de mim. Meu nome é Aryel Tupinambá, sou fundador e CTO da Liquidi, uma agência com bastante foco em tecnologia. Esse aí na foto sou eu, fingindo ser um cara sério e normal, mas é tudo fachada! A gente aqui sabe que não existe desenvolvedor que seja normal, né? Eu trabalho com PHP a mais ou menos 12 anos, desde aquela época em que o PHPClasses era a onda :)
  • Desde aquela época, até começar essa onda de startups e software como servico, esse aqui era o padrão de projetos web. O software tinha um único cliente, que era instalado no servidor do cliente, ou em algum servidor compartilhado, e rodava lá sozinho. Esse é um modelo que chamamos de "single tenant", ou "um único cliente". A gente compara esse modelo com uma casa, solitária e ocupando todo o terreno sozinha.
  • Só que aí, quando tudo dava certo e sua equipe comercial começava a bombar, o mesmo projeto que você fez pra um cliente, você começa a fazer pra outro, e mais outro, e mais outro. Seu projeto virou um produto, com vários clientes diferentes, mas sua estrutura continua a mesma. Pra cada cliente você tem uma estrutura diferente, com um código que pode até ser igual, mas que está replicado integralmente em cada um dos clientes. Aqui, a gente compara o modelo com um bairro. Tem várias casas, com vários clientes, mas tá todo mundo em seu terreninho, isolado do resto, cuidando do próprio umbigo. E todo esse isolamento, toda essa independência gera uma série de problemas.
  • Preciso fazer uma atualização de segurança, ou preciso corrigir um bug. E agora? Vou sair copiando e colando a atualização na pastinha de cada um dos clientes? Vou entrando no FTP, no SSH, cliente por cliente, e atualizando. E se eu customizei a instalação desse produto pra alguém? E se eu quiser pegar a customização de um e replicar pra outro, que tem outra customização? E esse espaço todo que essa galera tá ocupando? E o mais importante pro negócio, e o custo que tudo isso gera? Se eu tenho um custo que aumenta tão rápido conforme eu ganho novos clientes, como que o negócio vai crescer?
  • Aí que entra a arquitetura multi-tenant. Tenant é a palavra em inglês para inquilino. O conceito primário dela é que a gente usa uma única aplicação para vários clientes, e que esses clientes compartilham de uma estrutura comum. É igual a um prédio ou um condomínio, por que tá todo mundo compartilhando do mesmo terreno, o mesmo prédio. Daí que veio o termo "multi-inquilino". As benfeitorias servem pra todos, e o produto base é igual pra todos. E, igual a um apartamento, nada impede de um inquilino fazer uma "reforminha" no apê dele; ele vai estar com o mesmo apê que os outros, mas com uma ou outra coisa diferente. Parece ótimo né? Os olhinhos até brilham, tanto pros devs quanto pra empresa.
  • Mas naturalmente, uma arquitetura dessas traz vários desafios na hora de desenhar a aplicação. Eu separei ele em 3 pilares: data storage, que seria banco de dados e arquivos de cliente; codebase, que seria o código do aplicativo em si, back-end e front-end; e customização. E aí ficamos com algumas perguntas: no data storage, como que a gente segmenta a base de dados pra cada cliente? E os arquivos enviados? No code base, como fica o código fonte e as customizações? Como que a gente lida com updates e features novas? Na customização, como que a gente vai customizar? Como que a gente atualiza o produto base sem quebrar as customizações, ou vice versa?
  • Primeira coisa: não existe resposta certa, nem resposta errada. Pra cada uma dessas perguntas existe uma infinidade de respostas, assim como praticamente qualquer decisão na área de engenharia. Eu vou compartilhar aqui um pouco da minha experiência com projetos desse tipo, lá na Liquidi e em outras empresas que eu trabalhei e dei consultoria. Pra algumas pessoas, pode ser que a palestra fique um pouco superficial, por que o assunto é bem extenso e eu optei por falar mais sobre a arquitetura e menos sobre a implementação. O objetivo aqui é que vocês levem esses conceitos pra casa pra estudar com mais detalhe, testar, experimentar e pesquisar mais, e ver onde e como aplicar isso nos projetos de vocês, okay?
  • Vamos lá, data storage.
  • A gente tem 3 formatos mais comuns de segmentar um banco de dados no modelo multi-tenant, indo de um modelo mais compartilhado e unificado, e indo para um modelo mais isolado e independente. Vamos ver de baixo pra cima.
  • Banco de dados compartilhado, basicamente o sistema roda todo em cima de um único banco de dados, e a gente segmenta os clientes por uma coluna com o ID do cliente. Nesse caso aqui, tem a coluna TenantID que especifica qual o cliente.
  • Banco de dados separados, a gente cria um database pra cada cliente, dentro da mesma instância. O cliente é identificado pela database que ele está usando.
  • E por fim, instâncias completamente separadas, com seus dados isolados. Esse formato é um pouco incomum, por causa do overhead de usar uma instância única pra cada cliente, mas faz sentido quando você tem altos requisitos de segurança e compliance.

    Beleza, mas e agora, quando eu uso cada modelo?
  • A gente tem duas preocupações primárias na hora de segmentar o banco: custo de desenvolvimento e manutenção, e flexibilidade de extensão. Esse gráfico aqui mostra a progressão de custo sobre tempo das duas pontas do espectro. A gente vê aqui que o modelo Compartilhado tem um custo de desenvolvimento inicial maior, mas ganha no custo de manutenção futura. O modelo isolado é mais barato, mas encarece o processo de manutenção. A gente vai ver por que um pouco mais a frente. O custo por si só não deve ser o fator decisivo de um modelo ou outro.
  • Esse gráfico mostra a relevância de decisão entre os formatos. Ter um volume alto de clientes puxa a decisão mais próxima do modelo compartilhado, principalmente por motivos de custo. Puxando pro outro lado, a gente tem, primeiramente, o tamanho do banco por cliente e a quantidade de usuários por cliente. Isso por que escalar um banco de dados gigantesco e homogêneo vai se tornando um desafio progressivo conforme o volume por cliente vai aumentando. Quando você já sabe que esse volume vai ser alto, você já pode prever, num processo de sharding do banco por exemplo, que cada tenant ficaria em um shard. Naturalmente ficaria mais barato prever essa separação desde o início. Agora, o que eu considero o fator mais decisório é o último aqui da lista, que é o serviço agregado por cliente, ou seja, as customizações que um cliente vai ter na aplicação. Se você tá prevendo que seu produto vai ser um produto único, cujo comportamento nunca muda entre um ou outro cliente, o modelo compartilhado faz muito mais sentido. Se você está imaginando que alguns dos seus clientes vão querer acrescentar módulos ou alterar comportamentos dentro do seu produto, o modelo de isolamento é melhor, por que você fica livre pra alterar o modelo de dados, criar novas tabelas e colunas sem se preocupar em sujar os dados dos outros clientes que não tem essa mesma customização.
  • Então, sugestões de uso pra cada formato. O modelo compartilhado é ideal para aplicações em que pouca coisa, ou nada mesmo, vai mudar entre um cliente e outro. Esse modelo é o mais comum quando se fala de SaaS "para as massas", aquele tipo de aplicação que qualquer um assina num formulário web, coloca o número de cartão e sai usando. Geralmente essas aplicações tem um ticket médio baixo, e não faz muito sentido para o seu cliente customizar a experiência além daquilo que você previu desde o início.
  • O modelo de bancos separados é ideal pra quando você já está prevendo customizações e módulos adicionais. Aqui você fica livre para ter modelos de dados que variam entre clientes. Nesse modelo você consegue segmentar melhor também aqueles clientes que tem mais ou menos consumo, ou que tem níveis maiores ou menores. Você pode empacotar mais ou menos clientes em uma instância de banco dependendo do tier que ele contratou, ou mover clientes mais "barulhentos" para outras instâncias.
  • O modelo mais isolado, em que cada cliente tem uma instância isolada do banco, faz sentido quando você tem isso como pré-requisito de alguns clientes, como governos ou bancos. Aqui você consegue ter um controle bem maior da performance e do consumo de cada cliente.
  • Na regra geral, o que deve te motivar mais na decisão do modelo é a flexibilidade que você precisa para mexer no banco de dados. Fica muito difícil você customizar um software, colocar funcionalidades ou alterar fluxos quando o seu modelo de dados é praticamente intocável.

    Uma coisa muito importante, principalmente quando você tá indo para um modelo mais isolado, é usar o modelo de migrations e seeds para criar e manter o seu modelo de dados. As migrations funcionam como um controle de versão do banco, e é o que vai permitir que você crie customizações para clientes sem quebrar a aplicação base.

    Você não precisa ter um único modelo entre os 3, necessariamente. Dá pra fazer um híbrido entre um e outro, por exemplo, quando você tem interações entre clientes dentro da sua aplicação, mas em um cenário em que ela ainda é customizável. Nós tivemos esse cenário na LQDI na nossa ferramenta de gestão de projetos.
  • Lembrando de novo, gente, não tem resposta certa ou errada. Estude bem esses 3 modelos, veja o artigo que eu citei da Microsoft, avalie bem os requisitos do seu projeto e aí você consegue escolhar a mais adequada.
  • Na prática, como a gente poderia implementar essas divisões no código? Vou usar alguns exemplos usando Laravel, que é o framework que a gente mais usa na LQDI, e é super tranquilo de fazer. Outros frameworks e ORMs tem soluções parecidas, e você consegue usar esses componentes aqui apartados do Laravel se precisar.
  • No modelo compartilhado, a implementação mais fácil é criar uma query scope global, que condiciona as consultas baseadas no seu tenant ID. Aí você pode criar uma trait que aplica essa query scope global nos models que são multi-tenant. Nesse exemplo aqui, o Tenant ID vem de uma variável de ambiente, mas poderia vir de qualquer lugar; do subdomínio ou do domínio, de um campo na URL, de uma variável de sessão, e por aí vai.
  • No modelo isolado é ainda mais tranquilo: a gente cria duas conexões, sendo uma específica para os modelos multitenant, e aí usa essa conexão como padrão nos models que são multitenant. Na configuração daquele cliente, você tem tanto os dados do banco único ali em MASTER_DB, que tem os dados compartilhados, quanto os dados do cliente, ali em TENANT_DB.
  • Falando um pouco agora do codebase, do código fonte da aplicação.
  • A gente tem dois modelos mais comuns pra trabalhar: uma instância pra todo mundo, ou uma instância por cliente. Eu tô usando o termo instância aqui me referindo a instância do codebase, e não necessariamente uma instância de servidor. Dá pra notar que temos mais ou menos a mesma polarização que temos nos modelos de dados: uma opção mais compartilhada e uma opção mais isolada. Os pros e contras são basicamente os mesmos
  • No modelo de instância única, a gente tem algumas boas vantagens. As atualizações são sempre sincronizadas com todo mundo, já que todo mundo acessa o mesmo codebase. Também não tem muito housekeeping, muito trabalho de manutenção ou monitoramento pra cada cliente, uma vez que tá rodando tudo junto, e é um dos motivos pelo qual esse modelo fica mais barato ao longo do tempo.

    Agora, naturalmente, esse modelo não é muito flexível pra customizar a experiência por cliente. Ele é um modelo legal quando você tem módulos pré-definidos que são opcionais, e aí pra cada cliente você ativa ou desativa os módulos relevantes. Uma customização mais específica para um cliente fica mais difícil.

    Esse modelo também é bem fácil de escalar, principalmente se o padrão de consumo dos clientes é o mesmo. Dá pra colocar ele em um serviço Platform as a Service, tipo o Azure Websites ou o Amazon Elastic Beanstalk, e escalar ele só arrastando um slider no painel.
  • Na prática, o formato que a gente usa pra implementar esse modelo: o repositório de código é único, sincronizado via git. Cada cliente tem o seu config, que pode vir do banco mestre ou de um microservice e pode ficar cacheado na sessão, e aí a gente pode criar um site no nginx pra cada cliente, todos apontando para o mesmo lugar, que é o repositório do app. Sua aplicação fica responsável por rotear o domínio pro cliente correto, pegar os dados desse cliente e carregar a configuração dele.
  • O outro modelo é ter uma instância por cliente. A idéia aqui é que cada cliente tem sua pastinha separada, tendo exatamente o mesmo código fonte. Aqui a configuração dele pode estar na própria pasta, e essa pasta que vai conter qualquer código de customização específica de um cliente. As atualizações de código aqui não são sincronizadas por padrão, mas você pode, e deve, automatizar isso. Você acaba tendo um certo trabalho de housekeeping, já que cada cliente tem uma pasta com arquivos separados, e qualquer problema durante o deploy, execução ou instalação de customização pode exigir uma intervenção mais manual. O legal dele é que você fica livre pra tratar cada cliente de forma excepcional: fazer customizações, colocar e tirar funcionalidades, travar um cliente em uma versão do app ou colocá-lo pra rodar uma versão beta. Se em algum momento isso fizer sentido, você pode até criar um fork da sua aplicação pra atender um único cliente, como por exemplo em um projeto customizado pra um grande cliente.
  • Na prática, a gente pra cada pastinha um repositório git, sincronizado via um repositório central tipo Github ou Bitbucket. A configuração de cada ambiente fica mais fácil, podendo ser feita por exemplo em um arquivo DotEnv na raiz de cada cliente. E o código customizado entra, quando aplicável, individualmente na pasta do cliente
  • Nesse modelo é o mesmo esquema, cada cliente tem um site no Nginx, e aqui a gente pode até ter um pool separado do PHP-FPM por cliente, ganhando um certo nível de controle sobre a performance e carga de cada cliente em cima do servidor da aplicação.
  • Então, pro codebase o fator decisório principal também é a flexibilidade, até mais que no banco de dados. Você vai precisar de muita gambiarra pra ter o mesmo nível de flexibilidade do modelo isolado no modelo compartilhado, e dificilmente vai valer a pena.

    A escalabilidade é um desafio pra maioria dos projetos multitenant, mas merece uma palestra a parte. A escolha do modelo, desde que você automatize bem os processos e tenha uma arquitetura consistente, não deve te impedir ou dificultar de escalar a aplicação.

    Ter as instâncias isoladas te dá mais controle pra isolar clientes mais ou menos exigentes, e com mais ou menos volume. Igual ao caso do data storage, a gente pode segmentar as instâncias por tier ou plano do cliente, e colocar menos clientes por servidor nos tiers mais caros. Dá pra atingir o mesmo resultado com uma instância compartilhada, mas você tem um adicional de código e complexidade de ter que manter o controle de qual cliente está em qual servidor.
  • O terceiro desafio, e o mais, digamos, difícil de resolver, é o de Variabilidade.
  • É o real calcanhar de aquiles da arquitetura multitenant, e precisa de bastante atenção e estudo, pra não gerar uma dívida técnica desnecessária.

    É nesse tipo de arquitetura que a gente vê os design patterns, os princípios do SOLID, realmente brilharem e te ajudarem pra caramba.

    Vou falar aqui sobre algumas técnicas e formatos legais de trabalhar.
  • Lembrando de novo, não existe solução certa, ainda mais nesse quesito.
  • Pra configuração, nas instâncias de código isolado a gente pode ter um arquivo de configuração em cada instância. Eu sugiro aqui usar a biblioteca DotEnv, que permite você usar um arquivo tipo esse aqui, e carregar essas variáveis só chamando uma função no código. Esse arquivo fica no gitignore e você só gera ele quando for provisionar um novo cliente.

    Na instância única, você vai ter mais ou menos a mesma configuração, só que dentro da tabela de tenants no banco principal. É legal, nesse formato, fazer um cache dessa informação, ou usar um banco de dados in-memory como o Redis, pois você provavelmente vai precisar consultar os dados a cada requisição.
  • Pro data storage, ressalto a importância das migrations. Elas funcionam como um controle de versão do seu banco, onde a cada alteração você roda o script que cria novas tabelas ou altera as existentes, e ao fazer uma instalação do zero, todas as migrations são rodadas na ordem certa. É importante aqui sempre criar migrations que sejam reversíveis, problemas acontecem e você vai precisar voltar versões do modelo de dados ou desfazer customizações em alguns casos.

    A maioria dos frameworks modernos e ORM tem alguma forma de migration, e se não tiver, você pode usar as libs separadas do Laravel, que ficam no pacote Iluminate.
  • Pra código, pra customização de comportamento, a gente tem vários patterns legais de trabalhar.

    Events. Aconteceu algum evento ou transação relevante na sua aplicação base, você dispara um evento, e passa os parâmetros dessa transação junto. Com esse evento, quem estiver escutando recebe os dados e pode fazer o que for necessário. Nesse exemplo aqui: criei um pedido novo e disparei o evento. Um módulo customizado de um cliente específico, por exemplo, estava escutando e gerou uma nota fiscal eletrônica desse pedido. Pra outro cliente, esse evento pode registrar uma carga que vai ser rastreada na logística. O legal é que você pode usar esse padrão dentro da sua própria aplicação base. Você pode ter atividades que não são relacionadas necessariamente o domínio original da transação, como por exemplo mandar um e-mail de aviso pro usuário ou registrar a atividade do cara num log.
  • Aqui é o código dividido em core app, que é o código que todos os clientes tão rodando, e o custom code. O custom code não precisou mexer em nenhum código ou arquivo do core app pra poder tomar ações quando a pedido foi criado.
  • Hooks:
    são tipo eventos, só que tem um certo retorno
    o Wordpress usa bem esse modelo nos plugins dele
    um exemplo legal: na hora de construir o menu, você dispara um hook, e cada módulo ou serviço registrado responde com os ítens que quer adicionar ao menu.
    desse jeito, ninguém precisa mexer na view do core app pra customizar os ítens do menu
  • Injeção de dependência, uma coisa que eu sei que tem muita gente que ainda não sacou pra que serve, acho que agora vai ficar bem claro. Aqui nesse exemplo:
    O model de pedido da aplicação não sabe quem é o gateway de pagamento, mas ele sabe que precisa de UM gateway de pagamento
    então a gente estabelece um contrato, uma interface que determina o que um gateway de pagamento deve poder fazer
    O model de order pede pra um container "olha, me dá uma instância de alguém que implementa esse contrato", e esse container olha na configuração da aplicação e nos serviços e módulos registrados quem é a instância mais indicada.
    aqui ainda estamos no core app, e temos a implementação do gateway do PayPal. Então o container retorna pro model de pedido essa implementação, e o pagamento vai rolar via paypal
  • Aí você tem sua lojinha funcionando, e um belo dia um cliente fala pra vc que quer usar um gateway diferente. Como o model de pedido não tá lidando com a implementação do PayPal direta, e sim com o contrato, essa solicitação fica muito mais simples
  • A gente escreve uma nova implementação desse mesmo contrato, como por exemplo o PagSeguro.
    Depois, a gente fala pro container que a classe mais indicada pra lidar com esse contrato é a implementação do PagSeguro, e não do PayPal
    Pronto, trocamos de gateway, e não colocamos um dedo dentro do core, só mexemos na customização. A gente pode atualizar livremente o core app, sem quebrar o PagSeguro pra esse cliente, e nem o PayPal para os outros.

    O legal é que depois a gente pode pegar essa implementação do PagSeguro, e oferecer pra qualquer um dos clientes. O cliente quer, é só colocar o componente que registra essa implementação no container, e pronto.

    Geralmente containers de injeção de dependência, como o do Laravel, tem uma infinidade de controles e fine tuning. Você pode ser bem específico, por exemplo, sobre qual instância vai ser retornada caso a caso. Por exemplo, quando o model Pedido pedir um gateway, retornar PayPal. Quando a classe Assinatura pedir um gateway, retornar PagSeguro. O container cuida de decidir qual implementação vai ser usada.
  • Falando agora um pouco de frontend. Sempre que der, usem engines de template pro projeto. Dá pra facilitar bastante a customização. Esse aqui é um exemplo da engine Blade, que vem no Laravel. Olha só, no meu layout aqui eu defino algumas rotas por nome, e não por caminho absoluto, então eu posso sobrescrever essas rotas se eu quiser. Aí aqui eu uso especifico o output de algumas seções, sidebar e content. Esse aqui é o arquivo de layout.

    Aí eu posso criar um sublayout, que extende do layout principal, e que define algum conteúdo pra sidebar, por exemplo. Aí eu tenho uma página desse módulo, que usa o sublayout específico, e define uma outra seção. Reparem como fica limpo e fácil de visualizar e manter o HTML consistente, e de reutilizar bastante coisa, por exemplo, numa customização. Dá pra jogar uma série de layouts, componentes e sections na mão do programador front-end, e ele vai tocar muito mais tranquilamente o desenvolvimento, mexendo só no que precisa e reutilizando muita coisa.
  • Outra coisa importante pra incorporar no projeto é o Sass. Esse cara facilita muito a customização de layout e de visual entre um cliente e outro. Você define uma série de variávels que vão ser sobreescritas pelo cliente, escreve o estilo da aplicação e pronto. No seu processo de provisionamento, você compila o Sass e o CSS já sai pronto e customizado pro cliente. Dá pra você colocar tudo isso na mão do cliente, gerar o config via PHP carregando direto do banco e compilando o Sass. Tudo isso sem precisar sujar o HTML de estilos inlines que chamam variáveis carregadas de algum lugar, e que fica um inferno de administrar e manter depois.
  • Pra infra-estrutura e deploy, tem uma coisa que é primordial pra projetos multitenant
  • Automatizem tudo! De verdade, gente. Infra e deploy é o maior custo de overhead que um projeto desses vai acumular com o tempo, e quanto antes você automatizar esses processos, melhor. É importante já pensar nos processos automatizados na hora de planejar e desenhar a aplicação.
  • Pra deploy, tem várias ferramentas. Na LQDI a gente usa essas 3, e são ridiculamente fáceis de usar e configurar. O Envoyer é bem focado em deploy de PHP, o Forge é um automatizador de provisionamento, instalação, configuração, deploy de servidores feito pelo criador do Laravel, super completo. O DeployHQ é uma plataforma praticamente que universal de deploys, aceita diversos targets, faz deploy usando Git, FTP, Amazon S3, rsync e o que mais vc precisar, dá pra customizar bastante o processo, é a ferramenta mais completa de deploy que a gente já usou.
  • O Composer é melhor solução pra gerenciar e versionar tanto o seu core app quanto as customizações e módulos diferentes, principalmente no caso das instâncias isoladas. Você consegue criar repositórios privados usando uma ferramenta chamada Satis. Aí você gera um composer.json na hora de provisionar o cliente, e já especifica qual a versão do core code que você quer e quais módulos de customização. Pronto, os módulos viram packages e ficam isolados nas pastinhas dentro do vendor, e você evita a maioria dos problemas de housekeeping. Depois que você se acostuma a trabalhar desse jeito, fica fácil criar uma feature pra um cliente que pode ser revendida pra qualquer outro cliente.
  • Ainda na linha de automatizar tudo, o próprio processo de provisionamento é importante de ser automatizado. Provisionamento é algo que cabe bem dentro de um microservice, por exemplo, e economiza um tempo danado da equipe de desenvolvimento e dos sysadmins, e facilita a configuração e customização de alguns clientes. Aqui é um exemplo de um script de provisionamento: o cliente se cadastrou, a gente dá um trigger pro serviço de provisionamento. Esse serviço começa criando um registro do tenant no banco. Aí ele vai, cria a pastinha do cliente do servidor e já gera um arquivo de site pro Nginx. Cria o banco, gera o .env com a configuração dos bancos. Enquanto ele vai fazendo isso, ele vai atualizando o registro do tenant no banco; o usuário pode até acompanhar o processo de provisionamento em tempo real. Aí vai, gera o composer.json com os módulos selecionados, gera o config do Sass e compila o CSS, cadastra o tenant no Envoyer via API e dá o trigger no deploy. O envoyer vai clonar os arquivos do core, instalar as dependências do composer, rodar as migrations no banco e reiniciar o nginx quando terminar. No final, dispara um email pro cliente já com a URL do ambiente dele.
  • Só nas fases de customização, imagina o tempo que você não perderia fazendo isso manualmente. Criando arquivo, editando os campos, copiando e colando. Enquanto isso, seu cliente tá lá esperando. Se ele se inscreve direto do site, então, já era.
  • É importante essa automatização por que o processo de provisionamento interfere direto num KPI de negócio, que é o custo de aquisição de cliente. Se para cada novo cliente o negócio vai precisar que você aloque algumas horas configurando arquivos e mexendo em paineis dos serviços, esse custo sobe. É um custo que afeta diretamente a capacidade do negócio crescer, dificulta e encarece um modelo trial ou uma opção freemium, e por aí vai.

    Uma coisa importante pra lembrar na hora de pensar na automatização do provisionamento é que provavelmente, sua infra inteira tem APIs para serem administradas programaticamente. EC2 e DigitalOcean dá pra provisionar servidores virtuais via API. S3, Rackspace Cloud e Google Storage, dá pra criar buckets de arquivos via API. Route 51 e DigitalOcean, dá pra gerenciar o DNS pra criar domínios custom pros clientes. Tá muito mais fácil do que antigamente automatizar tudo isso.
  • Então, pra fechar gente, os principais pontos e dicas que eu posso dar pra arquitetura multitenant.

    Primeiro, foca sempre na sua necessidade de flexibilidade de customização na hora de decidir o modelo de segmentação de banco e de codebase. Quanto mais customizável, mais o modelo isolado é indicado. Quanto mais uniforme, mais o modelo compartilhado performa melhor.

    Segundo, tente usar sempre as melhores práticas do SOLID no projeto. Se você não conhece o SOLID ou ainda não entendeu muito bem, me manda um email que eu mando alguns materiais bem legais de SOLID. Inversão de dependência é uma coisa que te ajuda muito nesse tipo de projeto; é praticamente garantido que você vai ter casos de uso de ter que trocar a implementação algum serviço.
  • Terceiro, automatize TUDO que diz respeito a provisionamento, deploy e housekeeping. Esse trio é o responsável por acumular uma constante divida técnica no seu projeto, e é difícil você automatizar depois de ter uma gama de clientes, pq você acaba correndo um risco de ter inconsistências entre quem veio antes e quem veio depois. Pra diagnosticar isso depois é difícil.

    Quarto e último ponto: escreva testes pra tudo que estiver dentro do core. Lembra que qualquer problema que passar vai ser propagado por todos os clientes. É muito importante testar também por que as customizações que você for escrever no futuro dependem de um core estável; você precisa saber se a sua customização quebrou algo ou não, e os testes vão te dizer isso.
  • É isso gente! Perguntas?
  • Rapidinho antes de fechar gente, um recado: a gente tá contratando lá na LQDI. A gente tá procurando desenvolvedores de todos os níveis de experiência, de júnior a sênior e especialista. A LQDI é uma empresa bem legal pra trampar, descontraída, com remuneração competitiva, horários flexíveis, bastante abertura pra novas idéias e muito projeto legal pra fazer. É uma oportunidade de fazer carreira numa empresa ainda pequena que está crescendo com bastante velocidade.

    Se alguém se interessar, é só mandar um e-mail pra work@lqdi.net citando a indicação da palestra. Se quiser saber mais, pode vir falar comigo agora também. É a chance de começar 2016 de trampo novo, hein gente!
  • E é isso, pessoal. Muitíssimo obrigado pela atenção de vocês, e até a próxima! Meus contatos tão aí pra quem quiser tirar dúvida ou precisar de ajuda. E quem quiser os slides, é só pegar no meu Slideshare via QRCode. Valeu!
  • PHP Conference 2015: Construindo e mantendo aplicações multi-tenant (multi-cliente)

    1. 1. Construindo e mantendo aplicações multi-tenant (multi- cliente) Aryel Tupinambá PHP Conference 2015
    2. 2. Sobre o palestrante Co-fundador e CTO da LQDI Digital Projetos para empresas como Porto Seguro, Nestlé, Garoto, Editora FTD, Tishman Speyer e Ambev 12 anos trabalhando com PHP Desde a época que o PHPClasses era a onda :) Aryel Tupinambá
    3. 3. Single tenant - Um único "cliente" para a aplicação - Forma tradicional, instalada no servidor do cliente - Faz sentido quando a aplicação é um PROJETO
    4. 4. Agora com mais um cliente… e mais outro... - Seu PROJETO se tornou um PRODUTO - Diversos clientes usando a mesma aplicação - Estruturas separadas para cada novo cliente - Nada é compartilhado; tudo é isolado
    5. 5. E agora, como faço... … atualizações de segurança? … correção de bugs? … features novas para todo mundo? E o espaço que toda essa galera ocupa? E o custo que tudo isso gera?
    6. 6. Arquitetura multi-tenant - Tenant = inquilino - Uma única aplicação para vários clientes - Uma estrutura comum entre os clientes - As benfeitorias servem para todos - A customização de um cliente não interfere nos demais, nem previne que ele utilize da estrutura comum
    7. 7. Principais desafios DATA STORAGE CODEBASE CUSTOMIZAÇÃO Como segmentar a base de dados para cada cliente? Como segmentar os arquivos enviados? Como fica o código fonte da aplicação? Como ficam as customizações? Como lidamos com updates e novas features? Como podemos customizar as funcionalidades da aplicação? Como permitir que atualizações e novas features não sejam impedidas por customizações?
    8. 8. Não existe escolha certa ou errada O que existe são escolhas que fazem mais sentido para determinados cenários.
    9. 9. Data storage
    10. 10. Três formatos mais comuns Data storage Instâncias separadas Bancos de dados separadas Banco de dados compartilhado Cada cliente possui uma instância do seu SGDB (MySQL, Postgres, Mongo, etc), com seus dados isolados. Mais isolado Mais compartilhado Cada cliente tem um banco de dados (ou schema) diferente, na mesma instância Todos os clientes estão na mesma base de dados; o cliente é identificado por meio de uma coluna nas tabelas (tenantID) Referência / diagramas: Microsoft - https://msdn.microsoft.com/en-us/library/aa479086.aspx
    11. 11. Três formatos mais comuns Data storage Instâncias separadas Bancos de dados separadas Banco de dados compartilhado Mais isolado Mais compartilhado Todos os clientes estão na mesma base de dados; o cliente é identificado por meio de uma coluna nas tabelas (tenantID) Referência / diagramas: Microsoft - https://msdn.microsoft.com/en-us/library/aa479086.aspx
    12. 12. Três formatos mais comuns Data storage Instâncias separadas Bancos de dados separadas Banco de dados compartilhado Mais isolado Mais compartilhado Cada cliente tem um banco de dados (ou schema) diferente, na mesma instância Referência / diagramas: Microsoft - https://msdn.microsoft.com/en-us/library/aa479086.aspx
    13. 13. Três formatos mais comuns Data storage Instâncias separadas Bancos de dados separadas Banco de dados compartilhado Mais isolado Mais compartilhado Cada cliente possui uma instância do seu SGDB (MySQL, Postgres, Mongo, etc), com seus dados isolados. Referência / diagramas: Microsoft - https://msdn.microsoft.com/en-us/library/aa479086.aspx
    14. 14. Referência / diagramas: Microsoft - https://msdn.microsoft.com/en-us/library/aa479086.aspx
    15. 15. Referência / diagramas: Microsoft - https://msdn.microsoft.com/en-us/library/aa479086.aspx
    16. 16. Três formatos mais comuns Data storage Instâncias separadas Bancos de dados separadas Banco de dados compartilhado Sugestão de uso: - Aplicações com pouca ou nenhuma variabilidade / customização - Aplicações em que a principal customização é visual - Aplicações com alto número de usuários (500k+) Mais isolado Mais compartilhado
    17. 17. Três formatos mais comuns Data storage Instâncias separadas Bancos de dados separadas Banco de dados compartilhado Sugestão de uso: - Aplicações em que deve haver maior flexibilidade para customização - Aplicações em que a infra-estrutura sempre será responsabilidade sua - Planos baseados em volume de consumo ou performance Mais isolado Mais compartilhado
    18. 18. Três formatos mais comuns Data storage Instâncias separadas Bancos de dados separadas Banco de dados compartilhado Sugestão de uso: - Projetos em que a separação física do hardware se faz necessária (instalações on-premises, compliance corporativo ou governamental, decisões de performance); - Alta disparidade no volume de dados ou de acessos entre clientes (quando alguns clientes consomem demais) - Quando você precisa monitorar o consumo individual de hardware para cada cliente (e seu SGDB não faz isso por database/schema) Mais isolado Mais compartilhado
    19. 19. - Em regra geral, a FLEXIBILIDADE necessária deve ser o fator decisório - Customizar funcionalidades pode ser um desafio quando você tem um modelo de dados fixo; alterar o modelo de dados de toda uma aplicação só para atender um cliente / uma feature pode gerar inconsistência e dificultar a manutenção - Use o pattern de "migrations" e "seeds" para criar e manter o database schema; muito importante criar migrations que sejam, na medida do possível, 100% reversíveis. - Você não necessariamente precisa escolhar um único modelo para todos os dados da aplicação; se sua aplicação tem interações entre os clientes mas é altamente customizável, por exemplo, faz sentido segmentar o que faz parte do core em um único database, e o que faz parte da customização em um database separado Data storage
    20. 20. Relembrando: não existe escolha certa ou errada
    21. 21. Data storage: na prática
    22. 22. Data storage: na prática Modelo compartilhado (via tenant ID)
    23. 23. Data storage: na prática Modelo isolado (instâncias diferentes)
    24. 24. Codebase
    25. 25. Codebase - Dois cenários: uma instância para todos, ou uma instância por tenant app código fonte cliente1.app.com cliente2.app.com cliente3.app.com config 1 config 2 config 3 cliente1.app.com código fonte config 1 cliente2.app.com código fonte config 2 cliente3.app.com código fonte config 3 custom code Uma instância para todos Uma instância por cliente
    26. 26. Codebase - Uma instância para todos - Atualizações são sincronizadas sem custo ou esforço - Nenhum ou pouquíssimo "housekeeping" necessário - Não é muito flexível para customizações - Mais fácil de escalar quando o padrão de consumo de todos os clientes é semelhante app código fonte cliente1.app.com cliente2.app.com bigcorp.app.com config 1 config 2 config 3
    27. 27. Codebase - Uma instância para todos - Atualizações são sincronizadas sem custo ou esforço - Nenhum ou pouquíssimo "housekeeping" necessário - Não é muito flexível para customizações - Mais fácil de escalar quando o padrão de consumo de todos os clientes é semelhante app código fonte cliente1.app.com cliente2.app.com bigcorp.app.com config 1 config 2 config 3 Nginx sitesvia DB, cachedGit repository
    28. 28. Codebase - Uma instância por cliente - Atualizações não são sincronizadas, exceto se automatizadas - Necessário um trabalho de "housekeeping" para manutenção das instâncias - Bastante flexível para customizações - Qualquer cliente pode ter sua versão da aplicação "congelada" - O código do próprio "core" da aplicação pode ser forkado e alterado a qualquer momento (se em algum momento isso fizer sentido para o negócio) bigcorp.app.com código fonte config 1 cliente1.app.com código fonte config 2 cliente2.app.com código fonte config 3 custom code
    29. 29. Codebase - Uma instância por cliente - Atualizações não são sincronizadas, exceto se automatizadas - Necessário um trabalho de "housekeeping" para manutenção das instâncias - Bastante flexível para customizações - Qualquer cliente pode ter sua versão da aplicação "congelada" - O código do próprio "core" da aplicação pode ser forkado e alterado a qualquer momento (se em algum momento isso fizer sentido para o negócio) bigcorp.app.com código fonte config 1 cliente1.app.com código fonte config 2 cliente2.app.com código fonte config 3 Git repo .env files custom code
    30. 30. Codebase - Uma instância por cliente - Atualizações não são sincronizadas, exceto se automatizadas - Necessário um trabalho de "housekeeping" para manutenção das instâncias - Bastante flexível para customizações - Qualquer cliente pode ter sua versão da aplicação "congelada" - O código do próprio "core" da aplicação pode ser forkado e alterado a qualquer momento (se em algum momento isso fizer sentido para o negócio) bigcorp.app.com código fonte config 1 cliente1.app.com código fonte config 2 cliente2.app.com código fonte config 3 Nginx sites + PHP-FPM pools v1.4.0 v1.4.1 v1.4.0 custom code
    31. 31. Codebase - De novo, o maior fator decisório aqui deve ser a FLEXIBILIDADE: o quanto você espera que o código da aplicação se altere com customizações de clientes? - A escalabilidade, embora seja um desafio (e mereceria uma palestra a parte), não deve ser dificultada em nenhum dos cenários, DESDE QUE você automatize todos os processos e fique de olho na consistência - Instâncias isoladas permitem que você escale clientes mais "barulhentos" (alto volume) para hardware melhor - Você pode também trabalhar com instâncias segmentadas por tier ou plano, e "empacotar" menos clientes por instâncua em tiers mais elevados/caros.
    32. 32. Variabilidade e customização
    33. 33. Variabilidade e customização - O calcanhar de aquiles da arquitetura multi-tenant - Requer bastante meditação sobre os requisitos - Aqui é um dos melhores lugares onde design patterns e uma arquitetura sã e consistente brilham de verdade - Vou falar sobre algumas técnicas e formatos interessantes
    34. 34. Relembrando: não existe escolha certa ou errada
    35. 35. Variabilidade e customização - Para configuração: - Para instâncias de código isoladas: um arquivo de configuração (biblioteca DotEnv) - Para instâncias unificadas: parâmetros de configuração no banco/repositório de tenant data .env
    36. 36. Variabilidade e customização - Para o data storage: - Migrations: funcionam como um controle de versão do seu modelo de dados - A maioria dos frameworks/ORMs possuem suporte built-in (Laravel, CakePHP, Doctrine), e há libs standalone para outros formatos. - Sempre criar migrations "reversíveis", ou seja, que possam sofrer rollback
    37. 37. Variabilidade e customização - Para o código / funcionalidade: EVENTS Transação de negócio (ex: novo pedido realizado) Gerenciador de eventos (via Framework, biblioteca ou hand-made) Envio de e- mail para o usuário Registro no log de atividade do usuário Sub-serviços do core app Módulo customizado: NF-E Geração de NF-E Módulo customizado: Tracking Registrar nova carga à entregar dispatchEvent('order_created', $order);
    38. 38. - Para o código / funcionalidade: EVENTS Variabilidade e customização Transação de negócio (ex: novo pedido realizado) Gerenciador de eventos (via Framework, biblioteca ou hand-made) Envio de e- mail para o usuário Registro no log de atividade do usuário Sub-serviços do core app Módulo customizado: NF-E Geração de NF-E Módulo customizado: Tracking Registrar nova carga à entregar dispatchEvent($order); CORE APP CUSTOM CODE
    39. 39. return [ ['Entregas','abc.delivery.index'], ['Configurar Frete','abc.delivery.config'] ]; - Para o código / funcionalidade: HOOKS Variabilidade e customização Renderizar menu principal Gerenciador de hookstriggerHook('render_menu'); return [['Pedidos','core.orders.index']]; return [['Estoque','core.inventory.index']]; Módulo de Pedidos Módulo de Estoque Módulos do core app Módulo de Entregas Custom code do cliente ABC return [...];
    40. 40. Variabilidade e customização - Para o código / funcionalidade: DEPENDENCY INJECTION Order BillingGatewayContract PayPalBillingGateway implementa DI Container registra recebe PayPalBillingGateway pede BillingGatewayContract
    41. 41. Variabilidade e customização - Para o código / funcionalidade: DEPENDENCY INJECTION Order BillingGatewayContract PayPalBillingGateway Core app Order BillingGatewayContract PayPalBillingGateway Core app Order BillingGatewayContract PayPalBillingGateway Core app TENANT 1 TENANT 2 TENANT 3
    42. 42. PagSeguroBillingGateway Variabilidade e customização - Para o código / funcionalidade: DEPENDENCY INJECTION Order BillingGatewayContract PayPalBillingGateway Core app Order BillingGatewayContract PayPalBillingGateway Core app Order BillingGatewayContract PayPalBillingGateway Core app TENANT 1 TENANT 2 TENANT 3 Custom code
    43. 43. Variabilidade e customização - Para o frontend / interface: - Dê preferência para usar uma engine de templates (Blade, Twig, etc);
    44. 44. Variabilidade e customização - Para o frontend / interface: - Use Sass ou outro pré-compilador de CSS - Você pode gerar o config via PHP, e então rodar o compilador (automático ou manual)
    45. 45. Variabilidade e customização - Para infraestrutura / deploy:
    46. 46. Variabilidade e customização - Para infraestrutura / deploy:
    47. 47. Variabilidade e customização - Para infraestrutura / deploy: http://forge.laravel.com http://deployhq.com http://envoyer.io Use ferramentas ou serviços de automatização de deploy (imagine atualizar manualmente a versão de 600 clientes)
    48. 48. Variabilidade e customização - Para infraestrutura / deploy: Use o Composer para gerenciar core app e código customizável
    49. 49. Variabilidade e customização - Para infraestrutura / deploy: Automatize (se aplicável) o processo de provisionamento para novos clientes Novo cliente cadastrado Script / microservice de PROVISIONAMENTO Criar pasta no servidor e site no Nginx Criar banco de dados Gerar .env de configuração Criar registro no DB de tenants Cadastrar no Envoyer e realizar primeiro deploy Gerar composer.json com Core + Custom code Disparar e-mail para o cliente Gerar config do Sass e compilar CSS final
    50. 50. Variabilidade e customização - Para infraestrutura / deploy: Automatize (se aplicável) o processo de provisionamento para novos clientes Novo cliente cadastrado Script / microservice de PROVISIONAMENTO Criar pasta no servidor e site no Nginx Criar banco de dados Gerar .env de configuração Criar registro no DB de tenants Cadastrar no Envoyer e realizar primeiro deploy Gerar composer.json com Core + Custom code Disparar e-mail para o cliente Gerar config do Sass e compilar CSS final
    51. 51. Variabilidade e customização - Para infraestrutura / deploy: - O custo e tempo para provisionamento interfere diretamente em um KPI de negócio, o CAC (Custo de Aquisição de Cliente) - Provavelmente toda sua infra-estrutura pode ser administrada via API; use isso a seu favor - Amazon EC2 e DigitalOcean tem APIs para provisionamento de servidores - Amazon S3 e Rackspace Cloud tem APIs para criação de novos "buckets" de arquivos - Amazon Route 51 e DigitalOcean tem APIs para gerenciar o DNS (domínios customizáveis)
    52. 52. TL;DR - Oriente sua arquitetura principalmente pela sua necessidade de flexibilidade e customização - Mais customizável -> mais isolada - Mais uniforme -> mais compartilhada - Use as melhores práticas de SOLID, principalmente no que diz respeito a Inversão de Dependência; a arquitetura multitenant é provavelmente o melhor use- case de substituição de implementações de uma interface em runtime
    53. 53. TL;DR - Automatize TUDO que diz respeito a provisionamento, deploy e housekeeping de infraestrutura: manutenção manual de instâncias é uma eterna dívida técnica que se acumula - TDD é PRIMORDIAL; lembre-se que em um ambiente sincronizado, uma atualização impacta TODOS os seus clientes
    54. 54. Perguntas?
    55. 55. A está contratando! Procuramos desenvolvedores front-end e back-end, de TODOS os níveis de experiência, apaixonados pelo que fazem e a fim de aprender e ensinar Ambiente de trabalho bacana, descontraído, com remuneração competitiva, horários flexíveis e bastante abertura para novas idéias. Sem melindres, sem preciosismo e puxação de saco :D Plano de carreira sólido e flexível, com espaço para crescimento em gestão e especialização, e programas de feedback contínuo entre a equipe e os gestores. Manda um e-mail pra work@lqdi.net e cite a palestra da PHP Conference Ou me chame pessoalmente agora para trocarmos uma idéia Começe 2016 de trampo novo!
    56. 56. Obrigado! E-mail / Hangouts: aryel.tupinamba@lqdi.net Facebook: http://facebook.com/aryel.tupinamba Twitter: http://twitter.com/DfKimera LinkedIn: http://linkedin.com/in/aryeltupinamba Slides da palestra: http://slideshare.net/aryeltupinamba http://lqdi.net

    ×