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,ModelDefeViewDef— nomes obrigatórios, chamados por macro-execução a partir do nome do fonte.- Quem grava
MPFormModel, nãoFWFormModelpuro — a diferença está nos valores padrão doNew.- Include mínimo
Protheus.chmaisFWMVCDef.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:
Function FWLoadModel(cProgram)
Local oModel := &("STATICCALL("+cProgram+",ModelDef)")
Return oModelDuas 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:
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
TDNtdn.totvs.com
TDN — AdvPL utilizando MVCTDNtdn.totvs.com
TDN — FWFormModelTDNtdn.totvs.com
TDN — FWFormView
Continue por aqui
- Vídeo
ProtheusReferência técnica
ModelDef: o modelo de dados de uma rotina MVC
A sequência real de FWFormStruct, MPFormModel e AddFields que monta o modelo — e por que instanciar FWFormModel direto não grava nada.
- 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.