in DDD, i repository dovrebbero esporre un'entità o oggetti di dominio?

10

A quanto ho capito, in DDD, è appropriato utilizzare un modello di repository con una radice aggregata. La mia domanda è, dovrei restituire i dati come entità o oggetti di dominio / DTO?

Forse qualche codice spiegherà ulteriormente la mia domanda:

Ente

public class Customer
{
  public Guid Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

Dovrei fare qualcosa di simile?

public Customer GetCustomerByName(string name) { /*some code*/ }

O qualcosa del genere?

public class CustomerDTO
{
  public Guid Id { get; set; }
  public FullName { get; set; }
}

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

Domanda aggiuntiva:

  1. In un repository, dovrei restituire IQueryable o IEnumerable?
  2. In un servizio o repository, dovrei fare qualcosa del tipo .. GetCustomerByLastName , GetCustomerByFirstName , GetCustomerByEmail ? o semplicemente creare un metodo simile a GetCustomerBy(Func<string, bool> predicate) ?
posta codefish 06.01.2016 - 15:21
fonte

3 risposte

7

should I return the data as an entity or domain objects/DTO

Bene, dipende interamente dai casi d'uso. L'unica ragione per cui posso pensare di restituire un DTO invece di un'entità completa è se la tua entità è enorme e devi solo lavorare su un sottoinsieme di essa.

Se questo è il caso, forse dovresti riconsiderare il tuo modello di dominio e dividere la tua grande entità in entità minori correlate.

  1. In a repository, should I return IQueryable or IEnumerable?

Una buona regola empirica è quella di restituire sempre il tipo più semplice (il più alto nella gerarchia dell'eredità) possibile. Quindi, restituisci IEnumerable a meno che non desideri consentire al consumer del repository di lavorare con un IQueryable .

Personalmente, penso che restituire un IQueryable sia un'astrazione che perde ma ho incontrato diversi sviluppatori che sostengono con passione che non lo è. Nella mia mente tutta la logica di interrogazione dovrebbe essere contenuta e nascosta dal Repository. Se si consente al codice di chiamata di personalizzare la propria query, qual è il punto del repository?

  1. In a service or repository, should I do something like.. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? or just make a method that is something like GetCustomerBy(Func predicate)?

Per la stessa ragione per cui ho menzionato al punto 1, sicuramente non usa GetCustomerBy(Func<string, bool> predicate) . All'inizio può sembrare allettante, ma questo è esattamente il motivo per cui le persone hanno imparato a odiare i repository generici. È perdente.

Cose come GetByPredicate(Func<T, bool> predicate) sono utili solo quando sono nascoste dietro classi concrete. Quindi, se tu avessi una classe base astratta chiamata RepositoryBase<T> che ha esposto protected T GetByPredicate(Func<T, bool> predicate) che è stata utilizzata solo da repository concreti (ad es. public class CustomerRepository : RepositoryBase<Customer> ), allora andrebbe bene.

    
risposta data 06.01.2016 - 15:55
fonte
6

C'è una considerevole comunità di persone che usano CQRS per implementare i loro domini. La mia sensazione è che, se l'interfaccia del tuo repository è analoga alle migliori pratiche utilizzate da loro, che non andrai troppo lontano fuori strada.

Basato su ciò che ho visto ...

1) I gestori di comandi di solito utilizzano il repository per caricare l'aggregato tramite un repository. I comandi mirano a una singola istanza specifica dell'aggregato; il repository carica la radice per ID. Non c'è, che io possa vedere, un caso in cui i comandi sono eseguiti contro una raccolta di aggregati (invece, si dovrebbe prima eseguire una query per ottenere la raccolta di aggregati, quindi enumerare la raccolta e inviare un comando a ciascuno.

Pertanto, nei contesti in cui modificherete l'aggregato, mi aspetto che il repository restituisca l'entità (ovvero la radice aggregata).

2) I gestori di query non toccano affatto gli aggregati; invece, funzionano con le proiezioni degli aggregati - oggetti valore che descrivono lo stato dell'aggregato / aggregati in un determinato momento. Quindi pensa a ProjectionDTO, piuttosto che AggregateDTO, e hai l'idea giusta.

Nei contesti in cui si stanno eseguendo query sull'aggregato, preparandolo per la visualizzazione e così via, mi aspetto di vedere restituito un DTO o una raccolta DTO piuttosto che un'entità.

Tutte le tue chiamate getCustomerByProperty mi sembrano simili, quindi rientrerebbero in quest'ultima categoria. Probabilmente vorrei utilizzare un singolo punto di ingresso per generare la raccolta, quindi dovrei cercare di vedere se

getCustomersThatSatisfy(Specification spec)

è una scelta ragionevole; i gestori di query costruiranno quindi la specifica appropriata dai parametri forniti e passeranno tale specifica al repository. Lo svantaggio è che la firma suggerisce davvero che il repository è una raccolta in memoria; non mi è chiaro che il predicato ti comprenda molto se il repository è solo un'astrazione di eseguire un'istruzione SQL su un database relazionale.

Ci sono alcuni schemi che possono aiutare, però. Ad esempio, invece di compilare manualmente le specifiche, passare al repository una descrizione dei vincoli e consentire l'implementazione del repository per decidere cosa fare.

Attenzione: digitata java come rilevata

interface CustomerRepository {
    interface ConstraintBuilder {
        void setLastName();
        void setFirstName();
    }

    interface ConstraintDescriptor {
        void copyTo(ConstraintBuilder builder);
    }

    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor);
}

SQLBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        WhereClauseBuilder builder = new WhereClauseBuilder();
        descriptor.copyTo(builder);
        Query q = createQuery(builder.build());
        //...
     }
}

CollectionBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        PredicateBuilder builder = new PredicateBuilder();
        descriptor.copyTo(builder);
        Predicate p = builder.build();
        // ...
}

class MatchLastName implements CustomerRepository.ConstraintDescriptor {
    private final lastName;
    // ...

    void copyTo(CustomerRepository.ConstraintBuilder builder) {
        builder.setLastName(this.lastName);
    }
}

In conclusione: la scelta tra fornire un aggregato e fornire un DTO dipende da cosa si aspetta che il consumatore faccia con esso. La mia ipotesi sarebbe una implementazione concreta che supporti un'interfaccia per ogni contesto.

    
risposta data 06.01.2016 - 17:15
fonte
1

In base alla mia conoscenza della DDD dovresti avere questo,

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

In a repository, should I return IQueryable or IEnumerable?

Dipende, per questa domanda troverai opinioni miste di popoli diversi. Ma personalmente credo che se il tuo repository sarà utilizzato da un servizio come CustomerService, allora puoi usare IQueryable altrimenti rimani con IEnumerable.

I repository dovrebbero restituire IQueryable?

In a service or repository, should I do something like.. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? or just make a method that is something like GetCustomerBy(Func predicate)?

Nel tuo repository dovresti avere una funzione generica come hai detto GetCustomer(Func predicate) , ma nel tuo livello di servizio aggiungi tre metodi diversi, questo perché c'è la possibilità che il tuo servizio venga chiamato da diversi client e hanno bisogno di DTO diversi.

Puoi anche usare Pattern di deposito generico per comuni CRUD, se non lo sai già.

    
risposta data 06.01.2016 - 15:48
fonte

Leggi altre domande sui tag