Delphi-spec-kit Delphi SOLID Patterns

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

install
source · Clone the upstream repo
git clone https://github.com/delphicleancode/delphi-spec-kit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/delphicleancode/delphi-spec-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.gemini/skills/delphi-patterns" ~/.claude/skills/delphicleancode-delphi-spec-kit-delphi-solid-patterns-478d0d && rm -rf "$T"
manifest: .gemini/skills/delphi-patterns/SKILL.md
source content

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?