Ir para o conteúdo
14 de ago. de 2026·8 min de leitura

A lógica de negócio Delphi está presa nos formulários?

Encontre e separe a lógica de negócio Delphi escondida em formulários VCL, controles de dados, eventos de dataset e código de transações.

A lógica de negócio Delphi está presa nos formulários?

Formulários Delphi muitas vezes são especificações executáveis cobertas por uma interface de usuário. Um clique em botão calcula descontos, um TDBEdit.OnExit normaliza um código de conta, BeforePost rejeita um período fechado e um evento da grade muda o retorno da próxima consulta. Se esses handlers forem tratados como código descartável de apresentação, o substituto parecerá pronto enquanto muda silenciosamente o negócio.

Uma conversão direta piora esse risco. Recriar cada formulário em um navegador ou em outro toolkit de desktop preserva os limites antigos e depois cobra a redescoberta de dependências ocultas em um modelo de eventos menos tolerante. O caminho mais seguro identifica o comportamento observável, extrai regras por trás de interfaces explícitas e permite que a aplicação VCL antiga e o novo sistema chamem as mesmas operações conceituais durante a transição.

Como a lógica de negócio foi parar nos formulários Delphi?

A lógica de negócio foi parar nos formulários Delphi porque a VCL encurtava muito o caminho até um software funcional. Coloque um dataset, um TDataSource, alguns controles conscientes de dados e um botão no formulário, depois ponha a decisão ao lado do evento que precisa dela. Essa escolha era razoável quando um desenvolvedor cuidava da aplicação e os usuários ficavam perto do banco de dados. Anos de mudanças transformaram proximidade em arquitetura.

A classe do formulário passou a acumular várias responsabilidades. Ela lê o estado dos controles, interpreta a intenção do usuário, aplica políticas, inicia transações, atualiza datasets, formata mensagens e decide qual tela abrir em seguida. O arquivo .dfm acrescenta outra camada porque valores de propriedades e ligações entre componentes mudam o comportamento em execução sem aparecer no método Pascal que está sendo lido.

Por isso, a contagem de linhas subestima a migração. Uma unit com 250 linhas pode depender de dezenas de propriedades herdadas, campos persistentes, ações, módulos de dados compartilhados e atribuições de eventos armazenadas no DFM. Até um TDBEdit aparentemente vazio escreve pelo TDataSource no buffer de um dataset. Seu comportamento depende do estado do dataset, de eventos de campo, máscaras de edição e do que BeforePost fará depois.

Não classifique todo código de evento como lógica de negócio. Mostrar uma caixa de diálogo, mudar o foco e redimensionar colunas são tarefas de apresentação. Decidir que uma fatura não pode ser lançada depois do fechamento de um período é uma regra. Traduzir a faixa de um cliente em desconto é um cálculo. Chamar uma stored procedure pode ser orquestração da aplicação ou acesso a dados, conforme o contrato exposto pela procedure. A distinção importa porque cada tipo de código precisa de um destino diferente.

Um teste útil remove mentalmente o formulário. Se a decisão precisa continuar válida para uma importação, uma requisição de API ou uma rotina em lote, ela é lógica de negócio. Se o comportamento só ajuda uma pessoa a operar essa tela específica, ele pode ficar na interface. Se coordena um caso de uso, mas não contém nenhuma regra, pertence a um serviço de aplicação.

O arquivo do formulário é só metade do programa

Você precisa de um inventário do comportamento executável antes de extrair qualquer coisa. Esse inventário deve incluir Pascal, recursos DFM, formulários herdados e objetos do banco de dados. Buscar apenas handlers de clique deixa de fora edições automáticas e eventos do ciclo de vida. Ler somente o DFM ignora handlers atribuídos durante a execução.

Comece com um mapa mecânico. Para cada formulário, registre todos os eventos de componentes e datasets, ações, timers, handlers de mensagens e chamadas que atravessam para outra unit. Inclua OnCreate, OnShow, OnCloseQuery, OnChange, OnExit, OnClick, OnExecute, BeforeEdit, BeforePost, AfterPost, OnCalcFields e handlers de exceção. Procure atribuições como Button.OnClick :=, pois algumas aplicações refazem as ligações do comportamento depois da construção.

Depois, acrescente os caminhos implícitos. Registre quais controles apontam para cada TDataSource, qual dataset cada fonte expõe, se AutoEdit está ativado e quais objetos TField persistentes têm eventos de validação ou mudança. A documentação de TDataSource da Embarcadero descreve o componente como o canal entre um dataset e controles conscientes de dados. Essa palavra modesta, canal, é o aviso: uma tecla pode cruzar o limite da interface antes que qualquer botão Salvar seja executado.

Monte uma tabela com uma linha por comportamento observado, não uma linha por método. Estas colunas revelam a maioria das armadilhas:

Trigger              Reads                    Writes           Rule owner
btnPostClick         invoice fields, role     invoice status   posting policy
AmountFieldValidate  amount, currency         record buffer    money rule
CustomerDataChange   current customer         filter params    query orchestration

A coluna Evidence exige precisão. O nome de um handler não é evidência. Capture valores de entrada, estado do dataset, chamadas SQL, linhas retornadas, mensagens e valores finais armazenados. Se um handler depende da ordem em que eventos VCL disparam, registre essa ordem. A sequência faz parte do comportamento até que se prove que usuários e integrações não conseguem observá-la.

Formulários herdados merecem uma verificação separada. Um DFM filho pode substituir uma propriedade e continuar herdando um evento de um ancestral que fica em outro diretório do projeto. A unit filha pode parecer inofensiva mesmo que o formulário base abra datasets ou altere permissões em OnShow. Expanda a cadeia de herança e registre a configuração efetiva dos componentes na aplicação compilada, não apenas o texto guardado em um arquivo.

Actions também escondem reutilização. Um mesmo TAction.OnExecute pode ser acionado por um item de menu, um botão da barra de ferramentas e um atalho, enquanto OnUpdate decide a disponibilidade pelo estado global. Se o novo cliente copiar somente o botão visível, usuários de teclado podem perder um caminho e a autorização pode virar um simples sinal visual de desativado. Trate a permissão de execução como uma regra no limite do comando e o estado habilitado como uma visão dessa decisão.

Extraia decisões antes de mover controles

Extraia primeiro decisões e cálculos puros, pois eles oferecem pontos de separação estáveis sem perturbar a tela. Deixe o handler de evento onde está, mas reduza-o a coletar a entrada, chamar uma regra e apresentar o resultado. A aplicação em funcionamento continua útil enquanto a regra passa a ser chamada sem formulário.

Imagine que um formulário de pedidos calcula uma decisão de crédito dentro de btnApproveClick. O handler original lê campos, verifica um indicador do cliente, compara totais, atualiza controles, faz o post do dataset e exibe uma mensagem. Separe a decisão desses efeitos:

type
  TApprovalInput = record
    OrderTotal: Currency;
    CreditLimit: Currency;
    AccountOnHold: Boolean;
  end;

  TApprovalDecision = record
    Allowed: Boolean;
    ReasonCode: string;
  end;

function DecideApproval(const Input: TApprovalInput): TApprovalDecision;
begin
  if Input.AccountOnHold then
    Exit(TApprovalDecision.Create(False, 'ACCOUNT_HOLD'));
  if Input.OrderTotal > Input.CreditLimit then
    Exit(TApprovalDecision.Create(False, 'LIMIT_EXCEEDED'));
  Result := TApprovalDecision.Create(True, 'APPROVED');
end;

A sintaxe exata do record talvez precise de ajustes para a versão de Delphi do ambiente. O projeto é o que importa: a função recebe valores, devolve uma decisão e não sabe nada sobre TEdit, TField, resultados modais ou transações. Um teste unitário consegue cobri-la, uma importação em lote pode chamar uma operação equivalente e um serviço de destino pode implementar o mesmo contrato.

Não mova o handler antigo intacto para uma classe chamada TOrderService. Um método que recebe um formulário ou alcança um módulo de dados global continua acoplado à interface. Passar vinte controles como parâmetros apenas esconde esse acoplamento na assinatura. Defina entradas em termos do negócio e inclua contexto suficiente para tornar a decisão determinística.

Também resista a extrair helpers compartilhados cedo demais. Dois handlers que calculam imposto podem ser diferentes porque um trata notas de crédito e o outro trata faturas anteriores a uma mudança de regra. Primeiro caracterize os dois comportamentos. Una-os apenas quando as evidências mostrarem que a diferença é acidental.

Controles de dados criam um caminho invisível de escrita

Controles conscientes de dados exigem um modelo explícito de edição no substituto porque combinam apresentação, navegação, buffer, validação e persistência. Um campo de navegador ligado a JSON não é um substituto equivalente para TDBEdit conectado por TDataSource a um TDataSet ativo.

O primeiro comportamento oculto é a entrada no modo de edição. A Embarcadero documenta que TDataSource.AutoEdit é true por padrão e chama o método Edit do dataset quando um usuário tenta modificar um controle ligado. A aplicação original pode, portanto, bloquear uma linha, marcar um registro como alterado ou habilitar as ações Post e Cancel na primeira tecla. Uma interface nova que espera um Salvar explícito tem outro modelo de concorrência, mesmo que os campos pareçam idênticos.

O segundo comportamento oculto é o buffer. Um valor de campo exibido pode não ser nem o último valor confirmado no banco nem o valor visto por outro controle depois de um evento. Edit, Insert, Post, Cancel, atualizações em cache e configurações do provider determinam quando as mudanças se tornam permanentes. Escreva esses estados como uma pequena máquina de estados. Por exemplo: View permite navegação; Edit guarda um rascunho local; Saving valida o rascunho e envia um comando; Conflict preserva o rascunho do usuário enquanto mostra a versão mais recente do servidor.

O terceiro comportamento é a posição da validação. Uma máscara de edição verifica caracteres durante a entrada. O OnValidate de um campo verifica um valor completo pouco antes de entrar no buffer do registro. BeforePost pode inspecionar o registro inteiro. Restrições do banco atuam depois e podem cobrir relações que nenhum formulário conhece. A documentação de TField.OnValidate da Embarcadero observa explicitamente que uma atribuição programática ignora EditMask, enquanto OnValidate ainda verifica o campo antes do post. É uma boa razão para mover regras permanentes para baixo do nível do widget.

Use quatro grupos ao reposicionar a validação:

  1. Assistência à entrada, como formatação e resposta imediata a caracteres, fica no cliente.
  2. Invariantes de campo, como um conjunto permitido de códigos, vivem na operação de domínio e também podem rodar no cliente para responder depressa.
  3. Regras entre campos e de autorização rodam no servidor ou no limite da aplicação que controla o comando.
  4. Restrições referenciais e de unicidade continuam impostas pelo Postgres, mesmo quando verificações mais amigáveis rodam antes.

Duplicar uma regra para resposta imediata só é aceitável se uma implementação continuar sendo a autoridade. O servidor precisa rejeitar um comando inválido independentemente do que o cliente verificou. Caso contrário, uma importação, uma integração ou um cliente desatualizado pode contornar o negócio.

A ligação mestre-detalhe acrescenta outra armadilha. Mover o cursor mestre pode mudar parâmetros e atualizar linhas de detalhe automaticamente. Os usuários talvez vejam isso como uma área de trabalho coerente, mas a implementação depende da posição do cursor, não de um identificador explícito. O substituto deve solicitar detalhes pelo ID mestre, manter a seleção no cliente e decidir o que acontece com um rascunho de detalhe não salvo quando o mestre muda.

Campos calculados e de lookup também precisam de um responsável. Um campo calculado usado apenas para exibição pertence a uma projeção de consulta ou a um view model. Se outra regra o lê, mova o cálculo para a operação de domínio e teste suas entradas. Um campo de lookup pode esconder uma ida ao banco ou um valor antigo no cache, então registre se o comportamento atual vê dados em tempo real, dados do momento da abertura ou dados atualizados por um evento específico.

Eventos de dataset não são um modelo de domínio

Substitua VCL com evidências
O tráfego de produção gravado orienta testes de paridade da aplicação reescrita.

Mover datasets para um TDataModule melhora a organização, mas não separa sozinho a lógica de negócio. A Embarcadero descreve TDataModule como lugar para centralizar componentes não visuais e até permite regras de negócio ali. Esse conselho reduz a bagunça dos formulários. Não cria limites, entradas explícitas nem casos de uso testáveis de forma independente.

Eventos de dataset muitas vezes misturam três trabalhos. BeforePost pode validar um invariante, preencher campos de auditoria e executar outra consulta. AfterScroll pode atualizar um dataset de detalhe e habilitar uma ação. OnCalcFields pode calcular um valor de exibição que outro handler depois trata como autoridade. Copiar esses eventos para um repositório ou hook de ORM recria a mesma ambiguidade.

Classifique cada evento pelo que o causa e pelo que garante. Uma operação de domínio deve rodar porque alguém solicitou ApproveOrder, não porque um dataset genérico fez post. Um repositório deve persistir um pedido aprovado, não decidir se a aprovação é permitida. Um view model pode calcular texto de exibição, mas totais persistidos devem vir da regra responsável por cálculos monetários.

Existe um caso incômodo: código de terceiros pode chamar Post diretamente e depender de BeforePost para proteger o registro. Não apague essa proteção durante a extração. Coloque a regra em uma unit chamável, faça BeforePost invocá-la e encaminhe novos comandos pela mesma regra. Remova o evento antigo apenas quando os rastros mostrarem que todo caminho de escrita usa o novo limite.

Módulos de dados globais exigem cuidado especial. Um formulário pode supor que dmMain.qryCustomer já está aberto, posicionado no mesmo cliente e dentro de uma transação iniciada em outro lugar. Isso é estado mutável compartilhado. Transforme essas precondições em identificadores e escopos de transação explícitos. Passar CustomerId é mais seguro do que passar a linha atual de um dataset cujo cursor outro evento pode mover.

As transações devem seguir o caso de uso

Os limites de transação devem envolver uma operação de negócio, não um handler de botão ou cada post de dataset. O código antigo pode iniciar uma transação em um evento, alterar vários datasets por chamadas aninhadas e confirmar em outro evento. Dividir essa sequência entre requisições HTTP pode deixar trabalho parcial que a aplicação desktop nunca permitiu.

Rastreie uma operação bem-sucedida e cada falha relevante. Registre instruções SQL, chamadas de stored procedures, identificadores gerados, comportamento de bloqueios e o ponto de commit ou rollback. Depois, nomeie a operação na linguagem do negócio. ClosePeriod pode atualizar o registro do período, criar lançamentos contábeis e rejeitar rascunhos pendentes. Essas mudanças pertencem a um comando de aplicação, mesmo que a VCL as alcance por três formulários.

Defina uma requisição e um resultado antes de escolher detalhes de transporte:

{
  "operation": "ApproveOrder",
  "order_id": 4812,
  "expected_version": 17,
  "actor_id": 204,
  "decision_input": {
    "order_total": "1250.00",
    "currency": "EUR"
  }
}

Um resultado bem-sucedido deve devolver a nova versão, o status resultante e códigos de motivo estáveis. Um conflito deve retornar a versão atual sem sobrescrevê-la silenciosamente. O valor decimal é uma string para evitar que uma decisão monetária vire um acidente de ponto flutuante no cliente TypeScript.

Não exponha um endpoint genérico UpdateOrder que aceite todas as colunas. Ele transfere a abstração do dataset pela rede e convida chamadores a criar estados que o formulário antes impedia. Comandos como ApproveOrder, ReleaseHold e ChangeDeliveryDate revelam a intenção e dão a cada transação um limite defensável.

Stored procedures complicam a responsabilidade, mas não o método. Se uma procedure contém regras, caracterize entradas, saídas, mudanças e erros como parte do sistema atual. Mantenha-a atrás de um adaptador primeiro. Reescreva apenas depois que testes de paridade cobrirem o comportamento, especialmente quando o código Delphi interpreta códigos de erro do fornecedor ou depende de efeitos colaterais de triggers.

Testes de caracterização são a primeira especificação

Dê limites aos controles de dados
A reescrita separa a interação TypeScript das regras autoritativas do serviço e do banco.

Testes de caracterização devem comparar resultados observáveis da aplicação antiga com os resultados da operação extraída ou reescrita. Testes unitários escritos a partir de requisitos lembrados serão úteis depois, mas não mostram qual comportamento não documentado já sustenta o negócio.

Capture tráfego de produção representativo quando a política permitir, remova ou proteja dados sensíveis e transforme cada operação em um caso repetível. Em uma aplicação desktop, tráfego inclui mais do que requisições de rede. Registre o estado inicial do banco ou um fixture estável, entradas do usuário, permissões relevantes, ação chamada, mensagens ou códigos de motivo, efeitos SQL e linhas finais. Acrescente casos de cancelamento, cliques duplicados, registros desatualizados, valores nulos, limites de arredondamento e falhas do banco.

Um fixture compacto facilita a revisão:

case: approve-order-over-limit
given:
  order_id: 4812
  order_total: "1250.00"
  credit_limit: "1000.00"
  account_on_hold: false
when: ApproveOrder
expect:
  allowed: false
  reason_code: LIMIT_EXCEEDED
  order_status: DRAFT
  committed_writes: 0

Execute o caso contra uma instância controlada do caminho Delphi e contra o novo caminho. Compare resultados de negócio, estado persistido e efeitos colaterais relevantes. Não compare diferenças incidentais, como timestamps gerados, a menos que afetem um contrato. Normalize identificadores gerados pelo banco quando a identidade em si não tiver significado.

Testes de golden master têm limites. O sistema antigo pode estar errado, e preservar cegamente todo defeito o congela. Marque discrepâncias como paridade esperada, correção aprovada ou diferença não resolvida. Uma correção aprovada precisa de responsável nomeado e teste do comportamento pretendido. Sem isso, desenvolvedores chamarão surpresas de correções e revisores perderão a capacidade de distinguir migração de redesenho.

Tempo e localidade merecem fixtures deliberados. Aplicações Delphi muitas vezes convertem datas pelas configurações da estação de trabalho e arredondam moedas em pontos diferentes, por tipos do banco, tipos de campo e formatos de exibição. Inclua valores de fim de dia, mudanças de horário quando timestamps importam, metades decimais, strings vazias e nulos. Verifique valores armazenados e códigos de motivo, não rótulos formatados, exceto quando o próprio rótulo for um campo de documento contratual.

Teste também a supressão de eventos. O código pode desabilitar controles temporariamente, desligar um evento ou definir uma flag de carregamento para impedir atualizações recursivas. A nova operação não deve reproduzir esses truques mecânicos, mas seu resultado final precisa coincidir. Uma reprodução que grava apenas a requisição correta e a linha final pode ignorar efeitos colaterais duplicados no meio da sequência.

É aqui que CodeHero usa um harness de paridade contra tráfego de produção gravado. O princípio útil não depende da nossa plataforma: substituição é um problema de evidências, e semelhança de telas é evidência fraca.

Uma conversão direta da interface preserva o limite caro

Uma conversão direta da interface costuma ser a rota mais cara porque reconstrói telas antes de descobrir as operações sob elas. Equipes reproduzem abas, caixas de diálogo modais, grades e navegação, depois ligam tudo a endpoints CRUD genéricos. Cada regra oculta aparece tarde como erro de interface, exceção de API ou discussão sobre o que Salvar significava.

A tela copiada também leva premissas do desktop para um sistema distribuído. A aplicação VCL pode manter um cursor de dataset ativo, compartilhar uma conexão e responder de modo síncrono a eventos de campo. Um cliente web enfrenta latência, novas tentativas, edições simultâneas, sessões vencidas e requisições que podem chegar duas vezes. Simular um dataset com estado sobre HTTP cria APIs tagarelas e lógica frágil no cliente.

Paridade de pixels, portanto, é o critério de aceitação errado. Preserve paridade de tarefas e de negócio. O usuário ainda precisa conseguir aprovar o pedido certo, ver por que uma decisão falhou, recuperar-se de um conflito e concluir o trabalho com as informações necessárias. A nova tela pode combinar diálogos antigos ou remover etapas de navegação se a operação e seus controles continuarem claros.

Há casos em que uma conversão leve da interface faz sentido. Se o objetivo imediato é compatibilidade com o sistema operacional, o banco e as integrações permanecerão iguais e o formulário tem pouca lógica de negócio, um substituto desktop compatível pode ganhar tempo. Trate-o como contenção com vida útil declarada. Não o chame de separação arquitetural.

Para a maioria dos sistemas longevos, defina o destino em torno de comandos, consultas e estado explícito. Serviços Go combinam com operações transacionais da aplicação e acesso a dados. Rust faz sentido para núcleos numéricos onde comportamento exato e desempenho merecem um limite estreito. Clientes TypeScript devem controlar estado de interação e apresentação, enquanto Postgres impõe restrições relacionais duráveis. Essas são opções descritas no contexto de projeto fornecido, não uma ordem para que todo ambiente Delphi use as quatro.

Faça a mudança por operação, não por formulário

Liberte as regras dos formulários
CodeHero reescreve o comportamento de formulários Delphi como serviços Go e clientes TypeScript explícitos.

Mude uma operação de negócio por vez porque formulários raramente correspondem a limites limpos de serviço. Um formulário de pedido pode conter busca de clientes, formação de preço, aprovação, impressão e pagamentos. Substituir o formulário inteiro obriga os cinco caminhos a ficarem prontos juntos e cria uma unidade grande de rollback.

Escolha uma operação com entradas claras, resultados mensuráveis e pouco estado compartilhado. Coloque uma interface diante da implementação antiga e depois acrescente a nova implementação atrás do mesmo contrato. O formulário VCL pode chamar esse limite antes de o novo cliente existir. Isso produz evidência de que a separação funciona sem amarrar a extração do domínio à reconstrução visual.

Uma sequência prática tem cinco partes:

  1. Grave o comportamento atual e os casos de falha da operação escolhida.
  2. Extraia entradas de regras e códigos de resultado enquanto o formulário ainda controla a apresentação.
  3. Coloque a persistência atrás de um adaptador e defina o limite da transação.
  4. Reproduza casos de paridade contra as duas implementações e classifique cada diferença.
  5. Direcione um conjunto controlado de chamadas ao novo caminho, com um interruptor explícito de rollback.

Evite gravações duplas, exceto se puder torná-las idempotentes e reconciliá-las. Ler dos dois sistemas para comparar é mais seguro do que deixar ambos alterarem dados autoritativos. Se uma execução em sombra disparar e-mails, pagamentos, tarefas de impressão ou registros de auditoria, substitua esses efeitos por gravadores no ambiente de comparação.

Faça do rollback uma decisão de roteamento por operação. Se o novo caminho de aprovação falhar, mande a aprovação de volta à implementação antiga sem reverter buscas de clientes já migradas. Mantenha explícita a compatibilidade do banco enquanto os dois caminhos funcionam. Uma mudança de esquema que impeça o executável antigo de ler uma linha remove seu rollback, mesmo que a feature flag ainda exista.

Observe resultados de negócio durante a mudança. Conte códigos de motivo, conflitos, cancelamentos e operações concluídas e compare suas distribuições com a referência gravada. Taxas brutas de erro são imprecisas demais: um caminho novo pode retornar sucesso HTTP enquanto aprova pedidos rejeitados pelas regras antigas. Investigue mudanças nas decisões antes de aumentar o tráfego.

A dependência mais difícil deve determinar a ordem. Se a aprovação depende da formação de preço, extraia o preço primeiro ou mantenha-o atrás de um adaptador chamado pelas duas versões. Não migre telas fáceis deixando a transação central como surpresa final. Isso produz progresso visível e pouca redução de risco.

CodeHero lê toda a base de código Delphi, inclusive as linguagens conectadas, e moderniza a arquitetura mantendo o comportamento fiel ao original. Para um programa manual, vale a mesma disciplina: mapear globalmente, cortar localmente e nunca deduzir segurança de uma tela compilada.

O substituto deve impedir o estado oculto

O destino está pronto quando decisões de negócio não dependem mais de um controle, da linha atual do dataset, da ordem de criação dos formulários ou de uma transação global implícita. Deve ser possível chamar uma operação com entrada serializada, observar um resultado estável e testá-la sem construir uma janela.

Esse padrão revela extrações incompletas. Um serviço que lê Screen.ActiveForm, um repositório que executa políticas em um hook genérico de gravação ou um cliente TypeScript que calcula o total autoritativo da fatura ainda carrega o problema antigo. Os nomes mudaram, mas o comportamento continua escondido atrás de eventos de infraestrutura.

Mantenha uma lista curta de limites na revisão de código:

  • A operação aceita valores e identificadores de negócio, em vez de controles ou cursores de dataset?
  • Toda regra permanente pode rodar para chamadas da interface, de importação e da API?
  • Uma transação cobre toda a mudança de negócio?
  • Os resultados de concorrência e repetição são explícitos?
  • Um caso gravado consegue provar paridade sem comparar pixels?

Algum comportamento ficará na casca VCL durante a transição, e isso não é um problema. Gerenciamento de foco, atalhos de teclado, layout e apresentação do rascunho local não precisam de abstração precoce. O limite a defender é a autoridade: a apresentação pode propor e explicar uma mudança, mas uma operação de aplicação decide e confirma.

Não comece redesenhando o maior formulário. Escolha a operação dentro dele cuja falha custa mais, capture as evidências atuais e dê um nome a ela. Quando essa operação rodar sem o formulário, o restante da modernização terá um limite sobre o qual construir.

Perguntas frequentes

Como sei se um handler Delphi contém lógica de negócio?

Imagine a mesma operação chamada por uma importação ou API sem construir o formulário. Decisões que ainda precisam valer são regras de negócio; mudanças de foco, diálogos e layout continuam sendo apresentação.

Devo mover primeiro o código do formulário para um TDataModule?

Um TDataModule pode organizar o formulário, mas não cria um limite de negócio. Mova regras para units com valores de entrada e resultados explícitos, então faça formulário e módulo chamarem as mesmas regras.

É seguro trocar controles VCL de dados por campos web comuns?

Somente depois de modelar o comportamento oculto de edição, buffer, validação, post e cancelamento. Campos parecidos na tela não garantem as mesmas regras de concorrência ou persistência.

Onde ficam as regras Delphi OnValidate em um sistema novo?

Coloque invariantes duráveis de campo na operação de aplicação ou domínio usada por todos os chamadores. O cliente pode repetir uma verificação para responder rápido, mas não pode ser a autoridade.

Como migrar com segurança a lógica de BeforePost?

Extraia a regra para uma unit chamável e mantenha BeforePost chamando-a enquanto existirem caminhos antigos de escrita. Remova o evento apenas quando os rastros provarem que toda escrita passa pelo novo limite.

Por que uma conversão direta da interface Delphi custa tanto?

Ela reconstrói telas antes de expor as operações e o estado implícito que ficam abaixo. A equipe paga pela reprodução visual e depois pelas mudanças de interface e API impostas pelas regras escondidas.

Preciso preservar todo defeito para ter paridade de comportamento?

Não. Classifique cada diferença como paridade necessária, correção aprovada ou comportamento não resolvido. Uma correção precisa de responsável e teste para a migração não virar um redesenho sem revisão.

Testes unitários substituem casos de produção gravados?

Testes unitários provam as regras que você conhece e sabe escrever. Casos gravados expõem ordens de eventos, formatos de dados e efeitos esquecidos, então use os dois para fins diferentes.

A nova API deve expor endpoints CRUD genéricos?

Evite atualizações genéricas em registros cheios de regras. Comandos como ApproveOrder expressam intenção, definem o limite transacional e impedem que chamadores montem estados inválidos campo a campo.

Qual é a unidade mais segura para mudar uma aplicação Delphi?

Mude uma operação de negócio nomeada, com entradas, resultados e rollback claros, mesmo que atravesse vários formulários. Um formulário inteiro costuma ser amplo demais e um handler de campo estreito demais.