Condizioni delle query del repository, dipendenze e DRY

2

Per semplificare, supponiamo che un'applicazione abbia Accounts e Users . Ogni account può avere un numero qualsiasi di utenti. Ci sono anche 3 consumatori di UserRepository :

  • Un'interfaccia di amministrazione che può elencare tutti gli utenti
  • Front-end pubblico che può elencare tutti gli utenti
  • Un'API autenticata dall'account che dovrebbe elencare solo i propri utenti

Supponendo che UserRepository è qualcosa del tipo:

class UsersRepository extends DatabaseAbstraction {
    private function query() {
        return $this->database()->select('users.*');
    }
    public function getAll() {
        return $this->query()->exec();
    }
    // IMPORTANT:
    // Tons of other methods for searching, filtering,
    // joining of other tables, ordering and such...
}

Tenendo presente il commento di cui sopra e la necessità di astrarre le condizioni di query degli utenti, come dovrei gestire l'interrogazione degli utenti che filtrano per account_id ? Posso immaginare tre possibili strade:

1. Devo creare un AccountUsersRepository ?

class AccountUsersRepository extends UserRepository {
    public function __construct(Account $account) {
        $this->account = $account;
    }
    private function query() {
        return parent::query()
            ->where('account_id', '=', $this->account->id);
    }
}

Questo ha il vantaggio di ridurre la duplicazione dei metodi UsersRepository , ma non si adatta abbastanza a tutto ciò che ho letto su DDD finora (sono tra l'altro rookie)

2. Dovrei metterlo come metodo su AccountsRepository ?

class AccountsRepository extends DatabaseAbstraction {
    public function getAccountUsers(Account $account) {
        return $this->database()
            ->select('users.*')
            ->where('account_id', '=', $account->id)
            ->exec();
    }
}

Ciò richiede la duplicazione di tutti i metodi UserRepository e potrebbe essere necessario un altro strato UserQuery , che implementa tali logiche di interrogazione in modo concatenabile.

3. Devo interrogare UserRepository dall'entità del mio account?

class Account extends Entity {
    public function getUsers() {
        return UserRepository::findByAccountId($this->id);
    }
}

Questo mi sembra più una radice aggregata per me, ma introduce la dipendenza di UserRepository su Account entità, che può violare alcuni principi.

4. O mi manca completamente il punto?

Forse c'è una soluzione ancora migliore?

Note a piè di pagina: Oltre alle autorizzazioni che riguardano un servizio, a mio avviso, non dovrebbero implementare la query SQL, ma lasciarle ai repository poiché potrebbero non essere nemmeno basate su SQL.

    
posta vFragosop 24.10.2013 - 04:47
fonte

3 risposte

1

Conserverei tutto User relativo all'accesso ai dati in UserRepository . Non è una buona idea fare in modo che i repository dipendano l'uno dall'altro, ma è perfettamente logico che un servizio dipenda da più repository. Quindi in questo caso potresti avere un AccountService che utilizza sia UserRepository che AccountRepository .

La terza opzione che hai considerato è qualcosa che ho anche usato e ha il vantaggio che ora l'elenco degli utenti viene caricato pigramente (anche se questo può essere ottenuto anche con altri mezzi). L'enorme svantaggio è che sarà molto difficile serializzare quell'oggetto per inviarlo via cavo, ad esempio e abbina anche il livello di accesso ai dati con il modello a oggetti.

    
risposta data 24.10.2013 - 10:11
fonte
1

Qui ci sono alcuni principi che possono influire sul tuo design:

Aggregates - se Account è il tuo elemento di primo livello (radice aggregata) per questo aggregato, che dovrebbe essere il tuo repository. Non è necessario un repository per tabella, in quanto ciò interrompe qualsiasi incapsulamento del dominio che si sta tentando di ottenere. Sulla base di ciò, prenderei in considerazione un singolo AccountRepository, che ha opzioni per richiamare gli utenti per account (es: findUsersInAccount(Account $account) ) La tua preoccupazione per i join qui è ciò che mi fa sospettare che sia così.

Contesti limitati - Fai attenzione a non riempire tutto in un singolo contesto limitato. Spesso, le interfacce pubbliche e di amministrazione stanno eseguendo obiettivi aziendali completamente diversi e possono essere separate in quanto tali. Intendiamoci, se entrambi sono trutly basta chiamare tutte le chiamate (), ignorate questo, ma dubito che questo sia il caso. Non conosco la tua applicazione, ma forse il lato admin agisce su più account, mentre la parte pubblica agisce solo su uno solo (l'account dell'utente). Avere due diversi sottodomini (o contesti limitati) è spesso meglio di uno perfetto che è distorto cercando di adattarsi ciascuno.

Realtà - Ora, supponendo che tu abbia ancora soddisfatto quanto sopra e stai incontrando dei problemi, il wrapping di entrambi in un servizio è probabilmente la strada da percorrere. Quel servizio può nascondere tutto l'accesso al repository (da più repository).

Tuttavia, nello sforzo di una buona progettazione OO, e in particolare di DDD, sconsiglio di fare quanto segue:

  • Estendere i repository gli uni dagli altri A MENO CHE sia astratto. Le interfacce sono spesso più appropriate qui se si desidera riutilizzare i modelli.
  • Costruire i repository attorno allo schema SQL. Non assumere un back-end SQL, in quanto tale mappatura sarà evidente nel tuo modello di dominio; esattamente dove non è consentito.

In ogni caso, senza comprendere pienamente il tuo dominio, sembra che qualcosa del genere dovrebbe soddisfare le tue esigenze:

interface AccountRepository
{
    function findUsersInAccount(Account $account);
}

interface UserRepository
{
    function findAll()
}

Il tuo modello di dominio può esporre più interfacce per le sue esigenze , mentre le implementazioni effettive del database di questi repository possono essere mescolate insieme se necessario. Concentrati solo su ciò di cui ha bisogno il tuo dominio e preoccupati di come verrà implementato in seguito.

Infine, DDD è pensato per applicazioni con domini complessi. Applicarlo dove non è necessario può creare molta confusione perché molti dei vantaggi non si applicano necessariamente.

    
risposta data 14.12.2013 - 20:57
fonte
0

Se ho capito bene, ti suggerirei di modellare sia Account che User come root aggregati (con i loro repository). Quindi, se hai bisogno di tutti gli utenti dell'account, puoi farlo in questo modo:

interface UserRepository
{
    public function findAll();
    public function findByAccount(Account $account);
}

Sei libero di "riciclare" il tuo codice all'interno del livello infrastruttura (lato implementazione), potresti farlo in questo modo:

class UserRepositoryImpl extends BaseRepository implements UserRepository
{
    public function findAll()
    {
        return $this->query('user');
    }

    public function findByAccount(Account $account)
    {
        // ...
    }
}

class AccountRepositoryImpl extends BaseRepository implements AccountRepository
{
    public function findAll()
    {
        return $this->query('account');
    }
}

abstract class BaseRepository
{
    protected function query($type = 'user')
    {
        // common part 1
        if ($type == 'account')
            $q .= 'AND account_id = ...';
        // common part 2
    }
}

Ricorda solo una cosa, il modello (compresa l'interfaccia del repository) non dovrebbe occuparsi di problemi come questo. È sempre una cattiva idea cambiare il modello in base alle esigenze di accesso di DB.

    
risposta data 14.12.2013 - 20:37
fonte

Leggi altre domande sui tag