Pragiva — Tecnologia na prática

MVC no Protheus: o que é e por que ele existe

MVC separa modelo, tela e menu, e é por isso que ele grava sem RecLock. O esqueleto, o nome obrigatório de cada função e o que o framework custa em troca.

Protheus Guia de processo 5 min de leitura

Assista o vídeo

Resposta rápida

O que é
um framework que separa o menu, a regra de negócio e a tela em três blocos com nome fixo, para o Protheus montar sozinho.
As três funções
MenuDef, ModelDef e ViewDef — nomes obrigatórios, chamados por macro-execução a partir do nome do fonte.
Quem grava
MPFormModel, não FWFormModel puro — a diferença está nos valores padrão do New.
Include mínimo
Protheus.ch mais FWMVCDef.ch.
Serve para tudo?
Não. Regra fora do dicionário de dados vira evento, não campo do modelo.
Nesta página6 seções

Todo consultor já ouviu “isso é MVC” como sinônimo de “cadastro moderno”. A frase esconde o que realmente muda: em troca de seguir uma convenção de nomes, você para de escrever a gravação do registro. Este artigo abre a série explicando o que o framework é, o que ele poupa e onde ele não cabe. Os detalhes de cada peça — menu, modelo e tela — ficam para os próximos textos.

O que o MVC separa, e o que ele poupa

MVC divide um cadastro em três responsabilidades. O modelo guarda a regra: quais campos existem, quais são obrigatórios, o que valida antes de gravar. A view guarda a tela: como os campos aparecem, agrupados em qual caixa, em qual aba. O menu guarda as opções que o usuário vê no browse — pesquisar, incluir, alterar, excluir — e qual operação cada uma dispara.

A separação em si não é o ganho principal. O ganho é o que ela permite ao framework fazer por você: como o modelo sabe o alias, a chave primária e a estrutura de campos do dicionário de dados, o Protheus consegue montar sozinho o RecLock, o Replace campo a campo e o MsUnlock da gravação. Quem escreve um cadastro fora do MVC digita essa sequência à mão, campo por campo, com o risco de esquecer um RecLock antes de gravar ou de deixar um registro travado. Quem escreve um MVC declara a estrutura e devolve o modelo; a gravação é responsabilidade do framework.

Um nome não é estilo, é contrato

Um fonte MVC tem uma User Function — a que aparece no menu do Protheus — e três Static Function com nome obrigatório: MenuDef, ModelDef e ViewDef. “Obrigatório” aqui não é convenção de equipe. É o próprio mecanismo de carga.

FWLoadModel recebe o nome do fonte e devolve o modelo assim:

ADVPL / TLPP
Function FWLoadModel(cProgram)
Local oModel := &("STATICCALL("+cProgram+",ModelDef)")
Return oModel

Duas linhas. STATICCALL é uma macro-execução que chama, dentro do fonte cProgram, a Static Function com o nome literal ModelDef. Não existe busca por convenção nem reflexão sobre método — é texto concatenado e avaliado. Se a função dentro do seu fonte se chamar ModeloDef ou DefineModelo, o STATICCALL procura ModelDef e não encontra: erro em tempo de execução, não aviso de compilação.

FWLoadView faz a mesma macro-execução para ViewDef, com um detalhe a mais: antes de montar a chamada, ela corta tudo o que vem antes do primeiro ponto no argumento recebido. É por isso que uma ACTION no formato 'VIEWDEF.<fonte>' funciona — o prefixo VIEWDEF. existe só para o roteador de ações reconhecer que precisa carregar uma view; o que sobra depois do ponto é o nome do fonte, exatamente o que FWLoadView espera.

Quem grava de verdade: MPFormModel

O ModelDef de um MVC quase sempre instancia MPFormModel, não FWFormModel. As duas classes têm a mesma assinatura de New — id, bPre, bPost, bCommit, bCancel — porque MPFormModel herda de FWFormModel e redeclara o New só para trocar os valores padrão dos blocos de código:

ADVPL / TLPP
DEFAULT bPre := {||.T.}
DEFAULT bPost := {|| FWFormCanDel(Self) }
DEFAULT bCommit := {|| FWFormCommit(Self)}
DEFAULT bCancel := {|| FWFormCancel(Self)}

Instanciar FWFormModel direto e não passar bCommit deixa esse bloco vazio: nada grava. Instanciar MPFormModel sem passar nada já entrega um bCommit que chama FWFormCommit(Self), a rotina do próprio framework que percorre a estrutura montada por AddFields/AddGrid e grava campo a campo. É por isso que um MVC comum não tem uma linha de RecLock: quem grava não é o seu código, é o bCommit padrão da classe. Na prática, um MVC se apoia quase sempre em MPFormModel; FWFormModel puro é a exceção rara, e quem escolhe essa forma precisa lembrar de passar o próprio bCommit na mão — sem herdar o New de MPFormModel, não existe bloco padrão para gravar.

Um MVC completo cabe em poucas linhas

Um cadastro de entidade única — sem grade, sem regra fora do dicionário — resolve as três funções obrigatórias em poucas linhas cada. MenuDef declara as opções do menu com ADD OPTION, todas apontando para a mesma VIEWDEF.<fonte>. ModelDef lê a estrutura de campos do dicionário com FWFormStruct(1,...), instancia MPFormModel():New(cId), encaixa a estrutura com AddFields e fecha com SetPrimaryKey. ViewDef carrega o modelo pronto com FWLoadModel, lê a estrutura de novo — agora com FWFormStruct(2,...) — e liga um campo de tela ao modelo com AddField.

Repare no que falta nessa lista: não há RecLock, não há DbSeek, não há tratamento de lMsErroAuto. Um MVC completo, e ele grava. O quinto artigo desta série monta esse esqueleto por completo, num fonte único que compila.

O que ele custa

O ganho de gravação automática tem um preço: o modelo é amarrado ao dicionário de dados. FWFormStruct lê a definição dos campos direto do SX3 — tipo, tamanho, obrigatoriedade — e é a partir dela que AddFields monta o formulário. Uma regra que não corresponde a um campo do dicionário não tem onde morar dentro do modelo puro; ela vira código de evento, registrado por InstallEvent numa classe derivada de FWModelEvent, com métodos como InTTS, ModelPosVld e VldActivate. Ou seja: o MVC resolve bem o cadastro que se parece com uma tabela, e empurra para eventos tudo que exige lógica que a tabela não descreve sozinha. Um relatório, uma tela sem gravação ou uma regra que depende de múltiplas tabelas fora de uma relação pai-filho não ganham nada de MPFormModel — o ReclockOrExecauto e o GetArea e RestArea continuam sendo o caminho certo para esses casos.

Uma pergunta recorrente é se TLPP muda esse desenho. O que se pode afirmar, sem entrar em número nem em metodologia de busca: não há registro de uma tela MVC completa escrita em TLPP — MenuDef, ModelDef e ViewDef continuam em AdvPL, e a causa disso (limitação do compilador com o #xcommand de ADD OPTION, decisão de arquitetura ou apenas hábito de quem escreve) não está verificada aqui.

O que vem a seguir

Os próximos artigos descem para cada peça: o MenuDef e as duas escalas de operação que ele mistura; o ModelDef, campo a campo; o ViewDef, com as caixas; e o cadastro pai-filho, onde a ordem dos blocos de AddGrid inverte a de AddFields. Quem monta um fonte do zero pode revisar antes a estrutura de um fonte AdvPL.


Se a escolha entre MVC e uma tela tradicional está travando um projeto seu, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — AdvPL utilizando MVC
  2. TDNtdn.totvs.com

    TDN — FWFormModel
  3. TDNtdn.totvs.com

    TDN — FWFormView

Continue por aqui

  1. Vídeo

    ProtheusReferência técnica

    ViewDef: a tela de uma rotina MVC

    ViewDef não valida nem grava: só recebe o modelo pronto e diz onde cada componente aparece. O que acontece sem SetOwnerView e quando o box não é preciso.

    4 min de leituraAssista o vídeo