[PARTE 1] O Poder desconhecido do .yaml 🚀
Post – Parte 1 de 2
Nota: Este post foi dividido em duas partes para facilitar a leitura. Continue na Parte 2 para ver o conteúdo completo.
O Poder desconhecido do .yaml 🚀
Índice
- Oque é o .yaml?
- Porque comecei a seguir essa abordagem de desenvolvimento
- Como isso funciona no Ipanel?
- A minha versão do Ipanel o LaravelCMS
- Mostre código doidão
Salve tabnews, nesse artigo eu venho compartilhar um pouco de um projeto que estou desenvolvendo que envolve uma abordagem na programação talvez um pouco obscura e desconhecida mas que se usada de maneira eficiente pode fazer com que um sistema que você entregaria em 1 ano seja entregue em 6 meses.
Se ficou curioso com como raios um .yaml pode trazer tanta vantagem, se quer entender melhor oque o maluco aqui está falando, se quer me refutar, ou apontar algum erro no que estou fazendo por favor continue lendo o artigo e deixe um comentário para eu ler.
Oque é o .yaml?
Vamos começar pelo simples, oque seria um arquivo .yaml? arquivos .yaml ou .yml são arquivos de marcação que funcionam com base na identação do texto, ou seja, eles não funcionam com palavras reservadas como os arquivos de programação.
Normalmente esses arquivos são usados para gestão de dependências ou para automatizações, como no Flutter onde o pubspec.yaml controla dados como os pacotes que você importa do pub.dev e outras informações do projeto, ou como nas Github Actions onde você constroi pipelines CI/CD com a indentação do arquivo. Mas essas não são as únicas maneiras de se tirar proveito disso.
Porque comecei a seguir essa abordagem de desenvolvimento
Não posso ser mesquinho a ponto de dizer que fui eu que tive essa ideia de abordagem e acredito que isso consolida a eficiência dessa abordagem de desenvolvimento. Atualmente atuo como Desenvolvedor Fullstack Junior numa empresa do Sudoeste Paranaense chamada Imaxis.
E foi na Imaxis que eu descobri essa abordagem. Aqui na empresa temos diversas soluções para diferentes problemas, mas uma coisa em comum com todas seria o Ipanel. O Ipanel é uma invenção do meu chefe que vem sendo aprimorada desde 2011, desenvolvida em PHP puro com jQuery – inclusive, atualmente faço o sustain de um sistema que usa uma das primeiras versões do Ipanel e ele segue firme e forte (fora os problemas do legadão de sempre, klkkkk).
Atualmente, o Ipanel já está bem mais avançado do que era lá em 2011 e usamos uma versão com o CodeIgniter ainda com jQuery, o que trouxe uma organização para o projeto e fez com que a abordagem ainda seja eficiente. Basicamente, essa é a história e a maneira que eu descobri o poder do .yaml. Não irei entrar em muitos detalhes a nível de código do Ipanel, o foco é o meu CMS – mas posso responder qualquer pergunta.
Como isso funciona no Ipanel?
Chegando na parte mais legal e de fato o que preparei para esse artigo, o Ipanel funciona com base na estruturação das pastas e nomeação dos arquivos .yaml. Ou seja, depois de modelado o banco e com as entidades do Doctrine criadas, são feitos um arquivo .yaml para cada entidade com o mesmo nome do arquivo da Entidade – ou seja, para o usuário teríamos User.php (modelo Doctrine) e User.yaml (arquivo com as configurações da tela).
Através de diversos arquivos PHP, os .yaml são interpretados e processados de modo que o HTML, jQuery e CSS sejam construídos de maneira dinâmica, evitando a necessidade de criar um arquivo para cada entidade. Isso economiza muito tempo de desenvolvimento, principalmente para entidades simples onde só seria necessário um CRUD.
Destaque: Um caso atual que mostra a força do Ipanel foi uma das soluções da Imaxis, onde eu sozinho consegui subir o core de um sistema de vendas com carrinho, gestão de produtos, gestão das entregas e relatório de produtos em apenas 1 semana.
A minha versão do Ipanel o LaravelCMS
Depois de 1 ano trabalhando na empresa e sempre pensando: “Po, eu preciso fazer a minha versão do Ipanel”, decidi por jogar meu Laravel e desenvolver uma versão com as minhas decisões e com uma abordagem um pouco diferente – já que, mesmo parecidos, o CodeIgniter e o Laravel são bem diferentes. O que se manteve foi a organização em uma pasta core/ e a lógica de nomeação da entidade com o arquivo .yaml.
Como todo dev atarefado e universitário (5° Período na UTFPR-FB), não podia simplesmente gastar meu tempo refazendo o código sem ter de fato um retorno, nem que fosse de conhecimento. Então, decidi que iria usar a TALL Stack (TailwindCSS, AlpineJs, Livewire e Laravel). Mesmo que eu não conseguisse usar meu sistema num futuro freelance, pelo menos aprendi novas tecnologias.
Web nunca foi o meu forte e nunca foi o que me interessou, mas sempre gostei muito de PHP. O Ipanel facilitou muito minha vida nesse último ano, além de me lapidar como dev – afinal, o código é muito complexo, e tive que ler bastante para entender como funciona de fato. Nesse último mês, estive aprendendo e desenvolvendo essa solução. Algo interessante de se falar é que nunca toquei em nada da TALL Stack; meu dia a dia se resume ao pão com ovo do PHP, CodeIgniter, Bootstrap 4.5 e jQuery – o básico e que, de fato, funciona.
Mostre código doidão
Esse é o arquivo Person.yaml, para minha entidade de Person do Eloquent que vai armazenar as pessoas no sistema. A ideia é ter uma base sólida que exista em todo sistema – então estou me concentrando nisso atualmente: usuários, permissões, logs, o básico de todo sistema.
Person:
startsOn: 'grid'
identifier: 'pes_id'
listingConfig:
grid:
pes_id:
name: Id
tagStyle: 'mb-4'
labelStyle: 'font-semibold text-primary-400 text-xl pb-2'
fieldStyle: 'text-xl'
html: 'p'
pes_name:
name: Nome
labelStyle: 'font-semibold text-primary-400 text-md pb-2'
fieldStyle: 'truncate'
html: 'p'
pes_cpf:
name: Cpf
labelStyle: 'font-semibold text-primary-400 text-md pb-2'
fieldStyle: 'truncate'
html: 'p'
pes_created_at:
name: Criado Em
labelStyle: 'font-semibold text-primary-400 text-md pb-2'
fieldStyle: 'truncate'
html: 'p'
listingFunction: getDate
table:
pes_id:
name: Id
style: 'font-semibold'
pes_name:
name: Nome
style: 'font-semibold'
pes_cpf:
name: Cpf
style: 'font-semibold'
pes_created_at:
name: Criado Em
style: 'font-semibold'
listingFunction: getDate
formConfig:
view: "form.component"
pes_name:
type: string
label: "Nome"
edit: false
placeholder: "Informe o nome"
helper: "Nome do Usuário"
sizing: "mb-4 w-full max-w-2lg"
groupIn: "Dados Pessoais"
identifier: "name"
validationRules:
- "required"
- "min:3"
line: 1
pes_email:
type: string
label: "Email"
edit: true
placeholder: "Informe o Email"
helper: "Email do Usuário"
groupIn: "Dados Pessoais"
sizing: "mb-4 w-full max-w-md"
identifier: "email"
validationRules:
- "required"
line: 2
pes_phone:
type: string
label: "Telefone"
edit: true
placeholder: "Informe o telefone"
helper: "Telefone do Usuário"
sizing: "mb-4 w-full max-w-md"
groupIn: "Dados Pessoais"
identifier: "phone"
mask: "(99) 99999-9999"
line: 2
pes_cpf:
type: string
label: "CPF"
edit: false
placeholder: "Informe o cpf"
helper: "Documento do Usuário"
sizing: "mb-4 w-full max-w-md"
groupIn: "Dados Pessoais"
identifier: "cpf"
customValidation: ValidateCPF
mask: "999.999.999-99"
validationRules:
- "required"
line: 2
city_cit_id:
type: relation
label: "Cidade"
edit: true
placeholder: "Selecione a Cidade"
helper: "Cidade do Endereço"
sizing: "mb-4 w-4/4"
groupIn: "Endereço"
identifier: "city"
validationRules:
- "required"
values:
line: 1
pes_uf:
type: select
edit: true
updateRemoteField:
remoteIdentifier: city
remoteEntity: City
remoteAtrr: cit_uf
key: cit_id
value: cit_name
values:
AC: "Acre"
AL: "Alagoas"
AP: "Amapá"
AM: "Amazonas"
BA: "Bahia"
CE: "Ceará"
DF: "Distrito Federal"
ES: "Espírito Santo"
GO: "Goiás"
MA: "Maranhão"
MT: "Mato Grosso"
MS: "Mato Grosso do Sul"
MG: "Minas Gerais"
PA: "Pará"
PB: "Paraíba"
PR: "Paraná"
PE: "Pernambuco"
PI: "Piauí"
RJ: "Rio de Janeiro"
RN: "Rio Grande do Norte"
RS: "Rio Grande do Sul"
RO: "Rondônia"
RR: "Roraima"
SC: "Santa Catarina"
SP: "São Paulo"
SE: "Sergipe"
TO: "Tocantins"
label: "Estado"
placeholder: "Selecione o Estado"
helper: "Estado do Endereço"
sizing: "mb-4 w-1/6"
groupIn: "Endereço"
identifier: "uf"
validationRules:
- "required"
line: 1
pes_neighborhood:
type: string
label: "Bairro"
edit: true
placeholder: "Informe o bairro"
helper: "Bairro do Endereço"
sizing: "mb-4 w-2/4"
groupIn: "Endereço"
identifier: "neighborhood"
validationRules:
- "required"
line: 2
pes_street:
type: string
label: "Logradouro"
edit: true
placeholder: "Informe o Logradouro"
helper: "Logradouro do Endereço"
sizing: "mb-4 w-2/4"
groupIn: "Endereço"
identifier: "street"
validationRules:
- "required"
line: 2
pes_number:
type: string
label: "Número"
edit: true
placeholder: "Informe o Número"
helper: "Número do Endereço"
sizing: "mb-4 w-1/4"
groupIn: "Endereço"
identifier: "number"
validationRules:
- "required"
line: 2
pes_complement:
type: string
label: "Complemento"
edit: true
placeholder: "Informe o complemento"
helper: "Complemento do Endereço"
sizing: "mb-4 w-4/4"
groupIn: "Endereço"
identifier: "complement"
line: 3
pes_postal_code:
type: string
label: "CEP"
edit: true
placeholder: "Informe o cep"
helper: "CEP do Endereço"
sizing: "mb-4 w-1/6"
groupIn: "Endereço"
identifier: "postal_code"
mask: "99999-999"
validationRules:
- "required"
line: 3
buttonsConfig:
showDeleteButton: true
showEditButton: true
showDetailsButton: true
showInsertButton: true
showSearchButton: true
getConfig:
controller: ListingCtrl
method: getAll
params:
model: Person
Detalhe importante
As chaves principais deste arquivo são:
- StartsOn → modo de listagem da view
- identifier → pk da minha entidade
- listingConfig → configurações da listagem (colunas, labels, estilos)
- formConfig → configurações dos formulários (view, tamanho, validações, etc.)
- buttonsConfig → quais botões serão apresentados
- getConfig → controlador e método para listagem customizada
Em um primeiro momento pode parecer confuso, mas está feito da maneira que faz sentido pra minha cabeça. Algo que levei muito em conta e que resolve o principal problema dessa abordagem é: Telas Complexas.
Caso você já tenha desenvolvido um sistema, sabe que essa abordagem não vai funcionar para alguma entidade que tenha muitas peculiaridades ou que o cliente traga algum requisito muito louco/específico que fuja do escopo disso. Por isso, em todos os momentos tento dar a possibilidade de se tratar essas coisas com um arquivo customizado – um exemplo seria no formConfig→view, onde eu posso passar uma view customizada para o form, podendo assim tratar qualquer peculiaridade.
Mas isso não faz com que o resto seja descartado também. Segue o exemplo da User.yaml, que renderiza uma view custom:
formConfig:
view: "user-form"
usr_email:
type: string
label: "Email"
edit: true
placeholder: "Informe o Email"
helper: "Email do Usuário"
groupIn: "Dados Usuário"
sizing: "mb-4 w-1/2"
identifier: "email"
validationRules:
- "required"
line: 1
usr_password:
type: string
label: "Senha"
edit: true
placeholder: "Informe a Senha"
helper: "Senha do Usuário"
groupIn: "Dados Usuário"
sizing: "mb-4 w-1/2"
identifier: "password"
validationRules:
- "required"
line: 1
confirm_password:
type: string
edit: false
label: "Confirmar Senha"
placeholder: "Confirme a senha"
helper: "Confirme a senha"
sizing: "mb-4 w-1/2"
groupIn: "Dados Usuário"
identifier: "confirm_password"
validationRules:
- "required"
line: 1
persons_pes_id:
type: relation
label: "Operador"
edit: false
placeholder: "Selecione o Operador"
helper: "Pessoa vinculada a esse usuário"
sizing: "mb-4 w-2/3"
groupIn: "Dados Usuário"
identifier: "persons"
validationRules:
- "required"
fillOnStart:
controller: GenericCtrl
params:
model: "Person"
method: getAll
pluck:
- pes_name
- pes_id
line: 2
profiles_prf_id:
type: relation
label: "Perfil de Acesso"
edit: false
placeholder: "Selecione o Perfil de Acesso"
helper: "Perfil de Acesso desse usuário"
sizing: "mb-4 w-1/3"
groupIn: "Dados Usuário"
identifier: "profile"
validationRules:
- "required"
updateRemoteField:
customRemote: getRepresentedAgents
fillOnStart:
controller: GenericCtrl
params:
model: "Profile"
method: getAll
pluck:
- prf_name
- prf_id
line: 2
Mesmo tendo suas peculiaridades, eu ainda aproveito as coisas que podem ser "matadas" com .yaml na view personalizada, usando meu componente Blade que consome os dados processados do .yaml e constrói o HTML diretamente na view personalizada.