Menu

Tradução

Português English Español Français

Arquitetura Limpa em Delphi (Clean Architecture): Princípios e Aplicação

Nesse post iremos abordar sobre Arquitetura Limpa em Delphi, também conhecida como Clean Architecture. Com exemplo de uso completo.

 

Clean Architecture em Delphi
Arquitetura Limpa

Introdução

Nesse post iremos abordar sobre Arquitetura Limpa em Delphi, também conhecida como Clean Architecture. Com exemplo de uso completo.

A Arquitetura Limpa, é uma abordagem para organizar e estruturar o código de software de maneira que ele seja fácil de entender, manter e testar. Criada por Robert C. Martin (conhecido como Uncle Bob), essa arquitetura enfatiza a separação de responsabilidades e a independência das camadas, resultando em um código modular e de alta qualidade. Neste artigo, exploraremos os princípios da Arquitetura Limpa e como aplicá-los em projetos Delphi, focando na separação de camadas e responsabilidades.

Princípios da Arquitetura Limpa

A Arquitetura Limpa baseia-se em alguns princípios fundamentais:

1. Independência de Frameworks

Os frameworks são ferramentas que ajudam a construir a aplicação, mas não devem ditar sua arquitetura. A lógica de negócios deve ser independente dos detalhes de implementação do framework.

2. Testabilidade

O sistema deve ser projetado de forma que todas as partes possam ser testadas isoladamente. Isso implica na criação de componentes pequenos, independentes e bem definidos.

3. Independência de Interface de Usuário

A lógica de negócios deve ser independente da interface do usuário (UI). A UI pode mudar sem afetar a lógica de negócios e vice-versa.

4. Independência de Banco de Dados

A lógica de negócios não deve depender dos detalhes de acesso ao banco de dados. Isso facilita a substituição ou a mudança de banco de dados sem impactar a lógica de negócios.

5. Independência de Agentes Externos

Qualquer serviço ou ferramenta externa deve ser tratada como um detalhe de implementação, que pode ser substituído sem afetar a lógica de negócios.

Aplicando a Arquitetura Limpa em Projetos Delphi

Separação de Camadas e Responsabilidades

A Arquitetura Limpa é frequentemente representada em camadas concêntricas, onde cada camada depende apenas das camadas internas. As camadas típicas incluem:

  • Entidades: Contêm a lógica de negócios e regras de negócio da aplicação.
  • Casos de Uso: Contêm a lógica específica da aplicação, coordenando os dados entre as entidades e outras camadas.
  • Interface de Aplicação: Define as interfaces usadas pela camada de Casos de Uso.
  • Interface de Usuário: Contém a lógica de apresentação, seja para web, desktop ou outras interfaces.

Exemplo de Implementação de Arquitetura Limpa em Delphi

Vamos considerar um exemplo simples de uma aplicação de gestão de produtos. A estrutura básica do projeto em Delphi seguindo a Arquitetura Limpa pode ser assim:

Configurando o projeto

  1. Abra o Delphi e crie um novo projeto VCL.
  2. Salve o formulário como MainForm.
  3. Adicione quatro Units ao projeto e salve com os respectivos nomes: ProductEntity, ProductUseCase, ProductRepositoryInterface e ProductRepository.

Implementando o código

1. Entidades

As entidades contêm a lógica de negócios.

Delphi

// Unit de Entidades
unit ProductEntity;

interface

type

  TProduct = Record
    ID: Integer;
    Name: string;
    Price: Double;
  end;
  
implementation

end.
  
Explicação do código

Esta unidade define o Registro TProduct, que representa a entidade de produto no sistema.

  • TProduct: Registro que possui as propriedades do produto (ID, Nome e Preço).

2. Interface de Aplicação

A interface de aplicação define as interfaces usadas pela camada de Casos de Uso.

Delphi

// Unit de Interface de Repositório
unit ProductRepositoryInterface;

interface

uses
  ProductEntity, System.Generics.Collections;

type
  IProductRepository = interface
    ['{8AEB25DF-4E57-47F8-83F3-DAB30E1D4F30}']
    procedure Save(AProduct: TProduct);
    function FindByID(AID: Integer): TProduct;
    function GetAll: TList<TProduct>;
  end;

implementation

end.
  
Explicação do código

Define a interface IProductRepository, que descreve os métodos para operações de persistência de produtos.

  • IProductRepository: Interface que declara os métodos Save, FindByID e GetAll para salvar e buscar produtos.

3. Casos de Uso

Os casos de uso coordenam a lógica entre entidades e outras camadas.

Delphi

// Unit de Casos de Uso
unit ProductUseCase;

interface

uses
  ProductEntity, ProductRepositoryInterface, System.Generics.Collections;

type
  TProductUseCase = class
  private
    FRepository: IProductRepository;
  public
    constructor Create(ARepository: IProductRepository);
    procedure AddProduct(AProduct: TProduct);
    function GetProduct(AID: Integer): TProduct;
    function GetAllProducts: TList<TProduct>;
  end;

implementation

constructor TProductUseCase.Create(ARepository: IProductRepository);
begin
  FRepository := ARepository;
end;

procedure TProductUseCase.AddProduct(AProduct: TProduct);
begin
  FRepository.Save(AProduct);
end;

function TProductUseCase.GetProduct(AID: Integer): TProduct;
begin
  Result := FRepository.FindByID(AID);
end;

function TProductUseCase.GetAllProducts: TList<TProduct>;
begin
  Result := FRepository.GetAll;
end;

end.
  
Explicação do código

Implementa a lógica de negócios para manipulação de produtos.

  • TProductUseCase: Classe que contém a lógica de negócio.
  • AddProduct: Método que chama o repositório para salvar o produto.
  • GetProduct: Método que chama o repositório para buscar um produto pelo ID.
  • GetAllProducts: Método que chama o repositório para buscar todos os produtos.

4. Implementação de Repositório

A implementação do repositório lida com o banco de dados ou outras fontes de dados.

Delphi

// Unit de Implementação de Repositório
unit ProductRepository;

interface

uses
  ProductRepositoryInterface, ProductEntity, System.Generics.Collections;

type
  TProductRepository = class(TInterfacedObject, IProductRepository)
  private
    FProducts: TDictionary<Integer, TProduct>;
    FNextID: Integer;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Save(AProduct: TProduct);
    function FindByID(AID: Integer): TProduct;
    function GetAll: TList<TProduct>;
  end;

implementation

constructor TProductRepository.Create;
begin
  FProducts := TDictionary<Integer, TProduct>.Create;
  FNextID := 1;
end;

destructor TProductRepository.Destroy;
begin
  FProducts.Free;
  inherited;
end;

procedure TProductRepository.Save(AProduct: TProduct);
begin
  if AProduct.ID = 0 then
  begin
    AProduct.ID := FNextID;
    Inc(FNextID);
  end;

  FProducts.Add(AProduct.ID, AProduct);
end;

function TProductRepository.FindByID(AID: Integer): TProduct;
begin
  FProducts.TryGetValue(AID, Result);
end;

function TProductRepository.GetAll: TList<TProduct>;
var
  Product: TProduct;
  ProductList: TList<TProduct>;
begin
  ProductList := TList<TProduct>.Create;
  for Product in FProducts.Values do
  begin
    ProductList.Add(Product);
  end;
  Result := ProductList;
end;

end.
  
Explicação do código

Fornece a implementação concreta da interface IProductRepository.

  • TProductRepository: Implementa a interface IProductRepository.
  • Save: Método que salva um produto no dicionário.
  • FindByID: Método que busca um produto no dicionário pelo ID.
  • GetAll: Método que lista todos os produtos do dicionário.

5. Interface de Usuário

A interface de usuário interage com a camada de Casos de Uso para apresentar dados e aceitar entradas.

Adicione os seguintes componentes ao formulário princial: dois TButton(btnAddProduct e btnListProducts), dois TEdit(edtProductName e edtProductPrice) e um TListBox(lbProducts).

Delphi

// Unit da Interface de Usuário
unit MainForm;

interface

uses
  Vcl.Forms, Vcl.StdCtrls, System.Generics.Collections, Vcl.Controls, System.Classes, System.SysUtils, Vcl.Dialogs,
  ProductUseCase, ProductRepository, ProductRepositoryInterface, ProductEntity;

type
  TForm1 = class(TForm)
    btnAddProduct: TButton;
    edtProductName: TEdit;
    edtProductPrice: TEdit;
    btnListProducts: TButton;
    lbProducts: TListBox;
    procedure btnAddProductClick(Sender: TObject);
    procedure btnListProductsClick(Sender: TObject);
  private
    FProductUseCase: TProductUseCase;
    procedure ListProducts;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

constructor TForm1.Create(AOwner: TComponent);
var
  Repository: IProductRepository;
begin
  inherited Create(AOwner);
  Repository := TProductRepository.Create;
  FProductUseCase := TProductUseCase.Create(Repository);
end;

destructor TForm1.Destroy;
begin
  FProductUseCase.Free;
  inherited;
end;

procedure TForm1.btnAddProductClick(Sender: TObject);
var
  Product: TProduct;
begin
  Product.Name := edtProductName.Text;
  Product.Price := StrToFloat(edtProductPrice.Text);
  FProductUseCase.AddProduct(Product);
  ShowMessage('Product added successfully!');
  edtProductName.Clear;
  edtProductPrice.Clear;
  edtProductName.SetFocus;
end;

procedure TForm1.btnListProductsClick(Sender: TObject);
begin
  ListProducts;
end;

procedure TForm1.ListProducts;
var
  Products: TList<TProduct>;
  Product: TProduct;
begin
  lbProducts.Clear;
  Products := FProductUseCase.GetAllProducts;
  try
    for Product in Products do
    begin
      lbProducts.Items.Add(Format('ID: %d, Name: %s, Price: %.2f', [Product.ID, Product.Name, Product.Price]));
    end;
  finally
    Products.Free;
  end;
end;

end.
  
Explicação do código

Define a interface gráfica da aplicação, onde o usuário pode adicionar produtos.

  • TForm1: Classe que define o formulário principal da aplicação.
  • Create: Método construtor. Cria a instância de TProductRepository e TProductUseCase.
  • Destroy: Método destrutor. Destrói a instância de TProductUseCase.
  • btnAddProductClick: Manipulador de evento para o botão "Add Product". Cria uma instância de TProduct, define suas propriedades com os valores dos campos de entrada e adiciona o produto usando FProductUseCase. Limpa as caixas de texto e seta o foco na caixa nome, para que o próximo produto possa ser inserido.
  • ListProducts: Método que exibe os produtos adicionados em uma lista. Cria uma instância de TProduct e chama o método GetAllProducts da instância de TProductUseCase que foi declarada no método Create.
  • btnListProductsClick: Manipulador de evento para o botão "List Products". Executa o método ListProducts que listará os produtos.

Veja abaixo a ilustração do projeto:

Ilustração do projeto.

Código fonte do exemplo

Você pode fazer o download do exemplo do projeto através do repositório do github:

https://github.com/Gisele-de-Melo/Arquitetura_Limpa

Conclusão

Aplicar a Arquitetura Limpa em projetos Delphi pode parecer desafiador, mas os benefícios em termos de manutenibilidade, testabilidade e flexibilidade são significativos. Ao separar claramente as camadas e responsabilidades, você cria um código mais modular e independente de detalhes de implementação, o que facilita a adaptação a novas necessidades e tecnologias. Esperamos que este artigo tenha fornecido uma compreensão clara de como aplicar os princípios da Arquitetura Limpa em Delphi e que os exemplos apresentados sirvam como um guia prático para começar a implementar essa abordagem em seus projetos.

Posts Relacionados



Nenhum comentário:

Postar um comentário

Recursividade em Delphi: Conceitos, Exemplos e Aplicações Práticas

Neste post, você aprenderá quando usar recursividade em Delphi, suas vantagens e desvantagens, além de um exemplo prático implementa...