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).