![]() |
Arquitetura Limpa |
Índice
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
- Abra o Delphi e crie um novo projeto VCL.
- Salve o formulário como MainForm.
- 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.
// 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.
// 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
eGetAll
para salvar e buscar produtos.
3. Casos de Uso
Os casos de uso coordenam a lógica entre entidades e outras camadas.
// 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.
// 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).
// 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
eTProductUseCase
. - 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 usandoFProductUseCase
. 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étodoGetAllProducts
da instância deTProductUseCase
que foi declarada no métodoCreate
. - 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.
Nenhum comentário:
Postar um comentário