agentsclimarketplace

Delphi patterns

Skill delphicleancode/delphi-spec-kit/.gemini/skills/delphi-patterns

An opinionated ecosystem of rules, skills, and steerings to elevate Delphi development to a state-of-the-art level with Artificial Intelligence.

Install
npx -y skills add delphicleancode/delphi-spec-kit --skill delphi-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

SOLID implementation patterns for Delphi projects — Repository, Service, Factory, Strategy with constructor injection and interfaces

SKILL.md

8.7 KB, as published. Nobody here has run it

Delphi SOLID Patterns — Skill

Use this skill when the user requests the creation of classes, services, repositories or any structure that follows SOLID principles in Delphi.

When to Use

  • When creating a new domain entity
  • When creating a repository (data access)
  • When creating a service (business logic)
  • When implementing a use case
  • When applying any design pattern (Factory, Strategy, Observer)

Repository Pattern

Repository Interface

unit MeuApp.Domain.Customer.Repository.Intf;

interface

uses
  System.Generics.Collections,
  MeuApp.Domain.Customer.Entity;

type
  /// <summary>
  ///   Interface para operations de leitura de customers.
  /// </summary>
  ICustomerReadRepository = interface
    ['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
    function FindById(AId: Integer): TCustomer;
    function FindAll: TObjectList<TCustomer>;
    function FindByCpf(const ACpf: string): TCustomer;
  end;

  /// <summary>
  ///   Interface para operations de escrita de customers.
  /// </summary>
  ICustomerWriteRepository = interface
    ['{B2C3D4E5-F6A7-8901-BCDE-F12345678901}']
    procedure Insert(ACustomer: TCustomer);
    procedure Update(ACustomer: TCustomer);
    procedure Delete(AId: Integer);
  end;

  /// <summary>
  ///   Interface completa de repository combinando leitura e escrita.
  /// </summary>
  ICustomerRepository = interface(ICustomerReadRepository)
    ['{C3D4E5F6-A7B8-9012-CDEF-123456789012}']
    procedure Insert(ACustomer: TCustomer);
    procedure Update(ACustomer: TCustomer);
    procedure Delete(AId: Integer);
  end;

implementation

end.

Implementation with FireDAC

unit MeuApp.Infra.Customer.Repository;

interface

uses
  System.SysUtils,
  System.Generics.Collections,
  FireDAC.Comp.Client,
  MeuApp.Domain.Customer.Entity,
  MeuApp.Domain.Customer.Repository.Intf;

type
  TCustomerRepository = class(TInterfacedObject, ICustomerRepository)
  private
    FConnection: TFDConnection;
  public
    constructor Create(AConnection: TFDConnection);

    { ICustomerReadRepository }
    function FindById(AId: Integer): TCustomer;
    function FindAll: TObjectList<TCustomer>;
    function FindByCpf(const ACpf: string): TCustomer;

    { ICustomerWriteRepository }
    procedure Insert(ACustomer: TCustomer);
    procedure Update(ACustomer: TCustomer);
    procedure Delete(AId: Integer);
  end;

implementation

constructor TCustomerRepository.Create(AConnection: TFDConnection);
begin
  inherited Create;
  if not Assigned(AConnection) then
    raise EArgumentNilException.Create('AConnection cannot be nil');
  FConnection := AConnection;
end;

function TCustomerRepository.FindById(AId: Integer): TCustomer;
var
  LQuery: TFDQuery;
begin
  Result := nil;
  LQuery := TFDQuery.Create(nil);
  try
    LQuery.Connection := FConnection;
    LQuery.SQL.Text := 'SELECT * FROM customers WHERE id = :id';
    LQuery.ParamByName('id').AsInteger := AId;
    LQuery.Open;

    if not LQuery.IsEmpty then
    begin
      Result := TCustomer.Create(LQuery.FieldByName('name').AsString);
      Result.Id := LQuery.FieldByName('id').AsInteger;
    end;
  finally
    LQuery.Free;
  end;
end;

// ... demais métodos seguem o mesmo default

Service Pattern

Service Interface

unit MeuApp.Application.Customer.Service.Intf;

interface

uses
  MeuApp.Domain.Customer.Entity;

type
  ICustomerService = interface
    ['{D4E5F6A7-B8C9-0123-DEF0-234567890123}']
    function GetById(AId: Integer): TCustomer;
    procedure CreateCustomer(const AName: string; const ACpf: string);
    procedure UpdateCustomer(ACustomer: TCustomer);
    procedure DeleteCustomer(AId: Integer);
  end;

implementation

end.

Service Implementation

unit MeuApp.Application.Customer.Service;

interface

uses
  System.SysUtils,
  MeuApp.Domain.Customer.Entity,
  MeuApp.Domain.Customer.Repository.Intf,
  MeuApp.Application.Customer.Service.Intf;

type
  /// <summary>
  ///   Service de customers com validation e orquestraction de dependências.
  /// </summary>
  TCustomerService = class(TInterfacedObject, ICustomerService)
  private
    FRepository: ICustomerRepository;
    procedure ValidateCpf(const ACpf: string);
  public
    constructor Create(ARepository: ICustomerRepository);
    function GetById(AId: Integer): TCustomer;
    procedure CreateCustomer(const AName: string; const ACpf: string);
    procedure UpdateCustomer(ACustomer: TCustomer);
    procedure DeleteCustomer(AId: Integer);
  end;

implementation

constructor TCustomerService.Create(ARepository: ICustomerRepository);
begin
  inherited Create;
  if not Assigned(ARepository) then
    raise EArgumentNilException.Create('ARepository cannot be nil');
  FRepository := ARepository;
end;

function TCustomerService.GetById(AId: Integer): TCustomer;
begin
  Result := FRepository.FindById(AId);
  if not Assigned(Result) then
    raise EEntityNotFoundException.CreateFmt('Customer not found: %d', [AId]);
end;

procedure TCustomerService.CreateCustomer(const AName: string; const ACpf: string);
var
  LCustomer: TCustomer;
  LExisting: TCustomer;
begin
  ValidateCpf(ACpf);

  LExisting := FRepository.FindByCpf(ACpf);
  if Assigned(LExisting) then
    raise EBusinessRuleException.CreateFmt('CPF already registered: %s', [ACpf]);

  LCustomer := TCustomer.Create(AName);
  try
    LCustomer.Cpf := ACpf;
    FRepository.Insert(LCustomer);
  except
    LCustomer.Free;
    raise;
  end;
end;

procedure TCustomerService.ValidateCpf(const ACpf: string);
const
  CPF_LENGTH = 11;
begin
  if ACpf.Trim.IsEmpty then
    raise EValidationException.Create('CPF cannot be empty');
  if ACpf.Length <> CPF_LENGTH then
    raise EValidationException.CreateFmt('CPF must have %d digits', [CPF_LENGTH]);
end;

// ... demais métodos

Factory Pattern

unit MeuApp.Infra.Factory;

interface

uses
  FireDAC.Comp.Client,
  MeuApp.Domain.Customer.Repository.Intf,
  MeuApp.Application.Customer.Service.Intf;

type
  /// <summary>
  ///   Factory para criar instâncias de services e repositories.
  /// </summary>
  TServiceFactory = class
  public
    class function CreateCustomerRepository(AConnection: TFDConnection): ICustomerRepository;
    class function CreateCustomerService(AConnection: TFDConnection): ICustomerService;
  end;

implementation

uses
  MeuApp.Infra.Customer.Repository,
  MeuApp.Application.Customer.Service;

class function TServiceFactory.CreateCustomerRepository(AConnection: TFDConnection): ICustomerRepository;
begin
  Result := TCustomerRepository.Create(AConnection);
end;

class function TServiceFactory.CreateCustomerService(AConnection: TFDConnection): ICustomerService;
var
  LRepo: ICustomerRepository;
begin
  LRepo := CreateCustomerRepository(AConnection);
  Result := TCustomerService.Create(LRepo);
end;

Strategy Pattern

unit MeuApp.Domain.Tax.Strategy;

interface

type
  /// <summary>
  ///   Interface para estratégias de cálculo de imposto.
  /// </summary>
  ITaxCalculator = interface
    ['{E5F6A7B8-C9D0-1234-EF01-345678901234}']
    function Calculate(ABaseValue: Currency): Currency;
    function GetDescription: string;
  end;

  TSimplesTaxCalculator = class(TInterfacedObject, ITaxCalculator)
  public
    function Calculate(ABaseValue: Currency): Currency;
    function GetDescription: string;
  end;

  TLucroPresumidoTaxCalculator = class(TInterfacedObject, ITaxCalculator)
  public
    function Calculate(ABaseValue: Currency): Currency;
    function GetDescription: string;
  end;

implementation

{ TSimplesTaxCalculator }

function TSimplesTaxCalculator.Calculate(ABaseValue: Currency): Currency;
const
  SIMPLES_RATE = 0.06;
begin
  Result := ABaseValue * SIMPLES_RATE;
end;

function TSimplesTaxCalculator.GetDescription: string;
begin
  Result := 'Simples Nacional (6%)';
end;

{ TLucroPresumidoTaxCalculator }

function TLucroPresumidoTaxCalculator.Calculate(ABaseValue: Currency): Currency;
const
  LUCRO_PRESUMIDO_RATE = 0.15;
begin
  Result := ABaseValue * LUCRO_PRESUMIDO_RATE;
end;

function TLucroPresumidoTaxCalculator.GetDescription: string;
begin
  Result := 'Lucro Presumido (15%)';
end;

Checklist for New Implementations

When creating any new functionality, check:

  • Interface defined in the Domain?
  • Implementation in Infrastructure?
  • Service in Application with constructor injection?
  • Factory method updated?
  • Guard clauses at the beginning of methods?
  • Try/finally for temporary objects?
  • Names following conventions (T, I, E, F, A, L)?
  • XMLDoc in public methods?
  • Methods ≤ 20 lines?
  • No with, no global variables, no magic numbers?

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.