Mettere una certa logica di business nei repository invece di tenerlo completamente fuori dai repository?

3

So che la maggior parte (se non tutti) la logica di business dovrebbe risiedere nel proprio livello, ma qual è il consenso generale di mettere alcune logiche di business di base all'interno del livello di repository stesso?

Il mio scenario: Abbiamo una tabella che ha alcune colonne opzionali, ma se una delle colonne contiene un valore, le altre due saranno influenzate da quel valore.

Un POCO di un'entità per quella tabella assomiglia a qualcosa di simile:

public class Template
{
     public int Id {get;set;}
     public string MessageTemplate {get;set;}
     public int? FacilityId {get;set;}
     public int? ResourceId {get;set;}
     public bit GlobalDefault {get;set;}
}

Ora dovrebbe esserci solo una voce che è designata come predefinita globale e, analogamente, ogni struttura o risorsa dovrebbe essere elencata nella tabella una sola volta. Gli ultimi vincoli vengono applicati da SQL Server, ma ciò richiede alcune logiche di business per assicurarsi che tutto scorra liscio.

Nel caso di questa tabella, se dovessimo tentare di inserire un nuovo record per una struttura o risorsa esistente, il record precedente dovrebbe essere aggiornato (o eliminato). Lo stesso vale per l'impostazione di un nuovo default globale. Solo un'entità dovrebbe essere l'impostazione predefinita globale, quindi se ne esiste un'altra, dovrebbe essere cancellata o aggiornata.

Ora ovviamente, il mio livello aziendale gestirà questi scenari al meglio delle loro possibilità, ma quanto è malvagio implementare anche quel livello di logica nei miei archivi stessi?

Sto immaginando qualcosa come il seguente nel mio repository:

public void InsertFacilityTemplate(Template template) 
{
    var existing = context.Templates.Where(n => n.FacilityId == template.FacilityId);
    Template recordToSave;
    if (existing.Count > 1) 
        DeleteRecords(existing);
    if (existing.Count == 1)
        recordToSave= existing.First();

    if (recordToSave != null)
    {
        recordToSave.MessageTemplate = template.MessageTemplate;
        Update(recordToSave);
    }
    else
        Insert(template);
}

Qual è l'approccio corretto qui?

    
posta JD Davis 16.08.2018 - 22:20
fonte

2 risposte

2

Database e metodi usano spesso il termine upsert per le azioni che inseriscono o si aggiornano in base all'esistenza. Va bene avere un metodo upsert in un repository.

Non è chiaro dalla tua descrizione se stai chiedendo di ripetere la logica di inserimento / aggiornamento altrove, o stai chiedendo a dove di mettere questa logica (ad es. metodo di esempio) .

  • La ripetizione della logica in un passaggio precedente o successivo è generalmente una cattiva pratica. Se questa logica viene ripetuta altrove , allora dovrebbe esserci un effetto diverso come dare all'utente finale un messaggio di errore / avvertimento e impedire ulteriori azioni. Gestire altrove allo scopo di " più checks = better chance of catching " è un'indicazione che la logica è tenuta insieme dalla speranza invece che dalla gestione intenzionale.
  • Il tuo esempio potrebbe usare più "separazione delle preoccupazioni" che si riferisce a dove parte della logica va. Sebbene il metodo repo abbia il nome "InsertX", aggiorna ed elimina anche . L'eliminazione qui è un effetto collaterale che è una pratica disapprovata quando non intenzionale o poco chiaro. Prendi in considerazione la possibilità di chiamare un metodo separato che rende la preparazione upsert più chiara come RemoveDuplicateInstances(ITemplate template) o PrepareUpsert(string templateId) . Anche l'esempio presuppone di sapere quale sia il campo aggiornato . Ad esempio, se il valore GlobalDefault è stato attivato, o è stato aggiunto un altro campo alla classe, allora l'Insert avrebbe mantenuto tali modifiche mentre la logica dell'aggiornamento non lo avrebbe fatto. Sarebbe un comportamento incoerente con le aspettative.

Riguardo alle preoccupazioni, se la tua domanda è piccola, proof-of-concept o estremamente semplice, questi cambiamenti saranno così limitati da non fare la differenza sul piano funzionale. farà fare la differenza se qualcun altro viene dopo di te o l'intenzione è di continuare ad espandere questo codice base.

    
risposta data 27.09.2018 - 20:22
fonte
0

La radice del problema sembra essere un modello di dominio anemico . Non c'è niente di sbagliato nell'avere una classe POCO per rappresentare il modello di dati. Il problema che stai cercando di risolvere deriva dalle seguenti regole aziendali:

  1. Una "funzione" può avere più modelli, purché ogni modello per quella struttura faccia riferimento a una "risorsa" univoca.

  2. Solo 1 dei modelli per una struttura dovrebbe essere il "predefinito globale".

Date queste regole devi spingere questa logica in una classe che ha un handle su una "struttura" e tutti i suoi "modelli".

Qualcosa come:

var facility = // get by facility Id
var template = new Template() { /* initialize properties */ };

facility.SetGlobalTemplate(template);

Quindi il metodo SetGlobalTemplate esegue il ciclo sui modelli associati a tale funzione e imposta i flag dei bit di conseguenza.

Quindi il tuo "repository" ha solo bisogno di salvare la nuova struttura. Assicurati che i tuoi mapping dei dati possano prelevare il nuovo modello e l'aggiornamento al precedente modello globale. Qualsiasi ORM a metà strada dovrebbe essere in grado di farlo (Entity Framework, NHibernate, ecc.)

repository.SaveFacility(facility);
    
risposta data 27.09.2018 - 21:25
fonte

Leggi altre domande sui tag