Main Module
O módulo de dados principal contém vários objetos associados a uma sessão:
A conexão com o banco de dados (TFDConnection)
Tabelas ou consultas para acessar o banco de dados (TFDTable, TFDQuery)
Fontes de dados para acessar os objetos do banco de dados a partir de controles vinculados a dados
Uma lista de ações (TActionList)
Uma lista de imagens (TUniNativeImageList) com um adaptador (TUniImageListAdapter) para usar as imagens da lista de ações
Usando os componentes anteriores e seu código interno, o MainModule encapsula a maior parte da lógica de negócio desta aplicação simples.
Criar / Destruir
Cada sessão tem sua própria conexão com o banco de dados. a string de conexão já está disponível no módulo do servidor.
procedure TUniMainModule.UniGUIMainModuleCreate(Sender: TObject);
begin
Conn.ConnectionString := UniServerModule.ConnString;
Conn.Open;
end;
procedure TUniMainModule.UniGUIMainModuleDestroy(Sender: TObject);
begin
Conn.Close;
end;Processo de login
Dependendo de como o usuário acessa a aplicação, a aplicação irá instanciar o formulário de login para desktop ou para touch. Após digitar o nome de usuário e a senha, pressionar OK acionará o processo de validação do login.
{ Validação de login e seleção da plataforma da sessão }
Login encapsula duas regras de negócio: como identificar um usuário válido (combinação usuário/senha) e que tipo de usuário pode ver a ação "Edit Users".
Seleção de plataforma
Cada formulário de login (dLoginForm e mLoginForm) informa ao MainModule a plataforma selecionada ao tentar um login.
{ Seleção de plataforma }
Após selecionar uma plataforma, a sessão saberá como interagir com o usuário nessa plataforma. A solução usada nesta aplicação é agrupar diferentes implementações de ações comuns por trás de uma fachada/interface e instanciar a correta para a plataforma selecionada.
A interface é IService:
Existem duas implementações (para desktop e touch, respectivamente).
Desktop
Touch
Fica agora claro que ambas implementações são praticamente idênticas, porque as únicas diferenças são as classes que implementam cada método na interface. Isso é uma limitação significativa nesta solução, pois há código duplicado, o que é uma má prática de programação. Uma das possíveis soluções é usar Dependency Injection para localizar as classes corretas para uma dada plataforma e evitar duplicação de código.
Implementando a tela de lançamento com ações
Uma das melhores práticas para separar a interface do usuário da lógica de negócio é usar ações. O módulo de dados principal tem uma lista de ações com as seguintes ações:
Edit Users (disponível somente para administradores)
Edit Orders
Relatório de Vendas
Não importa que tipo de controle visual seja usado pela plataforma alvo, se ele suporta ações, poderá acionar as ações selecionadas. Por exemplo, o formulário principal desktop expõe essas ações em um TUniMainMenu e em uma TUniToolBar, enquanto o formulário principal touch as expõe em botões grandes.
A interface do usuário não está solicitando explicitamente nada ao MainModule e o MainModule não tem conhecimento da interface do usuário de forma alguma.
Por outro lado, a lógica de negócio requer a execução das ações solicitadas, que é o motivo de criar a interface Services.
{ Ações de lógica de negócio da tela de lançamento }
Seguindo as melhores práticas, somente ao solicitar um relatório o módulo de dados correspondente é criado e usado para exportar o relatório em formato PDF.
Movendo a lógica de negócio da interface do usuário para o módulo de dados
Requisições explícitas do usuário já foram mapeadas para ações e tratadas no módulo de dados. Outras solicitações do usuário chegam ao módulo de dados como eventos relacionados a dados que devem ser tratados de acordo com a lógica de negócio da aplicação. Mas existe uma lógica de negócio implícita que precisa de uma solicitação explícita: abrir e fechar conjuntos de dados usados por formulários concretos.
Nesta aplicação não usaremos nenhum sistema de registro (que poderia relacionar formulários concretos ou interfaces a recursos), apenas chamadas explícitas solicitando suporte do módulo de dados.
Gerenciamento de recursos
{ Gerenciamento de recursos para formulários }
Cada formulário que requer recursos do módulo de dados chamará UniMainModule.OnCreate
(Self) e OnDestroy(Self) para que o módulo de dados configure e libere quaisquer recursos necessários. No código anterior o módulo de dados abre e fecha os conjuntos de dados requeridos por cada formulário.
Esse código em particular é apenas lógica de negócio do lado do servidor, e poderia ser ativado usando Dependency Injection. Isso pode ser feito registrando recursos com seus respectivos métodos OnCreate e OnDestroy.
Em situações mais complicadas, poderia ser possível "injetar" componentes no formulário concreto. Por exemplo, o MainMenu da aplicação poderia ser definido no módulo de dados, e algum método genérico poderia ser capaz de criar o equivalente correto para o formulário Sender com base em seu tipo.
O objetivo desse tipo de programação é alcançar maior desacoplamento entre a interface do usuário e a lógica de negócio. Os resultados virão mais tarde ao analisar a implementação de cada formulário.
Tratamento de eventos relacionados a dados
A maior parte da lógica de negócio pertence a esta categoria. É típico validar mudanças em OnBeforePost, atualizar informações ou acionar outras ações em OnAfterPost, calcular campos virtuais em OnCalcFields, etc.
{ Manipuladores de eventos de acesso a dados }
Executando ações
Algumas ações não são padrão para um controle relacionado a dados e são expostas em botões, itens de menu, etc.
{ Ações de lógica de negócio }
A última ação levanta uma preocupação. Se a lógica de negócio determina que pelo menos um usuário deve ser admin, por que essa validação só acontece quando o usuário pressiona o botão "Toggle Admin Status"? O que acontecerá se a mesma ação ocorrer clicando na checkbox Admin na interface do usuário?
Existem duas opções para resolver o problema anterior: tornar a coluna Admin somente leitura, ou executar essa validação em ambos os lugares (aqui e em tblUsers.OnBeforePost).