Il Repository Pattern in DelphiMVCFramework: Clean Architecture Resa Semplice
Introduzione
Dopo anni di lavoro con DelphiMVCFramework e di supporto agli sviluppatori nella creazione di REST API migliori, ho notato un pattern ricorrente: sebbene l’implementazione di ActiveRecord sia potente e conveniente, molti team avevano bisogno di una separazione più netta delle responsabilità, specialmente quando si costruiscono applicazioni più grandi con logica di business complessa.
Voglio essere chiaro fin dall’inizio: ActiveRecord rimane la tecnologia fondamentale per l’accesso ai dati in DelphiMVCFramework 🏗️. Tutto ciò che puoi fare con i repository, puoi ottenerlo anche usando ActiveRecord direttamente - richiede solo un po’ più di codice. Il Repository Pattern è semplicemente un comodo livello di astrazione che rende più semplice implementare certi pattern architetturali, in particolare quando hai bisogno di dependency injection, una separazione più pulita delle responsabilità, o codice più testabile.
Ecco perché sono entusiasta di introdurre il supporto nativo al Repository Pattern in DelphiMVCFramework 🚀. Questa nuova funzionalità porta pattern architetturali di livello professionale nelle tue applicazioni Delphi mantenendo la semplicità e l’eleganza che ti aspetti dal framework. Sotto il cofano, i repository delegano tutte le operazioni ad ActiveRecord, garantendo zero duplicazione di codice e sfruttando l’implementazione ActiveRecord collaudata che già conosci e di cui ti fidi.
Perché il Repository Pattern?
Il Repository Pattern agisce come mediatore tra la tua logica di dominio/business e i layer di mapping dei dati. Pensalo come un’interfaccia simile a una collezione per accedere alle entità di dominio. Ecco i principali vantaggi:
1. Separazione delle Responsabilità 🎯
I tuoi controller non devono più conoscere i dettagli di ActiveRecord. Lavorano con un’interfaccia pulita (IMVCRepository<T>
) che può essere facilmente simulata per i test.
2. Pronto per la Dependency Injection 💉
I repository sono perfetti per la dependency injection. Puoi iniettare IMVCRepository<TCustomer>
direttamente nei tuoi controller, rendendo il tuo codice più testabile e manutenibile.
3. Testabilità ✅
I mock dei repository sono facili da creare per gli unit test. Puoi testare la tua logica di business senza toccare il database.
4. Flessibilità 🔧
Hai bisogno di query personalizzate? Estendi semplicemente il repository base con metodi specifici del dominio mantenendo tutte le operazioni CRUD standard.
Iniziamo: Uso Base
Partiamo con un esempio semplice. Ecco come definire un’entità usando ActiveRecord:
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
TCustomer = class(TMVCActiveRecord)
private
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
fID: NullableInt64;
[MVCTableField('code')]
fCode: NullableString;
[MVCTableField('company_name')]
fCompanyName: NullableString;
[MVCTableField('city')]
fCity: string;
[MVCTableField('rating')]
fRating: NullableInt32;
public
property ID: NullableInt64 read fID write fID;
property Code: NullableString read fCode write fCode;
property CompanyName: NullableString read fCompanyName write fCompanyName;
property City: string read fCity write fCity;
property Rating: NullableInt32 read fRating write fRating;
end;
Ora, invece di usare i metodi di ActiveRecord direttamente nel tuo controller, hai due opzioni:
Opzione 1: Usa il Repository Generico Direttamente
Se non hai bisogno di logica personalizzata o metodi specifici del dominio, puoi semplicemente usare IMVCRepository<T>
direttamente senza creare una classe custom:
// Nel tuo controller o service
procedure RegisterServices(Container: IMVCServiceContainer);
begin
Container.RegisterType(
TMVCRepository<TCustomer>,
IMVCRepository<TCustomer>,
TRegistrationType.SingletonPerRequest
);
end;
// Poi iniettalo direttamente
function TCustomersController.GetCustomers(
[MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
Result := OKResponse(CustomersRepository.GetAll);
end;
Questo è perfetto per semplici operazioni CRUD dove i metodi standard del repository sono sufficienti. ⚡
Opzione 2: Crea una Classe Repository Personalizzata
Se hai bisogno di aggiungere metodi personalizzati o logica specifica del dominio, crea la tua classe repository:
type
TCustomerRepository = class(TMVCRepository<TCustomer>)
public
// Aggiungi metodi personalizzati qui se necessario
function GetCustomersByCity(const City: string): TObjectList<TCustomer>;
function GetTopRatedCustomers: TObjectList<TCustomer>;
end;
Implementare Metodi Repository Personalizzati
Una delle caratteristiche più potenti è la capacità di estendere i repository con metodi specifici del dominio. Ecco come:
function TCustomerRepository.GetCustomersByCity(const City: string): TObjectList<TCustomer>;
begin
Result := GetWhere('city = ?', [City]);
end;
function TCustomerRepository.GetTopRatedCustomers: TObjectList<TCustomer>;
begin
Result := SelectByNamedQuery('BestCustomers', [], []);
end;
Nota come questi metodi usano le capacità del repository base (GetWhere
, SelectByNamedQuery
) ma forniscono un’API più espressiva e specifica del dominio per i tuoi controller.
Usare i Repository nei Controller con Dependency Injection
È qui che le cose diventano davvero interessanti. Il container di dependency injection integrato in DelphiMVCFramework rende incredibilmente facile usare i repository nei tuoi controller:
[MVCPath('/api/customers')]
TCustomersController = class(TMVCController)
public
[MVCPath]
[MVCHTTPMethods([httpGET])]
function GetCustomers(
[MVCFromQueryString('rql','')] RQLFilter: String;
[MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
[MVCPath('/($ID)')]
[MVCHTTPMethods([httpGET])]
function GetCustomerByID(
const ID: Integer;
[MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
[MVCPath]
[MVCHTTPMethods([httpPOST])]
function CreateCustomer(
[MVCFromBody] const Customer: TCustomer;
[MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
end;
L’implementazione è pulita e focalizzata sulle preoccupazioni HTTP:
function TCustomersController.GetCustomers(
RQLFilter: String;
CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
Result := OKResponse(CustomersRepository.SelectRQL(RQLFilter));
end;
function TCustomersController.CreateCustomer(
const Customer: TCustomer;
CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
CustomersRepository.Store(Customer);
Result := CreatedResponse('/api/customers/' + Customer.ID.Value.ToString);
end;
Nota come i metodi del controller ricevono il repository come parametro con l’attributo [MVCInject]
. Il framework risolve e inietta automaticamente la dipendenza! 🎉
Registrare i Repository nel Container
Per abilitare la dependency injection, devi registrare i tuoi repository nel container. Questo viene tipicamente fatto nel tuo WebModule:
procedure RegisterServices(Container: IMVCServiceContainer);
begin
Container.RegisterType(
TMVCRepository<TCustomer>,
IMVCRepository<TCustomer>,
TRegistrationType.SingletonPerRequest
);
end;
Il tipo di registrazione SingletonPerRequest
garantisce che ogni richiesta HTTP ottenga la propria istanza del repository, che viene automaticamente distrutta quando la richiesta si completa.
Ricche Capacità di Query
L’interfaccia del repository fornisce molteplici modi per interrogare i tuoi dati:
Clausole SQL Where
lCustomers := Repository.GetWhere('city = ? AND rating >= ?', ['Rome', 4]);
RQL (Resource Query Language)
lCustomers := Repository.SelectRQL('eq(rating,5)');
lCustomers := Repository.SelectRQL('ge(rating,4)&sort(+code)');
SQL Raw
lCustomers := Repository.Select(
'SELECT * FROM customers WHERE city = ? AND rating >= ? ORDER BY code',
['Rome', 3]
);
Query Nominate
lCustomers := Repository.SelectByNamedQuery('BestCustomers', [], []);
Supporto alle Transazioni
I repository funzionano perfettamente con le transazioni usando il comodo pattern UseTransactionContext
:
function TCustomersController.BulkCreateCustomers(
const Customers: TObjectList<TCustomer>;
CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
var lCtx := TMVCRepository.UseTransactionContext;
for var lCustomer in Customers do
begin
CustomersRepository.Insert(lCustomer);
end;
Result := CreatedResponse();
end;
La transazione viene automaticamente committata quando lCtx
esce dallo scope, o viene eseguito il rollback se si verifica un’eccezione. Pulito e semplice! 🔄
Funzionalità Avanzate
Gestione Connessioni Personalizzate
Hai bisogno di usare una connessione database specifica? Usa TMVCRepositoryWithConnection
:
var
lConnection: TFDConnection;
lRepo: IMVCRepository<TCustomer>;
begin
lConnection := TFDConnection.Create(nil);
lConnection.ConnectionDefName := 'SecondaryDB';
lConnection.Connected := True;
lRepo := TMVCRepositoryWithConnection<TCustomer>.Create(lConnection, True);
// Usa il repository con la connessione personalizzata
end;
Il Metodo Store
Il metodo Store
è particolarmente utile - determina automaticamente se inserire o aggiornare in base alla presenza della chiave primaria nell’entità:
// Insert se PK è null
lCustomer := TCustomer.Create;
lCustomer.Code := 'NEW001';
Repository.Store(lCustomer); // INSERT
// Update se PK esiste
lCustomer := Repository.GetByPK(lID);
lCustomer.CompanyName := 'Updated Name';
Repository.Store(lCustomer); // UPDATE
Metodi Utility
Il repository include diversi metodi utility utili:
// Controlla l'esistenza senza caricare l'entità
if Repository.Exists(CustomerID) then
// Fai qualcosa
// Conta i record
lCount := Repository.Count('ge(rating,4)');
// Cancella tutto (usare con cautela!)
lDeleted := Repository.DeleteAll;
Esempio Completo: Repository Showcase
Ho creato due esempi completi nel framework:
- repository_showcase - Dimostra ogni funzionalità del Repository Pattern con oltre 20 esempi
- simple_api_using_repository_with_injection - Una REST API completa che usa repository e dependency injection
Questi esempi coprono:
- Operazioni CRUD base
- Metodi di query (SQL, RQL, Query Nominate)
- Metodi repository personalizzati
- Gestione delle transazioni
- Gestione degli errori
- Operazioni bulk
- Connessioni personalizzate
- E molto altro!
Migrare da ActiveRecord a Repository Pattern 🔄
Una delle migliori caratteristiche di questa implementazione è il percorso di migrazione senza interruzioni. Poiché il Repository Pattern è costruito sopra ActiveRecord, non devi rifare i mapping delle tue entità o cambiare la struttura del database. Le tue entità ActiveRecord esistenti funzionano immediatamente con i repository! ✨
Ecco cosa significa in pratica:
Prima (usando ActiveRecord direttamente):
function TCustomersController.GetCustomers: IMVCResponse;
begin
Result := OKResponse(TMVCActiveRecord.All<TCustomer>);
end;
Dopo (usando Repository Pattern):
function TCustomersController.GetCustomers(
[MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
Result := OKResponse(CustomersRepository.GetAll);
end;
La tua classe entità TCustomer
rimane esattamente la stessa! Stai semplicemente usando un pattern di accesso diverso. Questo significa che puoi:
- Migrare incrementalmente 📈 - Converti un controller o service alla volta
- Mescolare entrambi gli approcci 🔀 - Usa ActiveRecord in alcuni posti, Repository in altri
- Nessuna riscrittura richiesta 🎨 - I tuoi mapping delle entità, validazioni e logica di business rimangono intatti
- Scegli ciò che si adatta meglio 👍 - Usa il pattern che ha più senso per ogni caso specifico
Se la tua applicazione sta già usando ActiveRecord e vuoi introdurre la dependency injection o migliorare la testabilità, puoi iniziare a usare IMVCRepository<T>
immediatamente senza alcun refactoring del tuo layer di entità.
Quando Usare i Repository vs. ActiveRecord Direttamente
Usa i Repository quando:
- Stai costruendo applicazioni più grandi con logica di business complessa
- Hai bisogno di dependency injection per i test
- Vuoi una chiara separazione tra i layer
- Più sviluppatori stanno lavorando sulla stessa codebase
- Stai seguendo i principi del Domain-Driven Design
- Stai migrando verso un’architettura più testabile
Usa ActiveRecord direttamente quando:
- Stai costruendo semplici applicazioni CRUD
- Stai facendo prototipi o progetti proof-of-concept
- Il layer di astrazione aggiuntivo non è necessario
- Preferisci la semplicità dell’accesso diretto ai dati
- Stai lavorando su un piccolo microservizio focalizzato
Entrambi gli approcci sono validi, e puoi persino mescolarli nella stessa applicazione! La parte migliore è che passare da uno all’altro richiede modifiche minime al codice.
Ci Vediamo a ITDevCon 2025! 🎤
Presenterò una sessione approfondita sul Repository Pattern e altre funzionalità avanzate di DelphiMVCFramework a ITDevCon 2025 il 6-7 Novembre a Milano, Italia 🇮🇹.
Approfondiremo:
- Pattern repository avanzati 🏛️
- Strategie di testing con i repository 🧪
- Domain-Driven Design con Delphi 📐
- Best practice per applicazioni su larga scala 🚀
- Dimostrazioni di live coding 💻
Se sei seriamente interessato a costruire REST API migliori con Delphi, questo è un evento che non puoi perdere! Registrati ora su www.itdevcon.it
Conclusione
Il supporto al Repository Pattern in DelphiMVCFramework porta architetture di livello enterprise alle tue REST API senza sacrificare la semplicità e la produttività che gli sviluppatori Delphi amano. Che tu stia costruendo un piccolo microservizio o un’applicazione su larga scala, i repository ti danno la flessibilità e la struttura di cui hai bisogno. 💪
L’implementazione è leggera, sfruttando la base ActiveRecord esistente, quindi non c’è duplicazione di codice e overhead minimo. Ottieni codice pulito, testabile e manutenibile che segue le best practice del settore. ✨
Provalo nel tuo prossimo progetto e fammi sapere cosa ne pensi! Il framework è open source, quindi esplora gli esempi, sperimenta e non esitare a contribuire con miglioramenti. 🤝
Ci vediamo a ITDevCon 2025! 🎉
Link: 🔗
- DelphiMVCFramework su GitHub
- ITDevCon 2025 - 6-7 Novembre, Milano
- Progetti di esempio in:
/samples/repository_showcase
e/samples/simple_api_using_repository_with_injection
Buon coding! 👨💻
Daniele Teti
Comments
comments powered by Disqus