Gestione DDD di tabelle, repository e / o servizi

3

Lottare per gestire gli aggregati e aggregare le radici quando si tratta di persistere. Ad esempio, attualmente ho un repository per ogni aggregato / tabella, ma questo sta causando alcuni problemi nella mia classe di servizio.

Current JournalService

public class JournalService
{
    public JournalService(IUnitOfWork uow, IJournalRepository journalRepo)
    {
        this.uow = uow;
        this.journalRepo = journalRepo;
    }

    public void AddEntry(string journalName, Entry entry)
    {
        journalEntryRepo.AddEntry(journalName, entry);

        // there is no need for me to even have a journal class or any other domain class since repositories handle it all

        uow.Save();
    }

    private readonly IUnitOfWork uow;
    private readonly IJournalRepository journalRepo;
}

Nell'esempio sopra (quello che sto attualmente usando), sembra che non abbia senso avere anche classi di dominio come Journal. Potrei semplicemente gestire tutto accedendo ai repository. Questo mi sembra molto strano .. Quasi sento che non esiste un dominio.

Dopo ulteriori ricerche sembra che un repository dovrebbe gestire una radice aggregata; e restituire l'intera radice su cui quindi operare. Il problema che sto riscontrando con questa soluzione proposta è, devo solo chiamare l'aggiornamento sulla radice aggregata anche se ho aggiunto degli oggetti (ad esempio a un elenco)? Vedi sotto:

Proposta di nuovo JournalService

public class JournalService
{
    public JournalService(IUnitOfWork uow, IJournalRepository journalRepo)
    {
        this.uow = uow;
        this.journalRepo = journalRepo;
    }

    public void AddEntry(string journalName, Entry entry)
    {
        Journal journal = journalRepo.FindById(journalName);

        journal.AddEntry(entry);

        journalRepo.Update(journal);   ///// am I handling this correctly here?

        uow.Save();
    }

    private readonly IUnitOfWork uow;
    private readonly IJournalRepository journalRepo;
}

// simple CRUD for repo
public interface IJournalRepository
{
    Journal Create();
    Journal FindById(string name);
    void Update(Journal journal);
    void Remove(Journal journal);
}

Con la nuova soluzione, sto gestendo questo correttamente? Quindi suppongo che IJournalRepository debba gestire l'identificazione delle voci aggiunte e quindi aggiungerle alla tabella corretta? Anche se sembra molto lavoro extra ...

    
posta keelerjr12 12.04.2018 - 17:06
fonte

2 risposte

2

I almost feel that there is no domain.

Alcuni domini sono intrinsecamente anemici. Se una determinata scrittura non dipende dallo stato corrente, non c'è molto altro da fare oltre a guardare la rappresentazione in entrata e assicurarsi che abbia la forma corretta.

Un grande indizio qui è che tutto ciò che è necessario sapere per eseguire la scrittura nel database è contenuto nei parametri della richiesta stessi. Tutto quello che stai facendo è prendere una rappresentazione che ti è stata passata dal client e trasformarla in un messaggio che hai inviato al tuo archivio di persistenza.

Quindi i modelli, i repository e gli orsi danzanti non aggiungono alcun valore commerciale.

Ora - supponiamo che tu aggiunga un altro requisito: che ogni voce nel diario doveva essere unica. Potrebbe essere importante per te nel caso in cui stavi ricevendo messaggi su una rete inaffidabile e dovessi evitare di aggiungere messaggi duplicati.

Con alcuni archivi dati, potresti essere in grado di inviarlo solo un comando WriteIfAbsent (entry), nel qual caso questa è ancora solo una trasformazione.

Ma se è necessario implementare questa logica da soli, ora è necessario caricare la cronologia per questo diario, confrontare questa voce con quelli già presenti, aggiungere la voce se è mancante e memorizzare il risultato. In altre parole, il messaggio che invii all'archivio dati dipende dai dati già presenti nello store. In tal caso, potresti voler separare la logica del dominio, che si preoccupa dell'unicità delle voci dell'elenco ma non importa dove è memorizzato l'elenco, dalle componenti di persistenza che si preoccupano di dove è memorizzato l'elenco, ma non ti interessa tutto merito dell'unicità delle voci.

Poiché le regole per ciò che dovrebbe essere scritto diventano più complesse, il valore di (a) avere tutte quelle regole in un confine ben definito e (b) avere quel limite può essere rapidamente testabile senza trascinare in un mucchio di spese generali non correlate a le regole, sale.

    
risposta data 12.04.2018 - 17:53
fonte
0

Penso che ti sembri strano perché non c'è un repository (e il servizio, se è per questo) per Entry mentre dovrebbe, IMHO. Inoltre, credo che tu abbia bisogno di una risposta più "concreta", quindi ti faccio un esempio di come configurerei:

(1) Utilizza le classi del modello di domail ( Journal , Entry ) e vai con un progetto di repository generico:

public interface IRepository<T, TId>  where T : class
{
    IEnumerable<T> FindAll();
    IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
    void Save(T entity);
    void Add(T entity);
    void Remove(T entity);
}

(2) Fornire un'implementazione one per la funzionalità di repository comune:

public abstract class Repository<T, TEntityKey> where T : class
{
    private readonly JournalDbContext _dbContext = new JournalDbContext();

    public void Save(T entity)
    {
        _dbContext.Entry(entity).State = EntityState.Modified;
        _dbContext.SaveChanges();
    }

    public void Add(T entity)
    {
        _dbContext.Set<T>().Add(entity);
        _dbContext.SaveChanges();
    }

    public void Remove(T entity)
    {
        _dbContext.Set<T>().Remove(entity);
        _dbContext.SaveChanges();
    }

    public IEnumerable<T> FindAll()
    {
        return _dbContext.Set<T>();
    }

    public IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate)
    {
        return _dbContext.Set<T>().Where(predicate);
    }
}

(3) Le interfacce per il repository di entità possono essere definite come segue. Dovrebbe essere presente anche qualsiasi funzionalità specifica di repository:

public interface IJournalRepository : IRepository<Journal, int>
{
    // anything besides the common repository functionality
    Journal FindByName(string name);
}

public interface IEntryRepository : IRepository<Entry, int>
{
    // ...
}

(4) Implementazioni di archivi concreti se una specifica funzionalità di repository è definita in (3) sopra o solo le dichiarazioni di classe in caso contrario:

public class JournalRepository : Repository<Journal, int>, IJournalRepository
{
    public Journal FindByName(string name)
    {
        return FindAll().SingleOrDefault(j => j.Name == name);
    }        
}

public class EntryRepository : Repository<Entry, int>, IEntryRepository
{
    // ...
}

(5) Tratta Entry come radice aggregata e dispone anche di un servizio per poter scrivere (esempio basato sul tuo codice):

public EntryService(IUnitOfWork uow, IEntryRepository entryRepo, IJournalRepository journalRepo)
{
    this.uow = uow;
    this.entryRepo = entryRepo;
    this.journalRepo = journalRepo;
}

public void Add(string journalName, Entry entry)
{
    Journal journal = journalRepo.FindByName(journalName);

    entry.JournalId = journal.Id;  // associate entry with the journal  

    entryRepo.Add(entry);   

    uow.Save();
}     
    
risposta data 13.04.2018 - 12:39
fonte

Leggi altre domande sui tag