Jump to content
Douglas Motta

Gerenciamento de Aplicação Multi Instance/Tenancy em PHP

Recommended Posts

Olá, pessoal.

Desenvolvi uma aplicação em PHP para um segmento específico onde cada empresa terá essa aplicação. 

Minha dúvida é quanto ao gerencimento deste sistema, tendo em vista que cada empresa poderá personalizar a aplicação conforme suas necessidades (locais, legislação, negócio, etc.). Porém a maioria das funcionalidades será comum a todas as empresas (mas nada garante que uma funcionalidade que é comum hoje vai ser comum a todas amanhã). Por questões de segurança cada empresa terá seu banco de dados.

Estou entrando no mercado com o primeiro cliente e estou pensando no momento que tiver 30 clientes como vai ser. Utilizo o Bitbucket para controle de versão.

Qual é a melhor forma de gerenciar o codebase e o codecustom desta aplicação?  Um branch para cada cliente? Submodulos do Git? Pela própria aplicação?

Alguém que já vivenciou algo parecido que pode me ajudar?

Obrigado.

Share this post


Link to post
Share on other sites

No decorrer da minha experiência como dev, eu vi muitas decisões ruins, algumas delas eu mesmo sugeri. Mas, a que mais me impressionava era quanto ao projeto base e suas customizações.


Pensando apenas em code (sem entrar em banco de dados), eu utilizaria uma mescla entre o GIT e o composer.

 

Branch's e divisões

 

No GIT:

  • Dividir as branchs em 5 tipos (serão mais branchs): master, cutom-*, homologacao-*, dev-* e hotfix-*;
  • A branch master será única e exclusivamente para o código base não customizado;
  • A branch custom-*, será específica para o código homologado do projeto customizado para uma empresa (custom-empresa01, custom-empresa02, etc..);
  • A branch homologacao-* será específica para homologar a transição do código de dev para prod, aonde testes de alto nível são aplicados. Cada branch de produção (master/custom) terá sua respectiva branch de homologação (homologacao-master, homologacao-custom-empresa01, etc);
  • A branch dev-* é aonde é realizado o desenvolvimento, ela são livres, aonde o "desenvolvedor não conhece as leis" ou "o filho chora e mãe não vê", etc.. Você deve ter quantas forem necessárias, para cada implementação, cada GAP, cada moficação (dev-master-task-2784, dev-custom-empresa01-task-3500, etc...);
  • A branch de hotfix-* é aquela correção rápida, aquele conflito do merge entre uma entre o código base e uma customização ou que está implicando em parada geral do sistema (aquele bug ou erro de português que passou pela homologação/testes), que não necessariamente deve passar por todos os passos de desenvolvimento/homologação/produção. O que nós normalmente chamamos de "codificar em prod"... Sim, isso existe na realidade de todos já fizeram.

Essa é a definição da árvore do projeto. Os caminhos a serem seguidos:

  • dev-master-* <--> homologacao-master <--> master --> homologacao-custom-* <--> custom-(todas as empresas);
  • dev-custom-* <--> homologacao-custom-* <--> custom-*;
  • hotfix-* <--> para aonde for necessário.

Perceba que no fluxo, uma custom nunca deve levar o seu código para a master. Apenas a master pode levar códigos para a custom. Sempre utilizando a branch de homologação da custom.

 

Fork

 

Uma característica bem interessante dos serviços de controle de versionamento (bitbucket, github, etc...) é a possibilidade de criar um fork de um projeto. O seu projeto CORE será o repositório principal e, para cada cliente, você criará um novo fork. Logo, você terá o projeto master e os forks para cada cliente (master -> cliente-01 -> cliente-02, etc...).

 

A divisão entre branchs, deve ser a mesma exemplificada exclusiva para a master. Ou seja, 4 tipos: master, homologação, dev-* e hotfix-*). A nomenclatura das branchs (mster, homologacao, dev-* e hotfix-*) segue a mesma definição anteriormente explicada.

 

A parte de fluxo será o que está exemplificado abaixo:

  • dev-* <--> homologacao <--> master;
  • hotfix-* <--> para onde for necessário.

 

O cuidado, vem na hora de sincronizar os forks. O BitBucket, por exemplo, lhe sugere um fork automático entre as branchs masters de cada repositório. Entretanto, para um melhor controle e validação do novo código, a sincronização deve ocorrer entre a branch master, do projeto master, para a branch homologacao do projeto customizado. Ou seja:

 

  • Master:master --> Cliente-01:homologacao;

 

Dessa forma, você pode homologar, corrigir e enviar para a produção (master) da customização de clientes.

 

No BitBucket, para realizar tal feito, você pode ir no menu compare, escolha  a branch da qual está o código (parte de baixo) e a branch na qual você quer enviar o novo código (branch de cima). Compare as diferenças e, no canto direito superior, o BitBucket lhe exibirá o botão merge. Feito o merge, o processo é exatamente o mesmo que utilizando branchs.

 

Módulos

 

Aqui é que entra a questão de módulos. Caso você implemente partes do sistema como módulos, você pode usar tanto o git-submodule ou o composer. Com ambos você poderá utilizar repositórios privados para baixar as suas dependências. Para cada módulo, você deverá ter o repositório separado e, caso o repositório separado seja customizado, poderá seguir a estruturação acima mencionada.

 

Entretanto, no caso de módulos personalizados, acredito que com o git submodule não seja possível separar entre versões (branch/tags) específicas para cada projeto/branch. No caso do composer é possível, pois, cada branch/projeto, terá seu próprio composer que pode ser personalizado.

 

Banco de dados

 

Nesse caso, vai depender muito do que você irá utilizar para gerenciar o SGBD. Eu utilizei por muito tempo o MySQL WorkBench. Ele funciona perfeitamente quando o assunto é modelagem e sincronização. Entretanto, o formato de arquivo gerado por ele não é aberto e, em consequência, não pode ser versionado. Logo, quando uma tabela precisar ser diferente em algum projeto, você terá dois arquivos distintos.

 

Há, também, a possibilidade de tratar tudo na forma de SQL scripts. Mas ficaria sem uma ferramenta de apoio. Talvez alguém conheça alguma ferramenta melhor para a situação.

 

A contra-partida

 

Quando você separa pequenas partes de um projeto em módulos e os adiciona como dependências do seu projeto, você pode encontrar uma situação denominada  Dependency Hell. Esse termo também pode ser utilizado para a manutenção do código entre as branchs, pois, todas as suas customizações necessitam estarem atualizadas com a branch master. E se você almeja 30 empresas, pense que uma mudança no CORE precisa ser homologada para as 30 empresas.

 

Considerações

 

Sendo dicas como elas são, apenas dicas, você pode considerar demasiada estrutura e simplista ou exagerada demais e partir para uma abordagem meio termo ou mais robusta. Tudo dependerá de onde você está e para onde gostaria de chegar.

 

Existem outras ferramentas e abordagens que pode lhe ajudar. Uma delas seria a integração contínua, o qual incluí N outras ferramentas de auxílio.

 

Update

 

Um sistema que gerencia suas próprias customizações acaba sendo limitado a campos e novas informações. Eu, particularmente, não gosto muito da prática. Entretanto, para evitar o problema com o SGBD, pode-se utilizar tabelas de cadastros par atributo-valor para cruzar informações dinâmicas e reduzir a necessidade de criar novas colunas e tabelas. Mas, é uma questão bem limitada.

 

Update 2

 

Com a nova parte de forks (que eu não havia me lembrado anteriormente), seria a sugestão mais indicada. Pois, se você possuir uma equipe de desenvolvimento específica para cada cliente, poderá gerenciar muito melhor as permissões e acessos. O controle é bem superior a de um repositório apenas.

Edited by Gabriel Heming
explicação sobre fork e novo posicionamento
  • +1 4

Share this post


Link to post
Share on other sites

Olá Gabriel. Muito obrigado pela resposta.

 

Você detalhou bastante e tirou as dúvidas que eu tinha em minha cabeça com relação à esse gerenciamento. Poderia tirar uma útlima dúvida? Com relação ao projeto, a estrutura de pastas, etc. Eu uso Symfony e gostaria de saber se para cada novo cliente eu crio um projeto novo no PhpStorm e clono o Core ou eu consigo gerenciar tudo isso com apenas um projeto no PhpStorm? Tem uma estrutura de pastas para esta arquitetura?

 

Valeu mesmo.

Share this post


Link to post
Share on other sites

Apesar de, estruturalmente, tudo estar em um mesmo projeto. Seria necessário instalar as dependências, pelo composer, para cada projeto. Isso seria necessário sempre que mudar de uma branch de um projeto para outro.

 

Então, sim, é melhor ter um projeto separado para cada cliente.

 

Outro detalhe, que eu não comentei anteriormente é sobre o fork. Para cada cliente pode ser criado um fork do projeto (vou atualizar a resposta anterior).

  • +1 1

Share this post


Link to post
Share on other sites

Atualizei o post principal. E, nesse momento, acredito que a solução entre forks do projeto seja a mais indicada.

  • +1 1

Share this post


Link to post
Share on other sites
1 hora atrás, Douglas Motta disse:

Tem uma estrutura de pastas para esta arquitetura?

 

Não, apenas que exista uma estrutura definida e com coerência (e não seja uma bagunça).

 

A que um framework, como Symfony, lhe oferece estará de bom tamanho.

  • +1 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Similar Content

    • By calinthians
      Olá pessoal, estou criando um sistema de portfólios e fiz tudo funcionar sem PHP, quando transformei o sistema em dinâmico não está mais abrindo o modal de cada portfólio qd clica nele, sendo que eu inspecionei a página e está pegando os valores corretos na ID, só não está abrindo mesmo, e outro modal sem o PHP continua abrindo normalmente.
       
       
      <section id="portfolio"> <div class="container"> <div class="text-center"> <h3 class="section-heading">Portfólio</h3> </div>   <ul id="port-group"> <?php while($dado = mysqli_fetch_assoc($consulta) ) { ?> <li> <div class="portfolio-item"> <a href="#portmodal<?php echo $dado['id']; ?>" class="portfolio-link" data-toggle="modal"> <div class="portfolio-hover"> <div class="portfolio-hover-content pagport"> <i class="fa fa-plus fa-3x"></i> </div> </div> <div class="portfolio-responsive idportimg1"> <style>.idportimg1 {background-image: url("img/bg.png")}</style> </div> </a>   <div class="portfolio-caption"> <h5><?php echo $dado['titulo']; ?></h5> <p class="text-muted"><?php echo $dado['subtitulo']; ?></p> </div> </div> </li>   <!-- Portfolio Modal --> <div class="portfolio-modal modal fade" id="portmodal<?php echo $dado['id']; ?>>" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="close-modal" data-dismiss="modal"> <div class="lr"> <div class="rl"> </div> </div> </div> <div class="container"> <div class="row"> <div class="col-lg-2"> </div> <div class="col-lg-8"> <div class="modal-body"> <h3><?php echo $dado['titulo']; ?></h3> <img class="img-responsive img-centered" src="img/portfolio/site1.jpg" alt=""> <p><?php echo $dado['descricao']; ?></p> <img class="img-responsive img-centered" src="img/portfolio/site11.jpg" alt=""> <ul class="list-inline"> <li><?php echo $dado['datax']; ?></li> <li><?php echo $dado['cliente']; ?></li> <li><?php echo $dado['categoria']; ?></li> </ul> <button type="button" class="btn-close" data-dismiss="modal"><i class="fa fa-times"></i> Fechar</button> </div> </div> <div class="col-lg-2"> </div> </div> </div> </div> </div> </div> <?php } ?> </ul> </div> </section>
    • By NeumarFreire
      Caros amigos. Sou novo neste forum e tenho um problema de código na página resposta. Apesar dos indeces estarem de acordo com os "name" do formulários o erro se mantém.
      Troquei o Method no campo emailId para GET e o erro desapareceu mas nos outros campos não. Vejam o código EM NEGRITO abaixo e se puderem ajudar...
      <?php include_once 'restrito_all.php'; ?>
      <?php include_once 'validar_user.php'; ?>
      <?php include_once 'header.php'; ?>
      <?php
      date_default_timezone_set('America/Sao_Paulo');
      $data= date('d/m/Y');
      $hora= date('H:i').' H';
      ?>
            
            <div id="local">
                  <div class="caminho">Onde estou: NFL & Associados &raquo; Painel de Controle &raquo; Admin &raquo; Visualizar/Responder</div>
                  <div class="welcome">Olá, <?php echo $clienteNome;?> | <?php  echo $data .' às '.$hora ?> | <a href="deslogar.php">Sair</a></div>
            </div>

             <div id="content">
           
      <?php include_once 'menu.php'; ?>
         
                   <div id="content_conteudo">
                   
      <?php include_once 'carregando.php'; ?>

      <?php
      $emailAdmin ='contato@nflweb.com.br';
      $emailAssunto = 'CONTATO Portal Imobiliário NFL & Associados';
      $emailStatus = 'completo';
      $emailResposta = date ('Y-m-d H:i:s');
      $headers = "From: $emailAdmin\n";
      $headers .= "MIME-Version: 1.0\n";
      $headers .= "content-type:text/html; charset=\"utf-8\"/n/n";
      $emailId = $_GET['emailId'];
      $emailTxt = $_POST['mensagem'];
      $emailEmail = $_POST['emailEmail'];
      $recebidoEm = $_POST['emailData'];
      $mensagemEm = $_POST['emailMensagem'];
      $sql_enviaAdmin = 'UPDATE imobi_mailadmin SET = emailStatus = :emailStatus, emailResposta = :emailResposta, emailTxt = :emailTxt WHERE emailId = :emailId ';
      try{
          $query_enviaAdmin = $conecta->prepare($sql_enviaAdmin);
          $query_enviaAdmin->bindValue(':emailStatus',$emailStatus,PDO::PARAM_STR);
          $query_enviaAdmin->bindValue(':emailResposta',$emailResposta,PDO::PARAM_STR);
          $query_enviaAdmin->bindValue(':emailTxt',$emailTxt,PDO::PARAM_STR);
          $query_enviaAdmin->bindValue(':emailId',$emailId,PDO::PARAM_STR);
          $query_enviaAdmin->execute();
          echo '<div class="ok">Mensagem enviada com sucesso!</div><br /> ';
          
      }catch(PDOexception $error_adminEmail){
          echo 'Erro ao atualizar e-mail';
      }
      ?>
                   
                   <div class="inbox">
                   
      <table width="100%" border="0" cellspacing="2" cellpadding="0">
        <tr class="cabecalho">
          <td align="center">DATA</td>
          <td align="center">NOME</td>
          <td align="center">E-MAIL</td>
          <td align="center">EXECUTAR:</td>
        </tr>
       
       <?php
         $emailId =$_GET ['emailId'];
         $sql_inboxAdmin = 'SELECT * FROM imobi_mailadmin WHERE emailId = :emailId ';
      try{
          
          $query_inboxAdmin = $conecta->prepare($sql_inboxAdmin);
          $query_inboxAdmin->bindValue(':emailId', $emailId,PDO::PARAM_STR);
          $query_inboxAdmin->execute();
          
          $resultado_inboxAdmin = $query_inboxAdmin->fetchAll(PDO::FETCH_ASSOC);
          
          }catch(PDOexception $error_inboxAdmin){
              echo 'Erro ao selecionar pendentes.';
          }
          $i=0;
          foreach($resultado_inboxAdmin as $res_inboxAdmin){
              
              $emailId = $res_inboxAdmin['emailId'];
              $emailNome = $res_inboxAdmin ['emailNome'];
              $emailMensagem = $res_inboxAdmin ['emailMensagem'];
              $emailEmail = $res_inboxAdmin ['emailEmail'];
              $emailData = $res_inboxAdmin ['emailData'];
              $emailStatus = $res_inboxAdmin ['emailStatus'];
              $emailResposta = $res_inboxAdmin ['emailResposta'];
              $emailTxt = $res_inboxAdmin ['emailTxt'];
              $cor = 'bgcolor="#EBE9FE"';
      ?>  
       
        <tr <?php echo $cor; ?>>
          <td align="center"><?php echo date('d/m/Y - H:i',strtotime($emailData));?>h</td>
          <td align="left"><?php echo $emailNome;?></td>
          <td align="center"><?php echo $emailEmail;?></td>
          <td align="center" ><a href="painel.php">Voltar</a></td>
        </tr>
       
        <tr <?php echo $cor; ?>>
          <td align="center" class="cabecalho">MENSAGEM:</td>
          <td align="left" colspan="3"><?php echo $emailMensagem;?></td>
        </tr>
       
       
       <?php
        }  
       
      ?>
       
      </table>
             
                </div> <!--FECHA CLASS INBOX --> <br />
      <br />
           <form name="responderEmail" action="" enctype="multipart/form-data" method="POST">
               <label>
                   <span>Escreva a resposta: </span>
                  <textarea rows="8" name="mensagem" cols="108"></textarea>
               </label>
               <input type="hidden" name = "emailId" value = "<?php echo $emailId;?>" />
               <input type="hidden" name = "emailEmail"  value = "<?php echo $emailEmail;?>" />
               <input type="hidden" name = "emailData"  value = "<?php echo $emailData;?>" />
               <input type="hidden" name = "emailMensagem"  value = "<?php echo $emailMensagem;?>" />
                         
               <input type="submit" name = "executar" id="executar" value="Enviar resposta" />
           </form>  
                    
               </div> <!--FECHA CONTENT CONTEUDO-->
               
           </div> <!--CONTENT-->

      <?php include_once 'footer.php'; ?>

    • By daniramon
      Olá pessoal, tudo bem?
       
      Sou novato em PHP, e preciso criar a seguinte função.
       
      Meu problema: 
      - Recebo os seguintes parâmetros: (total de alunos & número de pessoas por grupo).
      - Preciso definir um número ideal de pessoas por grupo, sem deixar nenhum grupo desfalcado. 
      - Mínimo de 4 pessoas por grupo;
      <?php $totalUsers = 50; $usersPerGroup = 6; $minPerGroup = 4; ?>  
      $var = $totalUsers / $usersPerGroup;
       
      Na simulação acima, terei 7 grupos com 6 pessoas, e um grupo com 1,4 pessoas..rs.
       
      Gostaria, que a função me retornasse, em um mundo ideal:
       
      7 grupos com 6 pessoas = 42 pessoas
       
       2  grupos com 4 pessoas = 8 pessoas
      <?php return $array = [ 7 => 6, 2 => 4]; ?>  
      Alguém poderia me ajudar na criação desta função, please...
    • By Carcleo
      Parece brincadeira, mas estou fazem 3 dias tentando instalar a cURL e não consigo
      Windows 10 Pro Apache 2.4 Php 7.3.4 MySQL 8.0.15.0 Tentativas:
      1) libeay32.dll, msvcr71.dll, ssleay32.dll e php_curl.dll coladas na pasta system32 e adicionadas ao PATH do Windows 2) Baixei a biblioteca cURL do site https://curl.haxx.se/, cliquei em curl.exe e nada acontece. 3) ;extension=CURL e ;extension=php_curl.dll descomentadas no php.ini (Embora a linha ;extension=php_curl.dll não exista nessa versão do PHP eu inserí ela na lista) 4) Confirmado de que o php.ini é o correto.  Agora só faltam 2 tentativas
       
      A) Apelar para a fé.
      B) Instalar Apache e PHP antigos pois lembro-me que usei em um projeto fazem uns anos e funcionava de boa.
           Mas não gostara de voltar no tempo.
       
      Claro que essas 2 foram só para descontrair!
       
      Alguém me ajuda? Ode estou errando?
    • By terra
      Olá,
       
      Preciso pegar o valor desse select
       
       
       aqui nessa query
       
       
      Agradeço qualquer ajuda
×

Important Information

Ao usar o fórum, você concorda com nossos Terms of Use.