Magento 2

Magento fundamentals: che differenza c'è tra collection e repository?

Lettura 5 minuti

Una delle domande più frequenti che capita di fare quando si gestiscono entità in Magento 2 è: “dovrei usare un repository o una collection?”

In questo articolo vedremo quali sono le principali differenze senza scendere in dettagli troppo tecnici ma analizzando soprattutto pro e contro per cercare di comprendere quando scegliere un approccio o l’altro.

Qualche breve definizione

I repository rappresentano una novità in Magento 2 mentre le collection sono una eredità di Magento 1.

Entrambi sono utilizzati per gestire la persistenza delle entità ma appartengono a livelli di astrazione diversi.

I repository appartengono ad un livello di astrazione più alto che consente il completo disaccoppiamento dal livello di persistenza.

Traducendo la definizione di Martin Fowler, “un repository funge da intermediario tra il dominio e i dati e utilizza una interfaccia simile a quella delle collection per accedere agli oggetti di dominio.”

Fowler non menziona casualmente il dominio; infatti, il repository è un pattern introdotto dal Domain-Driven Design nel 2014.

Dall’altro lato, le collection appartengono ad un livello di astrazione più basso, in cui la query viene costruita utilizzando concetti molto vicini al dominio dei database; è il livello dei ben noti resource model.

Poiché i repository appartengono ad un livello di astrazione più alto, in Magento 2 sono spesso implementati come wrapper dei resource model e i loro metodi sono esposti attraverso le API pubbliche.

Ecco un semplice diagramma che riassume le relazioni tra i diversi livelli:

Due tipi di oggetto

Collection e repository non si istanziano allo stesso modo perché sono due oggetti di tipo diverso.

Le collection mantengono uno stato e quindi devono essere considerati dei newable object; i repository invece generalmente non mantengono uno stato e, in quanto tali, sono trattati come injectable object.

Tale distinzione è importante perché ci dice che le collection devono essere istanziate attraverso un factory mentre i repository possono essere istanziati attraverso dependency injection dichiarandone una dipendenza nei costruttori.

Per approfondire l’argomento, si può fare riferimento a questo articolo.

Approfondiamo lo stato di collection e repository

Vediamo in che senso le collection mantengono uno stato mentre i repository generalmente no.

Le collection possono essere considerate oggetti di tipo builder e servono a costruire query, anche se non aderiscono strettamente al builder pattern.

Per costruire una query, una collection viene inizializzata impostando, ad esempio, i campi dell’entità e i filtri che vogliamo applicare al set di dati.

Quando il metodo load() è invocato, lo stato della collection è utilizzato per costruire la query.

Vediamo un frammento di codice tratto dal metodo load() implementato nella classe base \Magento\Eav\Model\Entity\Collection\AbstractCollection:

<?php
abstract class AbstractCollection 
    extends AbstractDb 
    implements SourceProviderInterface
{
    // ...
    public function load($printQuery = false, $logQuery = false)
    {
        if ($this->isLoaded()) {
            return $this;
        }
        // ...build query and load data...
        $this->_setIsLoaded();
        // ...
        return $this;
    }
    // ...
}

Il metodo load() costruisce la query e recupera il risultato della sua esecuzione, memorizzandolo nello stato interno della collection.

Le successive chiamate al metodo load() non ripeteranno il passaggio precedente ma si limiteranno a tornare il dato precedentemente memorizzato, a meno di invocare prima il metodo clear().

☝ Invocare il metodo load() non è necessario perché è invocato dal metodo getIterator(), a sua volta invocato appena si prova a iterare gli elementi della collection.
Il metodo getIterator() è dichiarato nella interfaccia IteratorAggregate implementata da ogni collection di Magento.
Questo meccanismo prende il nome di lazy data loading, e ha diversi vantaggi:
- ci consente di modificare lo stato di una collection fino a quando i dati non sono effettivamente caricati;
- se una collection non viene iterata, le query non vengono eseguite.

I repository sono oggetti di servizio generalmente privi di stato.

In realtà i repository possono avere uno stato ma poiché, a differenza delle collection, questo non ha un effetto sul risultato, possiamo considerarli oggetti privi di stato.

Lo stato a cui mi riferisco è rappresentato dall’uso della cache che un repository può implementare per ottimizzare le prestazioni.

Prendiamo, ad esempio, la classe \Magento\Catalog\Model\ProductRepository: il metodo getById() salva le istanze di prodotto per le chiamate successive, come mostrato di seguito.

<?php
public function getById($productId, $editMode = false, $storeId = null, $forceReload = false)
{
    $cacheKey = $this->getCacheKey([$editMode, $storeId]);
    if (!isset($this->instancesById[$productId][$cacheKey]) || $forceReload) {
        $product = $this->productFactory->create();
        // ...
        $this->cacheProduct($cacheKey, $product);
    }
    return $this->instancesById[$productId][$cacheKey];
}

Tecnicamente, questo significa mantenere uno stato; per questo motivo aggiungo sempre “generalmente” quando dico che i repository non mantengono uno stato.

Pro e Contro

Ricapitoliamo i pro e i contro nell’utilizzo di collection e repository.

PRO

  • I repository semplificano l’accesso ai dati.
  • I repository astraggono l’accesso ai dati, permettendo ad esempio di cambiare il livello di gestione della persistenza senza impattare sul codice che li utilizza.
  • I repository consentono di implementare meccanismi per migliorare le prestazioni, come l’utilizzo della cache nell’esempio del Product repository visto in precedenza.
  • Le collection consentono di avere più controllo sull’accesso ai dati, in particolare sui campi recuperati e sui filtri applicati.

CONTRO

  • I repository offrono meno controllo sull’accesso ai dati.
  • Le collection sono legate al livello di gestione della persistenza, quindi il codice che le usa è più accoppiato e meno facile da modificare.

Dovremmo sempre implementare un repository per le entità custom?

Questa domanda ricorre tutte le volte che sviluppiamo una entità custom.

La mia risposta è sì, a meno che l’entità che stiamo sviluppando rappresenti una estensione (extension attribute) dell’entità principale.

Poiché i valori degli extension attribute in genere sono iniettati nell’entità principale, non è necessario implementare un repository specifico per la nostra entità custom.

Nel gergo del Domain-Driven Design, si direbbe che non ci serve un repository per le entità figlie di un aggregato (l’entità principale) poiché, citando Martin Fowler, “gli aggregati rappresentano una unità transazionale”.

Per approfondire, vi rimando a questo breve articolo.

Conclusioni

Abbiamo visto che ci sono pro e contro nell’utilizzo sia dei repository sia delle collection.

Le collection ci offrono più espressività poiché sono più vicine al livello di persistenza; i repository sono meno espressivi ma ci consentono di disaccoppiare maggiormente il codice che li utilizza dal livello di persistenza; ci consentono, inoltre, di implementare meccanismi per migliorare le performance, come la cache.

Conoscere le differenze tra i diversi elementi offerti dal framework Magento è essenziale per fare scelte consapevoli e ottenere il miglior risultato possibile.

Articolo scritto da

COO | Reggio Emilia

Alessandro lavora in Bitbull come team leader, esperto di software design e sviluppo di soluzioni ecommerce.
Membro attivo della community Magento come contributor e maintainer, ha ricevuto per tre volte il riconoscimento di Magento Master e Top 50 Contributor.
Dal 2020 è membro della Magento Association content committee.