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/.claude/skills/delphi-patterns" ~/.claude/skills/delphicleancode-delphi-spec-kit-delphi-solid-patterns && rm -rf "$T"
manifest:
.claude/skills/delphi-patterns/SKILL.mdsource 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 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
, no global variables, no magic numbers?with