quarta-feira, 22 de janeiro de 2020

Deu ruim!

Resultado de imagem para carroça puxada por burro
Após um tempo de operação do sistema se chega às tabelas com milhões de registros, outras tabelas com outros mil, muitos usuários ativos e concorrentes, a infraestrutura começa a apresentar sinais de incapacidade, e os problemas que nem imaginávamos ter, aparecem nos piores momentos. Com certeza vai tirar de você uma noite de sono, e do seu chefe.

Não é normal um usuário aguardar cinco minutos para receber a resposta de um servidor Web, como também não é normal se formar filas no servidor SQL, coisas que dificilmente surgem quando o sistema é recém inaugurado.

Então, vou listar alguns aspectos que considero relevantes, e algumas dicas baseados na experiência de muitas noites em claro:

Uma coisa que o programador nunca pensa é onde o seu sistema vai ser executado, e quais recursos irá consumir, mesmo porque isso é um problema do pessoal da infra. O foco é fazer aparecer o conteúdo na interface não importa como, e as vezes esse 'como' leva para caminhos tortuosos que consomem recursos em excesso do servidor. Programador não vai pensar nisso, e se houver uma diretiva na empresa para conduzir a solução, pode ser que ele contorne a diretiva.

Então a primeira coisa é pensar na arquitetura da aplicação:

  1. Windows: Processamento local (depende da capacidade da máquina), armazenamento no servidor. O processamento de uma máquina não influencia as demais conectadas, se uma falha o sistema se mantém operante. O custo da solução é maior.
  2. Web: Processamento e armazenamento remoto (servidor). Nesse caso uma máquina servidora atende ao processamento de todos os clientes, deve ser robusta para comportar processamento paralelo. Se o servidor falha o sistema cai. Se um script entra em loop e consome os recursos do servidor, o sistema cai para todo mundo. O custo da solução é menor.
  3. Smartdevice: O processamento normalmente é local, mas certas arquiteturas se comportam como sistemas Web. Sistemas nativos utilizam recursos nativos e são independentes dos demais, sendo o servidor um elemento de apoio apenas. Sistemas online são como sistemas Web.
Dessa forma, solução Web significa que o seu lindo programa disputa recursos com outros programas ao mesmo tempo, e se algum deles estiver consumindo recursos em excesso, todos ficam lentos e ruins. E como esse é o pior cenário de concorrência, vamos focar nele.

Consultas otimizadas: 
Os for eachs do programa NÃO podem percorrer a tabela inteira para localizar um registro. A consulta à tabela deve sempre escolher os filtros adequados, e retornar sempre o mínimo possível de registros. Tabelas grandes e pesadas não perdoam quando os índices estão incorretos e a pesquisa percorre alguns milhares de registros.

  • O melhor índice e filtro é aquele que devolve um registro
  • Às vezes é melhor usar um índice que devolva alguns registros e em seguida se busque nos mesmos o que desejamos do que ter que percorrer a tabela inteira
  • Não saia criando índices para todo lado para melhorar o desempenho da consulta, tabelas grandes não perdoam. O custo para organizar cada um dos índices em tabelas gigantes é altíssimo. Muito processamento é necessário para otimizar índices, levando a operação de manutenção dos registros ficar lenta, vai melhorar um pouco a consulta e vai transformar a operação em uma carroça.

A pior coisa que podemos localizar numa tabela é o nome de uma pessoa, pois o usuário não vai digitar o nome completo, vai entrar um LIKE bonito na programação e o desempenho final vai ser uma porcaria. Tabelas de pessoas costumam crescer muito.

Tela Carregada:
Um ponto que o programador também não se importa muito é com os controles que vão para a interface. Um grid, por exemplo, com muita coisa oculta pode não ser uma boa estratégia, muitas variáveis criadas na interface vão levar o desempenho do navegador de internet no chão. É melhor uma única variável com um monte de conteúdo do que um monte de variáveis com pouco conteúdo cada uma.

Tela armazena conteúdo persistente, mas não pode ser confundida com uma tabela. As vezes o programador consulta um monte de tabelas para montar a interface com tudo que consultou, e em seguida deixar o conteúdo oculto na tela. Isso é meio irracional, mas estou acostumado a ver programas assim. É melhor manter apenas o valor que vai permitir consultar todo o resto, se assim a pessoa quiser ao pressionar um botão de ação, por exemplo. No retorno do servidor se consulta tudo que tem direito.

A regra de ouro é a seguinte, somente coloque na interface aquilo que a pessoa PRECISA ver, e não queira adivinhar o que a pessoa precisa, pergunte primeiro. Sendo assim abrir interface com conteúdo pré-determinado não é uma boa opção. Encher a interface de informação inútil também não é uma boa opção. Ser sintético sim, é uma boa, pois vai reduzir a quantidade de informação a ser transmitida, vai aliviar a carga do servidor Web e vai acelerar a apresentação no navegador do cliente.

Finalmente é melhor mostrar a informação com um Textblock.Caption do que uma &variável com readonly. Variável normalmente é retransmitida para o servidor, mesmo as ocultas, e a interface vai ficar mais lenta, pois mais informação vai retornar para o servidor.

Enfim, use o mínimo de variáveis em uma interface web, opte por freestyle grid com textblock ao invés de grid com variáveis.

Pouco conteúdo na interface é o segredo para um desempenho feliz.

Warnings:
O Genexus normalmente coloca warnings nos programas quando ele é especificado, indicando situações esquisitas. Você por acaso tem costume de ler os avisos e corrigir o que o Genexus propõe?  Comece por aí então.

Monitoramento:
No servidor é que a festa de fato acontece, e claro, todos os programas devem estar corretos e operando. O servidor configurado e dimensionado corretamente, nem precisa dizer né.

Atenção à sazonalidade das operações, e caso existam momentos de pico é melhor redimensionar a infra para suportar a demanda, e neste caso a utilização de nuvem é mais indicada, pois existe a possibilidade da ampliação e redução acontecer automaticamente, trabalho manual a menos. Genexus funciona na nuvem muito bem, mesmo em Ev1, que apesar de rodar .Net 2 já fiz testes na Microsoft Azure e foi de boa.

Aqui é que a coisa pega, não é fácil monitorar a aplicação, principalmente porque tem milhares de soluções de monitoramento que fica até dificil escolher alguma. A forma mais pobre de se analisar a situação do servidor são os registros de log do servidor e gráficos de consumo, as vezes assustadores.


Quando o servidor chega nesse nível, melhor descobrir quais os processos estão rodando lá (Worker Proccess) na imagem abaixo.



Se você não dispõe de uma solução maravilhosa de monitoramento poderá executar um simples comando no console do Windows para descobrir quem está carregando as coisas por lá.

C:\WINDOWS\system32\inetsrv\appcmd.exe list requests > resultado.txt

Isso vai gerar um relatório (resultado.txt) de todos os processos que estão em execução, no seguinte formato:

REQUEST "850000028000026d" (url:GET /site1/p1.aspx, time:887531 msec, client:177.95.190.87, stage:ExecuteRequestHandler, module:IsapiModule)

REQUEST "3600000280000301" (url:GET /site2/p2.aspx?6bde67e80ecdcea9ff88ed41ec240efa698fe68f2dc38a2dcc26d72a26e444d4,gx-no-cache=1579701550528, time:876531 msec, client:177.95.134.119, stage:ExecuteRequestHandler, module:IsapiModule)

E um pequeno detalhe nessa confusão de letrinhas é o time:876531 msec, ou seja, o tempo de resposta que o tal programa está levando para responder ao cliente. Nesse caso, incríveis, 14 minutos.


A meta da equipe deve ser produzir programas leves que respondam rápido, no momento da crise, atender a mais clientes possíveis, se é que isso é possível.

Boa sorte, e se possível, bons sonhos.




sexta-feira, 10 de janeiro de 2020

Responsividade com Flexbox

Existem muitos textos que falam sobre FLEXBOX na internet, assim não vou montar um manual a respeito deste assunto, mas sim focar naquilo que considero mais chato de ser resolvido: a largura dos itens na linha. Sem o domínio desse recurso, o resultado da interface não será convincente.

No Genexus este tema é muito relevante, pois, existem algumas propriedades que poderão ser  definidas no momento da criação da interface, e outras manualmente.

Vamos falar um pouco sobre a questão da largura dos elementos na interface, utilizando um simples arquivo HTML poderemos estudar com maior profundidade esse assunto antes de se aventurar a mexer na interface Genexus.

Largura dos itens

O default para se determinar a largura dos itens na linha em um display:flex, é determinado pelo conteúdo desses itens. A largura do texto, por exemplo, determina a largura do item. Se o conteúdo é menor que a largura, uma área com espaço vazio fica aparecendo, como na imagem abaixo.


Normalmente o que se busca fazer é que todo espaço da linha seja ocupado pelos itens, distribuídos com a mesma largura, ou que pelo menos a largura possa ser controlada.

Um dos recursos disponíveis no flexbox é determinar que os itens cresçam automaticamente, caso exista espaço. Para isso poderemos utilizar uma propriedade flex-grow:1 para determinar que os elementos possam crescer para o limite máximo disponível da linha. E se todos estiverem com 1 a dimensão será dividida de forma igual. O uso de flex-grow:0 fará com que o elemento fique em segundo plano na determinação da largura, ficando com o 'resto' do espaço disponível (normalmente o mínimo necessário para apresentar seu conteúdo).


O flex-grow não vai ajudar caso exista algum item maior (em termos de conteúdo) que os demais, pois este vai crescer mais que os outros. Se for um texto muito largo, praticamente vai consumir a largura inteira disponível, deixando os demais itens com o minimo possível, o suficiente apenas para manter visível seu conteúdo.


Desta forma é necessário mais alguns detalhes para se controlar a largura. Para se produzir isso, foi apenas necessário programar divs e classes css, conforme o exemplo a seguir.

<style>
.linha {display:flex; border:1px solid red; padding:2px;}
.item1 {flex-grow:1;border:1px solid red;}
.item2 {flex-grow:1;border:1px solid red;}
.item3 {flex-grow:1;border:1px solid red;}
</style>
<div class="linha">
 <div class="item1">Mussum Ipsum, ...</div>
 <div class="item2">2</div>
 <div class="item3">3</div>
</div>

Dominando a largura

Vimos que é necessário ter um pouco de controle sobre as larguras, e foi-se o tempo em que se podia determinar larguras fixas para os itens. Em tempos de responsividade a principal preocupação deve ser a de que a interface tenha uma apresentação boa em qualquer largura de dispositivo, e valores fixos em pixels não permitem isso.

Nesse exemplo anterior, é importante estabelecer um limite de crescimento para o item com muito texto, deixando espaço para os demais itens da linha. Para se dominar a largura dos itens é necessário utilizar max-widthmin-width ou flex-basis, e com esses estabelecer os limites máximos (max-width) e mínimos de cada item.

Desta forma, caso se deseje que o item que possui mais texto cresça até o limite máximo de 20% da largura da janela, bastará incluir o max-width:20% na classe do mesmo, e seguindo as definições em nossa classe item1 do exemplo anterior, poderíamos fazer: .item1{flex-grow:1; max-width:20%; border: 1px solid blue;}


O espaço que sobra será igualitariamente distribuído para o item 2 e 3.

E assim, se no item 3 espera-se que possua um conteúdo com no minimo 200px de largura, bastará incluir esta definição na classe correspondente. Por exemplo: .item3{flex-grow:1;  min-width:200px; border: 1px solid blue;}



Apesar de não ser recomendado o uso de pixels, isso será possível e não trará nenhum problema para a interface, desde que se planeje o tema responsividade com a determinação das dimensões para o caso do dispositivo possuir uma largura menor. Sempre que fixar algo em pixel, será necessário avaliar o resultado em dispositivos menores.

Uma propriedade flex-basis é disponibilizada no flexbox, que ao receber uma definição de tamanho em pixel ou % terá o significado de dimensão inicial, ou width.

Antes de encerrar essa parte é importante compreender que nos navegadores atuais temos várias unidades de medidas que poderão ser utilizadas para a determinação da largura., tais como: em, rem, vh, vw, vmin, vmax, ex, ch, px, %. E ainda será possível realizar cálculos das dimensões diretamente na propriedade com o width: calc(100%-20px), por exemplo. (https://developer.mozilla.org/pt-BR/docs/Web/CSS/calc). De todas essas unidades a única que não é boa no contexto atual é o pixel.

O script utilizado neste tópico, é apresentado a seguir.

<style>
.linha {display:flex; border:1px solid red; padding:2px;}
.item1 {flex-grow:1;max-width:20%; border:1px solid red;}
.item2 {flex-grow:1; border:1px solid red;}
.item3 {flex-grow:1;min-width:200px; border:1px solid blue;}
</style>
<div class="linha">
 <div class="item1">Mussum Ipsum, ...</div>
 <div class="item2">2</div>
 <div class="item3">3</div>
</div>

Responsividade

Finalmente dominada a questão das larguras, agora é necessário avançar sobre os diferentes dispositivos em que a interface será apresentada. O que significa avaliar a interface atual sob diversas dimensões distintas. Os navegadores atuais possuem um recurso de Inspecionar que permite reduzir as larguras das interfaces para dispositivos (android, iphone, ipad, ...), que facilita a compreensão da visualização final. Claro essa avaliação será apenas sob o ponto de vista da prototipação da interface, vai faltar uma avaliação final diretamente no dispositivo.

O recurso que precisaremos recorrer para construir interfaces responsivas é o media query, utilizado para selecionar certa característica de uma classe, assim que certa dimensão é alcançada. Para controlar a responsividade será necessário determinar as dimensões desejadas para cada um dos itens caso o dispositivo reduza ou amplie a largura.

Um exemplo poderia ser determinar largura de width:100% para os três itens da linha, fazendo com que os mesmos ocupem a largura toda da interface, isso quando o dispositivo atingir uma dimensão em pixel de 768px. Nesse contexto estamos dizendo que para smartphones os itens devem ser apresentados com 100% de largura.

Para programar essa definição será necessário programar, dentro da área script do exemplo:

@media max-width:768px) {
 .item1 {flex-grow:1;width:100%;border:1px solid red;}
 .item2 {flex-grow:1;width:100%;border:1px solid red;}
 .item3 {flex-grow:1;width:100%;border:1px solid red;}
}

As classes podem ser completamente redefinidas por meio desse recurso, estabelecendo fontes, cores, bordas diferentes para cada situação. Inclusive ocultar algum item na apresentação.

E para causar o efeito de reposicionamento em uma nova linha, será necessário incluir mais uma propriedade flex-wrap:wrap, indicando que em caso de não haver espaço suficiente para item na linha, será necessário definir o próximo item em uma nova linha. Quebra de linha, em outras palavras.


O código para produzir esse cenário responsivo é apresentado abaixo.

<style>
.linha {display:flex;flex-wrap:wrap;border:1px solid red; padding:2px;}
.item1 {flex-grow:1; border:1px solid red;}
.item2 {flex-grow:1; border:1px solid red;}
.item3 {flex-grow:1; border:1px solid red;}
@media(max-width: 768px) {
 .item1 {flex-grow:1;flex-basis:100%; border:1px solid red;}
 .item2 {flex-grow:1;flex-basis:100%; border:1px solid red;}
 .item3 {flex-grow:1;flex-basis:100%; border:1px solid red;}
}
</style>

Genexus

Tudo que vimos anteriormente poderá ser reproduzido no Genexus, sendo algumas coisas possíveis nas propriedades das tabelas, e outras que deverão ser programadas externamente em um arquivo CSS.

O elemento utilizado para produzir uma interface flexbox é a tabela (table), podendo ser a própria MainTable.



Será necessário converter a tabela com o botão direito do mouse e Convert to Flex.  Outra forma seria a programação hardcore em HTML + CSS inclusa em um textblock do tipo HTML, por exemplo. A tabela convertida receberá a class="Flex"[data-gx-flex]ao item, que definem o display:flex.

<div id="MAINTABLE" class="Flex" [data-gx-flex]></div>

As definições no carmine.css dessas classes são as seguintes:

[data-gx-flex] {
 display: flex;
}

.Flex {
    border-style: none;
    border-width: 0px;
}

Primeiramente será necessário criar o arquivo minhasclasses.css com as definições que desejamos para a nossa interface, inclusive com a programação do media query.  Este arquivo deverá estar na pasta web, juntamente com os demais arquivos do Genexus. Abaixo o conteúdo.

.linha{display:flex; flex-wrap:wrap;}
.item1{flex-grow:1;}
.item2{flex-grow:1;}
.item3{flex-grow:1;}
@media(max-width: 768px) {
 .item1{flex-grow:1;flex-basis:100%;}
 .item2{flex-grow:1;flex-basis:100%;}
 .item3{flex-grow:1;flex-basis:100%;}
}

Para incluir o arquivo no Webpanel, deverá ser programada a operação form.HeaderRawHTML no evento Start. Isso incluirá a chamada ao arquivo na área head do HTML.

Event Start
 form.HeaderRawHTML = '<link rel="stylesheet" type="text/css" href="minhasclasses.css" />'
Endevent

Se for necessário incluir mais coisas, basta colocar o conteúdo na mesma definição, ou ainda utilizar uma operação acumulativa: form.HeaderRawHTML +=  

Em seguida cada controle incluído na tabela será um item no layout flex, como por exemplo, inserindo três textblocks na sequencia. Para cada um deles será necessário determinar o conteúdo, em caption, e também a classe externa que definimos no arquivo minhasclasses.css.


Veja que a classe do textblock é associada a propriedade Cell Class, isso ocorre porque o Genexus vai incluir o item dentro de uma <div> específica, item (1) da imagem abaixo, e o próprio elemento textblock será definido em um <span>, item (2)


Não esqueça de definir a classe linha para o MainTable.


Optamos nesse exemplo em realizar externamente todas as definições de classe, mas se você acha mais interessante incluir tudo no Carmine.css, o efeito será o mesmo.

O resultado será um layout flexbox responsivo.


Que sofrerá alteração visual quando a interface for reduzida.




Genexus vai permitir que as definições sejam realizadas nas propriedades dos itens e tabelas, incluindo os elementos wrap, direction, justify content, entre outros. A opção por realizá-las externamente é que assim será possível alterar a visualização pela edição do CSS, sem a necessidade de se recompilar a interface. Acho melhor.

É claro que tem muita coisa a ser vista ainda, inclusive a troca de direção dos itens, mas isso é assunto para outro momento. Se você dominar a largura da interface já é um bom caminho percorrido. Sofri bastante para entender que a coisa era muito simples, espero que você sofra um pouco menos, e por falar nisso Feliz 2020!.

segunda-feira, 6 de janeiro de 2020

row, row, row

Tanto para Bootstrap quanto Flexbox, o conceito do que se coloca na linha é o dominante na construção de um layout. Porque o que causa maior impacto na variação da resolução dos dispositivos é a questão do WIDTH diferente para telas menores ou maiores. A altura HEIGHT é de certa forma ilimitada, pois na vertical, desde que se aceite a barra de rolagem, não existe limite. A barra de rolagem horizontal é considerado um absurdo nos layouts modernos, mesmo porque é chato pra caramba ficar movendo o dedo na horizontal em um dispositivo, na vertical parece ser mais natural para as pessoas (Salvo Instagram que veio com essa idéia de colocar as coisas na horizontal).

Desta forma a opção por Bootstrap ou Flexbox vai determinar a forma com que pensamos a linha.

Bootstrap
Na imagem abaixo temos tres elementos A, B e C que foram posicionados na linha. Bootstrap vai necessitar que se defina quantas colunas cada um dos itens irá ocupar. A, nesse exemplo ocupa 4 colunas, B ocupa 6 colunas e o restante vai para C (2 colunas), totalizando 12 colunas.

Nesse cenário é necessário que se pense na dimensão de A, B e C nas diversas larguras de dispositivos, <768 (extra-small), >=768 (small), >= 992 (medium) e >=1200 (large). As chamadas media querie (https://www.w3schools.com/css/css3_mediaqueries.asp). Apesar do editor Responsive Size do Genexus entregar um modelo mais visual para a definição das colunas, na prática o que temos mesmo é um conjunto de definições HTML + CSS que farão parte do código da página, basicamente, <div class="row"><div  class="col-xs-12 col-sm-4"> responsáveis pela 'alocação' dos recursos na interface.

O problema Bootstrap é que a segunda linha (se houver), deverá ter o mesmo dimensionamento definido na primeira linha. Então ao se posicionar os elementos na linha, na prática estamos definindo as colunas, não linhas. Se por exemplo, buscarmos criar um layout que inclua uma segunda linha com um item D ocupando as 12 colunas, teremos que aumentar bastante a complexidade do código HTML, visto que teriamos que transformar Row 1 e Row 2 em containers diferentes.


Bootstrap produz um código mais poluido, com muitos containers, rows e cells. Dê uma olhada no source produzido pelo Genexus para ver o emaranhado de <div>'s resultante, até mesmo de interfaces simples. Fazendo uma analogia com o passado, seria algo como ter que colocar <table> dentro de  <table> para posicionar as coisas.


Flexbox
Flexbox vai num caminho diferente e mais simples. Uma linha é um container e o que tem dentro desta são os itens, o layout consiste em incluir os itens na medida em que existe espaço disponível para ser apresentado, na sequencia em que são incluídos (isso pode ser revertido ao oposto também). E dependendo da configuração se não houver espaço se inclui uma "quebra de linha" (wrap).


O que se determina para cada item individualmente é sua largura minima e máxima, (min-width, max-width) ou se o mesmo poderá crescer ou reduzir conforme ocorre o redimensionamento da interface.

Para layouts mais complexos, os itens poderão ser transformados em containers, bastando definir a propriedade display:flex. Nesse cenário poderiamos ter A transformado em A1, A2 e A3, e B em B1, B2, B3, simplesmente incluindo uma definição no style dos itens.


Naturamente flexbox produz código HTML + CSS mais conciso, e praticamente o mesmo efeito obtido por Bootstrap poderá ser realizado com poucas definições em flexbox.

Resumo
Bootstrap busca um controle mais absoluto sobre as colunas, em um mundo onde não temos controle de nada na verdade. Comparando com o Flexbox é muito mais complexo de ser programado, exigindo muita definição e div's.

Flexbox é mais simples, mas vai exigir CSS e definição das media queries de forma manual. Pois o Genexus não oferece um editor Responsive Sizes para o Flexbox. Portanto, será necessário mais 'programação' e interfaces de mais 'baixo nivel' neste cenário, se é que definir classes CSSs pode ser considerado programação.








Flexbox

O desafio atual no mundo da construção das interfaces, Web principalmente, é a questão do posicionamento dos elementos em tempos de responsividade das interfaces, o que comumente chamamos de layout. Foi se o tempo em que sabiamos quem era o dispositivo consumidor, nossa preocuação era se a resolução da máquina cliente suportava a largura que definiamos como padrão para as as interfaces. Atualmente não conhecemos o dispositivo, a resolução, a orientação da interface, portanto, criar interfaces se tornou uma tarefa mais complexa.

Alguns modelos e frameworks foram disponibilizados para ajudar e o Bootstrap foi um que colou no mundo Genexus. Recentemente também recebemos mais um modelo de layout, o FlexBox (Flexible Box Module), enterrando de vez interfaces tradicionais criadas com
. Neste cenário temos dois elementos fundamentais para construir o layout
e CSS.


Bootstrap utiliza o conceito das 12 colunas para posicionar os elementos e em Genexus o editor 'abstract' possui uma área Responsive Sizes que permite definir para quatro tamanhos de dispositivo a organização das colunas. O conceito objetiva definir o total de 1/12 colunas que serão utilizados para comportar a largura máxima do elemento que se deseja alocar no layout da linha. Sendo assim se um elemento possui 6 colunas, podemos dizer que ele ocupará 50% da largura disponivel no dispositivo. Se 4 colunas, o mesmo dividirá a linha com mais elementos que ocuparão dois terços da tela, 4/12=1/3 (restando 2/3 para outros elementos). Tirando a lentidão do editor abstrato, definir interfaces responsivas com Bootstrap é relativamente simples.

Flexbox é um cenário mais livre, no qual temos os eixos principal (main-axis) e transversal (cross-axis), e os elementos que são inseridos na linha sem a preocupação de reservar aos mesmos um espaço que nem sempre saberemos se será ocupado ou não. flex-wrap fará com que ocorra a quebra da linha quando não houver mais espaço disponivel,  flex-direction definirá se o conteúdo percorrerá o sentido horizontal ou vertical. Os demais elementos vao permitir o alinhamento e justificação dos conteúdos, a largura dos elementos e se os mesmos terão a capacidade de crescer ou reduzir.

São dois cenários completamente diferentes de se planejar o layout, Bootstrap foca no conceito de reserva do espaço para um eventual conteúdo. Flexbox trabalha com o conceito de incluir conteúdo até quando a largura do dispositivo suportar. Em ambos os cenários, a incerteza de não nos apegarmos à confortável dimensão fixa em pixels dos elementos e prateleira de células que compunha as <table>.

Bootstrap já foi amplamente publicado aqui no Genexandoo, que tal limparmos a poeira do blog com artigos sobre flexbox? Vamos fazer isso.