Delphi patterns
Skill delphicleancode/delphi-spec-kit/.claude/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.
npx -y skills add delphicleancode/delphi-spec-kit --skill delphi-patternsAssembled 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.6 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 for client reading operations.
///</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 for client writing operations.
///</summary>
ICustomerWriteRepository = interface
['{B2C3D4E5-F6A7-8901-BCDE-F12345678901}']
procedure Insert(ACustomer: TCustomer);
procedure Update(ACustomer: TCustomer);
procedure Delete(AId: Integer);
end;
///<summary>
///Complete repository interface combining reading and writing.
///</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;
//... other methods follow the same pattern
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>
///Customer service with dependency validation and orchestration.
///</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;
//...other methods
Factory Pattern
unit MeuApp.Infra.Factory;
interface
uses
FireDAC.Comp.Client,
MeuApp.Domain.Customer.Repository.Intf,
MeuApp.Application.Customer.Service.Intf;
type
///<summary>
///Factory to create instances of services and 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 for tax calculation strategies.
///</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?