🎯 Objetivo
Criar um sistema Delphi capaz de:
- Interpretar um arquivo JSON com ações e decisões
- Executar comandos registrados dinamicamente
- Manter um contexto de variáveis (como memória de trabalho)
- Permitir fluxos condicionais (if/else)
- Permitir integração de plugins (ações customizadas)
🧱 Estrutura do Projeto
1. Classes principais
| Classe / Unidade | Responsabilidade |
|---|---|
TDynaEngine | Gerencia execução do JSON |
TActionRegistry | Registro de ações (Dictionary<string, TProc>) |
TExecutionContext | Armazena variáveis durante a execução |
TDynaInterpreter | Lê e executa o JSON como script |
IDynaAction (opcional) | Interface padrão para ações plugináveis |
📦 Exemplo de Fluxo JSON
[
{ "acao": "DefinirVariavel", "nome": "saldo", "valor": 500 },
{
"condicional": {
"se": { "variavel": "saldo", "operador": ">=", "valor": 100 },
"entao": [
{ "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Compra aprovada" }
],
"senao": [
{ "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Saldo insuficiente" }
]
}
}
]🧪 Ações já implementadas na engine
| Ação | Descrição |
|---|---|
DefinirVariavel | Define uma variável no contexto |
EnviarEmail | Simula envio de e-mail com log |
MostrarMensagem | Mostra mensagem na tela |
AtualizarStatus | Atualiza uma variável de controle |
🛠 Tecnologia
- Delphi FMX (ou VCL, se preferir)
- FireDAC para JSON (ou System.JSON)
TDictionary<string, Variant>para contexto- Código 100% reaproveitável e desacoplado
📁 Organização da Pasta do Projeto
DynaFlow/
├── Engine/
│ ├── DynaEngine.pas
│ ├── DynaInterpreter.pas
│ ├── ActionRegistry.pas
│ └── ExecutionContext.pas
├── Plugins/
│ ├── ActionEmail.pas
│ ├── ActionMensagens.pas
│ └── ...
├── Forms/
│ └── MainForm.pas
├── Sample/
│ └── fluxo_exemplo.json🚀 Etapas de Desenvolvimento
- Criar estrutura base (
TDynaEngine,TActionRegistry,TExecutionContext) - Implementar leitura e execução de JSON
- Criar ações básicas e registrá-las
- Executar JSON a partir do botão na UI
- Mostrar variáveis e logs de execução
unit DynaEngine;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections, System.JSON;
type
TExecutionContext = class
private
FVars: TDictionary<string, Variant>;
public
constructor Create;
destructor Destroy; override;
procedure SetVar(const Name: string; const Value: Variant);
function GetVar(const Name: string): Variant;
function TryGetVar(const Name: string; out Value: Variant): Boolean;
procedure Clear;
end;
TActionProc = reference to procedure(const Params: TJSONObject; const Context: TExecutionContext);
TActionRegistry = class
private
FActions: TDictionary<string, TActionProc>;
public
constructor Create;
destructor Destroy; override;
procedure RegisterAction(const Name: string; Proc: TActionProc);
function Execute(const Name: string; const Params: TJSONObject; const Context: TExecutionContext): Boolean;
function Contains(const Name: string): Boolean;
end;
implementation
{ TExecutionContext }
constructor TExecutionContext.Create;
begin
FVars := TDictionary<string, Variant>.Create;
end;
destructor TExecutionContext.Destroy;
begin
FVars.Free;
inherited;
end;
procedure TExecutionContext.Clear;
begin
FVars.Clear;
end;
function TExecutionContext.GetVar(const Name: string): Variant;
begin
Result := FVars[Name];
end;
procedure TExecutionContext.SetVar(const Name: string; const Value: Variant);
begin
FVars.AddOrSetValue(Name, Value);
end;
function TExecutionContext.TryGetVar(const Name: string; out Value: Variant): Boolean;
begin
Result := FVars.TryGetValue(Name, Value);
end;
{ TActionRegistry }
constructor TActionRegistry.Create;
begin
FActions := TDictionary<string, TActionProc>.Create;
end;
destructor TActionRegistry.Destroy;
begin
FActions.Free;
inherited;
end;
function TActionRegistry.Contains(const Name: string): Boolean;
begin
Result := FActions.ContainsKey(Name);
end;
procedure TActionRegistry.RegisterAction(const Name: string; Proc: TActionProc);
begin
FActions.AddOrSetValue(Name.ToLower, Proc);
end;
function TActionRegistry.Execute(const Name: string; const Params: TJSONObject; const Context: TExecutionContext): Boolean;
var
Proc: TActionProc;
begin
if FActions.TryGetValue(Name.ToLower, Proc) then
begin
Proc(Params, Context);
Exit(True);
end;
Result := False;
end;
end.
Pronto! A unidade DynaEngine já está criada com:
TExecutionContext: armazena variáveis em tempo de execução.TActionRegistry: permite registrar e executar ações dinamicamente.- Tipagem genérica e compatível com Delphi FMX.
Agora podemos seguir com:
DynaInterpreter.pas: leitura e execução do JSON (interpretador principal)
unit DynaInterpreter;
interface
uses
System.SysUtils, System.Classes, System.JSON, System.Variants,
DynaEngine;
type
TDynaInterpreter = class
private
FRegistry: TActionRegistry;
FContext: TExecutionContext;
public
constructor Create(ARegistry: TActionRegistry; AContext: TExecutionContext);
procedure ExecuteJson(const AJson: TJSONArray);
procedure ExecuteItem(const Item: TJSONValue);
procedure ExecuteConditional(const CondObj: TJSONObject);
end;
function EvaluateCondition(const Context: TExecutionContext; const Cond: TJSONObject): Boolean;
implementation
function EvaluateCondition(const Context: TExecutionContext; const Cond: TJSONObject): Boolean;
var
VarName, Operator: string;
ExpectedValue, ActualValue: Variant;
CompareResult: Integer;
begin
VarName := Cond.GetValue<string>('variavel');
Operator := Cond.GetValue<string>('operador');
ExpectedValue := Cond.GetValue('valor').Value;
if not Context.TryGetVar(VarName, ActualValue) then
Exit(False);
CompareResult := VarCompareValue(ActualValue, ExpectedValue);
if Operator = '=' then
Result := CompareResult = 0
else if Operator = '>' then
Result := CompareResult > 0
else if Operator = '<' then
Result := CompareResult < 0
else if Operator = '>=' then
Result := CompareResult >= 0
else if Operator = '<=' then
Result := CompareResult <= 0
else if Operator = '<>' then
Result := CompareResult <> 0
else
Result := False;
end;
{ TDynaInterpreter }
constructor TDynaInterpreter.Create(ARegistry: TActionRegistry; AContext: TExecutionContext);
begin
FRegistry := ARegistry;
FContext := AContext;
end;
procedure TDynaInterpreter.ExecuteJson(const AJson: TJSONArray);
var
Item: TJSONValue;
begin
for Item in AJson do
ExecuteItem(Item);
end;
procedure TDynaInterpreter.ExecuteItem(const Item: TJSONValue);
var
Obj: TJSONObject;
ActionName: string;
begin
if Item.TryGetValue<TJSONObject>(Obj) then
begin
if Obj.TryGetValue('acao', ActionName) then
begin
if not FRegistry.Execute(ActionName, Obj, FContext) then
Writeln('Ação não encontrada: ' + ActionName);
end
else if Obj.Contains('condicional') then
begin
ExecuteConditional(Obj.GetValue<TJSONObject>('condicional'));
end;
end;
end;
procedure TDynaInterpreter.ExecuteConditional(const CondObj: TJSONObject);
var
Condicao: TJSONObject;
Entao, Senao: TJSONArray;
Resultado: Boolean;
begin
Condicao := CondObj.GetValue<TJSONObject>('se');
Resultado := EvaluateCondition(FContext, Condicao);
if Resultado then
begin
if CondObj.TryGetValue('entao', Entao) then
ExecuteJson(Entao);
end
else
begin
if CondObj.TryGetValue('senao', Senao) then
ExecuteJson(Senao);
end;
end;
end.
O módulo DynaInterpreter está pronto! Ele interpreta e executa:
- Sequências JSON com ações ou blocos condicionais
- Condições baseadas em variáveis do
ExecutionContext - Comparações com operadores básicos (
=,>,<, etc.)
Agora podemos seguir com a próxima etapa:
- Criar ações básicas (
DefinirVariavel,MostrarMensagem,EnviarEmail) e registrá-las. - Criar uma unit
DynaActions.paspara conter essas ações. - Depois, construir a interface FMX para rodar e testar os fluxos.
Deseja que eu continue com a unit DynaActions.pas?
unit DynaActions;
interface
uses
System.SysUtils, System.JSON, FMX.Dialogs, DynaEngine;
procedure RegisterDefaultActions(Registry: TActionRegistry);
implementation
procedure RegisterDefaultActions(Registry: TActionRegistry);
begin
Registry.RegisterAction('definirvariavel',
procedure(const Params: TJSONObject; const Context: TExecutionContext)
var
Nome: string;
Valor: TJSONValue;
begin
if Params.TryGetValue('nome', Nome) and Params.TryGetValue('valor', Valor) then
Context.SetVar(Nome, Valor.Value);
end);
Registry.RegisterAction('mostrarmensagem',
procedure(const Params: TJSONObject; const Context: TExecutionContext)
var
Msg: string;
begin
if Params.TryGetValue('mensagem', Msg) then
ShowMessage(Msg);
end);
Registry.RegisterAction('enviaremail',
procedure(const Params: TJSONObject; const Context: TExecutionContext)
var
Para, Mensagem: string;
begin
Para := Params.GetValue<string>('para');
if Params.TryGetValue('mensagem', Mensagem) then
ShowMessage(Format('Simulando envio de e-mail para %s: %s', [Para, Mensagem]))
else
ShowMessage(Format('Simulando envio de e-mail para %s.', [Para]));
end);
end;
end.
✅ Ações Registradas em DynaActions.pas
DefinirVariavel
Define uma variável no contexto:
{ "acao": "DefinirVariavel", "nome": "saldo", "valor": 150 }- Exibe uma mensagem com
ShowMessage:
{ "acao": "MostrarMensagem", "mensagem": "Operação concluída." }- EnviarEmail: Simula envio de e-mail (mostra via
ShowMessage):
{ "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Sua compra foi aprovada." }
🎯 Próximos passos
Agora podemos:
- Criar um
FormFMX simples com:- Um
TMemoouTEditpara inserir JSON - Um botão para executar o fluxo
- Um log de saída (opcional)
- Um
- Incluir um arquivo
.jsonde exemplo com um fluxo completo: definição de variável, condicional, ação.
Deseja que eu crie agora o MainForm.pas com essa interface básica para execução e testes?
unit MainForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo, FMX.Layouts, FMX.Objects,
System.JSON, DynaEngine, DynaInterpreter, DynaActions;
type
TFormMain = class(TForm)
MemoJson: TMemo;
BtnExecutar: TButton;
LayoutMain: TLayout;
procedure FormCreate(Sender: TObject);
procedure BtnExecutarClick(Sender: TObject);
private
FEngine: TDynaInterpreter;
FRegistry: TActionRegistry;
FContext: TExecutionContext;
public
end;
var
FormMain: TFormMain;
implementation
{$R *.fmx}
procedure TFormMain.FormCreate(Sender: TObject);
begin
FRegistry := TActionRegistry.Create;
FContext := TExecutionContext.Create;
FEngine := TDynaInterpreter.Create(FRegistry, FContext);
RegisterDefaultActions(FRegistry);
MemoJson.Lines.Text :=
'[
' +
' { "acao": "DefinirVariavel", "nome": "saldo", "valor": 150 },
' +
' {
' +
' "condicional": {
' +
' "se": { "variavel": "saldo", "operador": ">=", "valor": 100 },
' +
' "entao": [
' +
' { "acao": "MostrarMensagem", "mensagem": "Compra aprovada" },
' +
' { "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Sua compra foi aprovada." }
' +
' ],
' +
' "senao": [
' +
' { "acao": "MostrarMensagem", "mensagem": "Saldo insuficiente" },
' +
' { "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Saldo insuficiente para compra." }
' +
' ]
' +
' }
' +
' }
' +
']';
end;
procedure TFormMain.BtnExecutarClick(Sender: TObject);
var
Json: TJSONArray;
begin
try
Json := TJSONObject.ParseJSONValue(MemoJson.Text) as TJSONArray;
if Assigned(Json) then
begin
FContext.Clear;
FEngine.ExecuteJson(Json);
Json.Free;
end
else
ShowMessage('JSON inválido.');
except
on E: Exception do
ShowMessage('Erro ao executar: ' + E.Message);
end;
end;
end.
O MainForm.pas está pronto! Com ele, você já pode:
- Editar um fluxo JSON direto no
TMemo - Executar o fluxo clicando em “Executar”
- Ver os efeitos das ações como mensagens na tela (e-mails simulados, mensagens, etc.)
🔧 O que falta para rodar:
- Criar o arquivo visual
.fmxcom:- Um
TMemochamadoMemoJson - Um
TButtonchamadoBtnExecutar - Um
TLayout(LayoutMain) como contêiner
- Um
- Adicionar as units
DynaEngine,DynaInterpreter,DynaActionsao projeto.
Se quiser, posso descrever o layout visual que você pode montar rapidamente no Delphi FMX Designer. Deseja isso?
Perfeito! Aqui está a descrição detalhada para você montar o layout visual do formulário principal no Delphi FMX Designer, de forma simples e funcional:
🎨 Layout Visual do MainForm.fmx
🧱 Componentes e propriedades
🔹 TLayout – LayoutMain
- Name:
LayoutMain - Align:
Client - Padding:
20,20,20,20
🔹 TMemo – MemoJson
- Parent:
LayoutMain - Name:
MemoJson - Align:
Client - Margins:
0,0,0,10 - WordWrap:
True - Lines.Text: (vazio ou será preenchido no
FormCreate)
🔹 TButton – BtnExecutar
- Parent:
LayoutMain - Name:
BtnExecutar - Text:
Executar - Align:
Bottom - Height:
50
🧭 Ordem de Criação
- Adicione um
TLayoutao form e definaAlign := Client. - Dentro do
TLayout, adicione:- Um
TMemocomAlign := ClienteMargins.Bottom := 10 - Um
TButtoncomAlign := Bottom,Height := 50, eText := Executar
- Um
✅ Resultado esperado
Você terá uma janela onde o usuário pode:
- Colar ou editar o JSON no memo
- Clicar em Executar e ver o resultado aparecer em caixas de mensagem (
ShowMessage)
Se quiser, posso preparar também um arquivo .json de exemplo externo ou um botão “Carregar JSON” a partir de um arquivo. Deseja isso como próximo passo?
unit JsonLoader;
interface
uses
System.SysUtils, System.Classes, System.JSON, System.IOUtils;
function LoadJsonFromFile(const AFileName: string): TJSONArray;
function LoadJsonTextFromFile(const AFileName: string): string;
implementation
function LoadJsonFromFile(const AFileName: string): TJSONArray;
var
JsonText: string;
begin
Result := nil;
if not FileExists(AFileName) then
Exit;
JsonText := TFile.ReadAllText(AFileName, TEncoding.UTF8);
Result := TJSONObject.ParseJSONValue(JsonText) as TJSONArray;
end;
function LoadJsonTextFromFile(const AFileName: string): string;
begin
Result := '';
if FileExists(AFileName) then
Result := TFile.ReadAllText(AFileName, TEncoding.UTF8);
end;
end.
Pronto! Criamos a unit JsonLoader.pas, com duas funções:
LoadJsonFromFile: retorna umTJSONArraycarregado a partir de um arquivo.jsonLoadJsonTextFromFile: retorna o conteúdo puro do JSON comostring(para preencher oTMemo)
🔄 Próximo passo: integrar no MainForm
Você pode adicionar um botão chamado BtnCarregar com:
- Text:
Carregar JSON - Align:
Bottom - Position.Y: acima do botão Executar
Depois, implemente algo assim:
uses JsonLoader;
procedure TFormMain.BtnCarregarClick(Sender: TObject);
var
FileName: string;
begin
FileName := 'fluxo_exemplo.json'; // ou um caminho escolhido via diálogo
MemoJson.Lines.Text := LoadJsonTextFromFile(FileName);
end;Se quiser, posso gerar esse botão e incluir o trecho completo no MainForm com evento configurado. Deseja isso agora?
unit MainForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo, FMX.Layouts, FMX.Objects,
System.JSON, DynaEngine, DynaInterpreter, DynaActions, JsonLoader;
type
TFormMain = class(TForm)
MemoJson: TMemo;
BtnExecutar: TButton;
LayoutMain: TLayout;
BtnCarregar: TButton;
procedure FormCreate(Sender: TObject);
procedure BtnExecutarClick(Sender: TObject);
procedure BtnCarregarClick(Sender: TObject);
private
FEngine: TDynaInterpreter;
FRegistry: TActionRegistry;
FContext: TExecutionContext;
public
end;
var
FormMain: TFormMain;
implementation
{$R *.fmx}
procedure TFormMain.FormCreate(Sender: TObject);
begin
FRegistry := TActionRegistry.Create;
FContext := TExecutionContext.Create;
FEngine := TDynaInterpreter.Create(FRegistry, FContext);
RegisterDefaultActions(FRegistry);
MemoJson.Lines.Text :=
'[
' +
' { "acao": "DefinirVariavel", "nome": "saldo", "valor": 150 },
' +
' {
' +
' "condicional": {
' +
' "se": { "variavel": "saldo", "operador": ">=", "valor": 100 },
' +
' "entao": [
' +
' { "acao": "MostrarMensagem", "mensagem": "Compra aprovada" },
' +
' { "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Sua compra foi aprovada." }
' +
' ],
' +
' "senao": [
' +
' { "acao": "MostrarMensagem", "mensagem": "Saldo insuficiente" },
' +
' { "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Saldo insuficiente para compra." }
' +
' ]
' +
' }
' +
' }
' +
']';
end;
procedure TFormMain.BtnExecutarClick(Sender: TObject);
var
Json: TJSONArray;
begin
try
Json := TJSONObject.ParseJSONValue(MemoJson.Text) as TJSONArray;
if Assigned(Json) then
begin
FContext.Clear;
FEngine.ExecuteJson(Json);
Json.Free;
end
else
ShowMessage('JSON inválido.');
except
on E: Exception do
ShowMessage('Erro ao executar: ' + E.Message);
end;
end;
procedure TFormMain.BtnCarregarClick(Sender: TObject);
var
FileName: string;
begin
FileName := 'fluxo_exemplo.json';
MemoJson.Lines.Text := LoadJsonTextFromFile(FileName);
end;
end.Agora o MainForm conta com:
- Botão
BtnCarregar: ao clicar, ele carrega o conteúdo do arquivofluxo_exemplo.jsonpara oTMemo. - Evento
BtnCarregarClick: já implementado com chamada aoLoadJsonTextFromFile.
🛠️ Ajustes no Designer FMX:
Adicione mais um botão:
🔹 TButton – BtnCarregar
- Name:
BtnCarregar - Text:
Carregar JSON - Align:
Bottom - Position.Y: acima do
BtnExecutar - Height:
50
Se quiser, posso te passar o conteúdo para o arquivo fluxo_exemplo.json como exemplo inicial. Deseja isso também?
[
{ "acao": "DefinirVariavel", "nome": "saldo", "valor": 150 },
{
"condicional": {
"se": { "variavel": "saldo", "operador": ">=", "valor": 100 },
"entao": [
{ "acao": "MostrarMensagem", "mensagem": "Compra aprovada" },
{ "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Sua compra foi aprovada." }
],
"senao": [
{ "acao": "MostrarMensagem", "mensagem": "Saldo insuficiente" },
{ "acao": "EnviarEmail", "para": "cliente@exemplo.com", "mensagem": "Saldo insuficiente para compra." }
]
}
}
]
📌 Onde salvar?
Coloque esse arquivo na mesma pasta do .exe (ou do projeto durante o desenvolvimento), com o nome: fluxo_exemplo.json
Perfeito. Um texto de fechamento é essencial para amarrar todas as ideias, valorizar o que foi construído e abrir caminho para evoluções. E sim: esse tipo de mini engine é extremamente útil em prototipagem, automação leve, engines de workflow e até sistemas configuráveis por analistas.
Segue uma sugestão de texto de encerramento para o projeto e a série até aqui:
🔚 Fechamento – Um motor leve com potência real
O que começamos como um experimento de desacoplamento evoluiu para algo muito mais poderoso: uma mini engine declarativa, capaz de interpretar comandos, tomar decisões e executar fluxos — tudo a partir de um arquivo JSON.
Com poucos arquivos e componentes, criamos:
- Uma estrutura de execução com variáveis em tempo real
- Registro e execução dinâmica de ações
- Condicionais com fallback
- Um interpretador simples, mas expressivo
- Uma interface funcional em Delphi FMX
🚀 E agora?
Esse projeto já está funcional, mas é só o começo.
Com pequenas extensões, ele pode evoluir para:
- 🔁 Loops declarativos: repetir ações até que uma condição seja satisfeita
- 🌿 Ramificações múltiplas: decisões complexas com múltiplos caminhos
- 🔌 Plugins externos: carregamento de DLLs ou units dinamicamente
- 🧩 Modularização: inclusão de arquivos JSON menores por referência
- 💡 Sistema de regras de negócio: editável por analistas sem recompilar
- 🧪 Testes automatizados de fluxos com dados simulados
💡 Por que isso importa?
Porque nem todo sistema precisa de um framework gigante para ser flexível.
Esse motor é:
- Leve
- Intuitivo
- Reaproveitável
- Ideal para protótipos, testes de conceito, automações internas
- E o melhor: você tem controle total sobre ele
Se você chegou até aqui, já tem tudo para começar a criar seus próprios interpretadores, DSLs leves e engines internas sob medida.
E quando o cliente pedir mudanças no comportamento do sistema... talvez você só precise editar um JSON. 😉
Se quiser, posso transformar esse texto final em artigo ou expandir isso para documentação ou README do projeto. Deseja isso também?