Mesmo que a nova aplicação resulte de uma migração de uma aplicação VCL atual que já é multiusuário e cliente/servidor, uma aplicação uniGUI atenderá múltiplos usuários dentro do mesmo processo.
Uma aplicação VCL cliente/servidor multiusuário geralmente tem várias instâncias do aplicativo cliente rodando em vários computadores, cada uma conectando-se a um servidor de banco de dados compartilhado. Quase todos os servidores de banco de dados suportam múltiplas conexões implementando bloqueio de linha/tabela quando necessário.
Esse tipo de aplicação VCL é uma aplicação típica de 2 camadas. Uma aplicação uniGUI similar é uma aplicação de 3 camadas, porque a interface do usuário (camada de apresentação) roda no navegador do cliente, a maior parte do código da aplicação anterior roda no servidor (agora atendendo múltiplos usuários/sessões) e compartilha o mesmo servidor de banco de dados.
O fato de o novo código não-visual da aplicação uniGUI atender múltiplos usuários exige o tratamento de acesso concorrente a quaisquer recursos compartilhados. Esse acesso concorrente ocorrerá de forma multithread, onde cada requisição vem de uma thread diferente.
ServerModule
Use o ServerModule singleton para informações globais e provavelmente apenas leitura.
A maioria das aplicações web exige acesso a um servidor de banco de dados. Às vezes usará controles de acesso a dados como TFDConnection, mas também é possível usar algum middleware. Em qualquer caso, a informação sobre como conectar ao servidor de banco de dados deve estar disponível para todas as sessões de usuário. Se estiver usando um TFDConnection ou similar, o programa precisará montar a string de conexão correta ou instanciar os componentes correspondentes.
Como mencionado anteriormente, se usar FireDAC devemos adicionar o TFDManager, o driver para o banco de dados escolhido, etc. Se usar um TADOConnection, basta ter a connection string.
Após criar o ServerModule, ele deve expor apenas propriedades de leitura para as sessões de usuário.
Como o ServerModule é um singleton, criado quando o servidor inicia, introduzir variáveis de leitura/gravação bloqueará o uso de qualquer tecnologia de balanceamento de carga. uniGUI planeja liberar seu Load Balance Server e assume nenhuma afinidade entre usuários e servidores. Se uma sessão precisar ser reiniciada, ela deve ser capaz de rodar em qualquer servidor disponível. Qualquer variável de leitura/gravação que tenha direito a ser compartilhada no ServerModule deve ser movida para o banco de dados compartilhado (que deverá cuidar do acesso concorrente por si só).
Se, apesar do aviso anterior, existir alguma razão muito importante para expor aqui uma propriedade de leitura/gravação, ela deve ser protegida assegurando que apenas uma sessão por vez possa modificá-la. A solução óbvia para esse tipo de proteção é usar uma seção crítica.
Neste caso, a connection string foi atribuída ao criar o ServerModule, e é uma propriedade somente leitura. No entanto, a SharedVariable é uma propriedade de leitura/gravação, e devemos garantir que apenas um usuário possa escrevê-la. Estamos reforçando o acesso único à variável usando uma seção crítica. Ainda mais, estamos ocultando esse fato no método SetSharedVariable para que não seja necessário conhecer, adquirir nem liberar o objeto TCriticalSection ao acessar a propriedade. Claro que, ao permitir modificar essa propriedade, também precisamos controlar as leituras. Qualquer propriedade de leitura/gravação desacelerará a aplicação (dependendo de quão frequentemente ela acessa a variável).
É importante notar que, embora você possa usar variáveis compartilhadas graváveis no ServerModule conforme definido acima, não é recomendado confiar nelas como uma variável global de toda a aplicação. No futuro, cada aplicação uniGUI pode ser dividida em vários processos menores para implementar balanceamento de carga e outros recursos avançados. Nesse caso, qualquer variável global compartilhada no ServerModule será visível apenas para seu processo proprietário, não para os outros processos que servirão a mesma aplicação em um pool de processos. Por isso, é recomendável manter tais variáveis globais em uma tabela de banco de dados em vez de mantê-las em memória.
Não confie em variáveis compartilhadas graváveis no ServerModule para estado de aplicação global. Armazene essas variáveis globais em uma tabela de banco de dados compartilhada para que permaneçam visíveis entre processos em um ambiente com balanceamento de carga.
MainModule
Use o MainModule para variáveis de sessão de usuário.
Usar o RAD Studio para desenvolvimento em Delphi gera código correto para uma aplicação VCL de usuário único, mas parte desse código criará problemas em um ambiente multiusuário ou multithread. Abaixo estão exemplos mostrando por que falhará e como corrigir.
1
Globais problemáticos auto-gerados de formulário/data module
Após pedir para criar um novo formulário VCL, obtemos este código:
Então outra unit pode usar essa variável global assim:
Form1 é acessível a qualquer sessão de usuário, e vários usuários poderiam estar usando-o. Imagine o que acontecerá se dois usuários criarem a mesma variável (causando um vazamento de memória de um formulário e uma referência inválida ao liberar o formulário sobrevivente).
2
Uso corrigido (sem variáveis globais)
Uma correção simples evita essa situação:
No uniGUI não precisamos manter uma instância do formulário em uma variável global. Delete todas as variáveis globais geradas automaticamente e crie sua própria instância privada. Este é um bom hábito para aplicações VCL e requer corrigir formulários e data modules.
Exemplo de um data module auto-gerado com uma variável global (não use):
uniGUI é mais do que apenas um conjunto de componentes para renderizar a camada de apresentação; é também um framework que suporta as melhores práticas para desenvolvimento de aplicações web. Entre as melhores práticas suportadas está a ausência de variáveis globais geradas automaticamente e o gerenciamento automático de memória de formulários e data modules anexados a uma sessão de usuário.
Em vez da variável global que o Delphi cria para formulários e data modules, o uniGUI cria funções que retornam a instância da sessão com base no tipo registrado anteriormente. Vamos examinar o MainModule como um exemplo típico.
Note a chamada a RegisterMainModuleClass durante a inicialização. Ao precisar acessar o MainModule da sessão, basta usar a função UniMainModule. Outra função de sessão, UniApplication, retornará a instância do MainModule registrado correspondente à sessão atual.
O mesmo tipo de solução se aplica a formulários e módulos de sessão. Se usarmos o assistente uniGUI para criar um novo formulário e selecionar Application Form, obteremos isto:
o uniGUI também gerencia o tempo de vida do formulário. Exemplo de uso a partir de um formulário principal:
No código VCL anterior precisávamos criar a instância temporária do formulário, abri-lo usando ShowModal e liberá-lo (usávamos try ... finally para evitar vazamento de memória). Esse código precisa apenas de uma chamada: UniForm1.ShowModal. Por quê?
UniForm1 cria um formulário temporário que é liberado automaticamente ao alcançar o fim de seu tempo de vida, mas estará disponível até esse momento.
E quanto a mostrar um formulário não modal? os formulários uniGUI são liberados automaticamente quando fechados.
Se você precisa de apenas alguns data modules, pode usar o MainModule para compartilhar sua conexão e outras variáveis de sessão, e criar esses data modules como "Application Data Modules". Eles serão anexados à sessão e gerenciados pelo uniGUI. O código é muito similar ao formulário anterior.
Se você tiver muitos formulários ou os data modules consumirem muitos recursos, qualquer formulário poderia gerenciar seu próprio data module manualmente criando-o em FormCreate e liberando-o em FormDestroy. Ainda assim, ao criar o data module como um data module uniGUI, nenhuma variável global será criada, não será necessário criá-lo em FormCreate e, se o desenvolvedor esquecer de liberar o data module em FormDestroy, ele será liberado automaticamente ao fechar a sessão do usuário.
Como o MainModule é criado automaticamente para cada novo usuário, todas as variáveis anexadas a uma sessão devem ser acessíveis através dele. Nunca use variáveis globais.
Forms e DataModules livres
No uniGUI você também pode criar forms e datamodules livres. Eles são chamados livres porque seu tempo de vida é gerenciado pelo desenvolvedor, não pelo framework.
Uma vez que o MainModule exista, o runtime do uniGUI verificará se um formulário de login foi registrado. Você pode criar o formulário de login usando o assistente uniGUI para criar formulários e selecionando "Login Form". A única diferença em relação a qualquer outro formulário de aplicação é que ele herda de TUniLoginForm e será exibido automaticamente quando uma nova sessão for iniciada.
Ao implementar a funcionalidade deste formulário, é importante lembrar que ele só pode usar o ServerModule, DataModule e qualquer outro formulário temporário que os utilize, mas não o MainForm. De fato, nenhum formulário deve acessar o MainForm, e sim o MainModule.
Usando referências globais de Form e DataModule
Em Application Forms do uniGUI, Application DataModules e MainModule são referenciados com funções globais que retornam uma instância desse objeto para a sessão atual.
Para formulários, essa função substitui a variável padrão criada em aplicações VCL. Ela permite manter a mesma sintaxe para acessar os formulários:
UniForm1.Show;
Chamar a função UniForm1 criará uma instância de TUniForm1 ou retornará a instância existente (tornando-a um singleton por sessão). Pode ser usada para criar e exibir formulários facilmente. Um detalhe importante é que essas funções não devem ser usadas como referências globais para esses formulários ou data modules. Não é recomendável chamar a função global de um formulário a partir de outros formulários ou data modules para qualquer propósito além de exibir esse formulário.
O framework uniGUI gerencia o tempo de vida de todos os anteriores objetos "de aplicação" (formulários e datamodules). Cada sessão/MainModule uniGUI mantém uma lista de formulários e datamodules gerenciados. Uma chamada como UniMainModule.GetFormInstance(TUniForm1) solicita um singleton daquele formulário específico (TUniForm1). Se essa instância não existir, ela será criada. Se a instância já foi criada, ela será retornada.
Além das fábricas singleton, o uniGUI também tenta otimizar o uso de memória. Os application datamodules serão liberados automaticamente ao fechar a sessão (porque não há como saber se estão em uso), mas os formulários são liberados assim que não estiverem em uso. Por exemplo, após sair de uma chamada a ShowModal e receber o resultado, esse formulário pode ser liberado com segurança.
Usar funções para acessar um formulário em vez de variáveis globais fornece duas vantagens:
Cada sessão tem acesso ao seu próprio formulário, e apenas um (como um singleton por sessão).
Os formulários de aplicação são gerenciados pelo framework uniGUI, tornando-se recursos sob demanda.
procedure TForm2.Button1Click(Sender: TObject);
begin
with TForm1.Create(Self) do
try
ShowModal;
finally
Free;
end;
end;
unit Unit3;
interface
uses
System.SysUtils, System.Classes;
type
TDataModule1 = class(TDataModule)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
// esta é uma variável global e não pode ser usada no uniGUI
var
DataModule1: TDataModule1;
implementation
{$R *.dfm}
end.
unit MainModule;
interface
uses
uniGUIMainModule, SysUtils, Classes;
type
TUniMainModule = class(TUniGUIMainModule)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
function UniMainModule: TUniMainModule;
implementation
{$R *.dfm}
uses
UniGUIVars, ServerModule, uniGUIApplication;
function UniMainModule: TUniMainModule;
begin
// retorna a instância correta do MainModule para a sessão atual
Result := TUniMainModule(UniApplication.UniMainModule)
end;
initialization
RegisterMainModuleClass(TUniMainModule);
end.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, uniGUITypes, uniGUIAbstractClasses,
uniGUIClasses, uniGUIForm;
type
TUniForm1 = class(TUniForm)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
function UniForm1: TUniForm1;
implementation
{$R *.dfm}
uses
MainModule, uniGUIApplication;
function UniForm1: TUniForm1;
begin
// retorna a instância correta de UniForm1 para a sessão atual
Result := TUniForm1(UniMainModule.GetFormInstance(TUniForm1));
end;
end.
unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, uniGUITypes, uniGUIAbstractClasses,
uniGUIClasses, uniGUIRegClasses, uniGUIForm, uniGUIBaseClasses, uniButton;
type
TMainForm = class(TUniForm)
btnShow: TUniButton;
btnShowModal: TUniButton;
procedure btnShowClick(Sender: TObject);
procedure btnShowModalClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
function MainForm: TMainForm;
implementation
{$R *.dfm}
uses
uniGUIVars, MainModule, uniGUIApplication,
uniGUIDialogs,
Unit1;
function MainForm: TMainForm;
begin
// retorna a instância correta do MainForm para a sessão atual
Result := TMainForm(UniMainModule.GetFormInstance(TMainForm));
end;
procedure TMainForm.btnShowClick(Sender: TObject);
begin
UniForm1.Show;
end;
procedure TMainForm.btnShowModalClick(Sender: TObject);
begin
UniForm1.ShowModal; // Modo síncrono
ShowMessage('Result = ' + IntToStr(UniForm1.ModalResult));
end;
initialization
RegisterAppFormClass(TMainForm);
end.
unit Unit2;
interface
uses
SysUtils, Classes;
type
TDataModule2 = class(TDataModule)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
function DataModule2: TDataModule2;
implementation
{$R *.dfm}
uses
UniGUIVars, uniGUIMainModule, MainModule;
function DataModule2: TDataModule2;
begin
// retorna a instância correta de DataModule2 para a sessão atual
Result := TDataModule2(UniMainModule.GetModuleInstance(TDataModule2));
end;
initialization
RegisterModuleClass(TDataModule2);
end.
unit LoginFormUnit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, uniGUITypes, uniGUIAbstractClasses,
uniGUIClasses, uniGUIRegClasses, uniGUIForm;
type
TUniLoginForm1 = class(TUniLoginForm)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
function UniLoginForm1: TUniLoginForm1;
implementation
{$R *.dfm}
uses
uniGUIVars, MainModule, uniGUIApplication;
function UniLoginForm1: TUniLoginForm1;
begin
Result := TUniLoginForm1(UniMainModule.GetFormInstance(TUniLoginForm1));
end;
initialization
RegisterAppFormClass(TUniLoginForm1);
end.