è una cattiva pratica che il controller chiama il repository anziché il servizio?


44

è una cattiva pratica che il controller chiama il repository anziché il servizio?

per spiegare di più:

Ho scoperto che, in una buona progettazione, i controller chiamano il repository di servizio e servizio.

ma a volte nel controller non ho / ho bisogno di alcuna logica e devo solo recuperare da db e passarlo per visualizzarlo.

e posso farlo chiamando semplicemente il repository - non c'è bisogno di chiamare il servizio - è una cattiva pratica?


Come stai chiamando il servizio? Attraverso un'interfaccia REST?
Robert Harvey,

2
Uso questo approccio progettuale da solo piuttosto comunemente. Il mio controller (o una classe compositore sottostante) richiederà o invierà i dati al repository, quindi li passerà a tutte le classi di servizio che devono eseguire l'elaborazione. Nessun motivo per combinare le classi di elaborazione dei dati con le classi di recupero / gestione dei dati, sono diverse preoccupazioni anche se so che l'approccio tipico è farlo in quel modo.
Jimmy Hoffa,

3
Meh. Se si tratta di una piccola applicazione e stai solo cercando di ottenere dati da un database, un livello di servizio è una perdita di tempo a meno che quel livello di servizio non faccia parte di alcune API pubbliche come un'interfaccia REST. "Il latte fa bene o male a te?" Dipende se sei intollerante al lattosio.
Robert Harvey,

4
Non esiste una regola rigida e veloce che si dovrebbe avere un Controller -> Servizio -> Struttura del repository su Controller -> Repository. Scegli il modello giusto per l'applicazione giusta. Quello che direi è che dovresti rendere coerente la tua candidatura.
NikolaiDante

Forse potresti trovare un servizio generico che inoltra la tua richiesta al repository e poi ritorna. Ciò potrebbe essere utile per mantenere un'interfaccia uniforme e sarebbe semplice se in futuro fosse necessario aggiungere un servizio reale per fare qualcosa prima di chiamare il repository.
Henrique Barcelos,

Risposte:


32

No, pensala in questo modo: un repository è un servizio (anche).

Se le entità recuperate tramite il repository gestiscono la maggior parte della logica aziendale, non sono necessari altri servizi. Basta avere il repository è sufficiente.

Anche se hai alcuni servizi che devi attraversare per manipolare le tue entità. Prendi prima l'entità dal repository e poi passala a detto servizio. Essere in grado di lanciare un HTTP 404 prima ancora di provare è molto conveniente.

Inoltre, per gli scenari di lettura è comune, è sufficiente che l'entità lo proietti su un DTO / ViewModel. Avere un livello di servizio intermedio spesso porta a molti metodi di passaggio che sono piuttosto brutti.


2
Ben detto! La mia preferenza è quella di chiamare repository, e solo nei casi, quindi un repository non è sufficiente (cioè due entità devono essere modificate utilizzando repository diversi), creo un servizio che è responsabile di questa operazione e lo chiamo dal controller.
Zygimantas,

Avevo notato un codice piuttosto complicato solo per giustificare l'uso di un servizio. Assurdo, il minimo ...
Gi1ber7,

Quindi il mio repository restituisce un elenco di "oggetti business" che devo convertire in "oggetti xml", è questa ragione sufficiente per avere un livello di servizio? Sto chiamando un metodo su ciascun oggetto per convertirlo in un altro tipo e aggiungerlo a un nuovo elenco.
bot_bot

L'accesso diretto DAO è pericoloso nei controller, può renderti suscettibile alle iniezioni di SQL e ti dà accesso ad azioni pericolose come ,, deleteAll '' Lo eviterei sicuramente.
Anirudh,

4

Non è una cattiva pratica per un controller chiamare direttamente un repository. Un "servizio" è solo un altro strumento, quindi usalo dove ha senso.

NikolaiDante ha commentato:

... Scegli il modello giusto per l'applicazione giusta. Quello che direi è che dovresti rendere coerente la tua candidatura.

Non credo che la coerenza sia l'aspetto più importante. Una classe di "servizio" è intesa per incapsulare una logica di livello superiore in modo che il controller non debba implementarla. Se non è richiesta una "logica di livello superiore" per una determinata operazione, basta andare direttamente al repository.

Per promuovere una buona separazione delle preoccupazioni e della testabilità, il repository dovrebbe essere una dipendenza che si inietta nel servizio tramite un costruttore:

IFooRepository repository = new FooRepository();
FooService service = new FooService(repository);

service.DoSomething(...);

Se la ricerca di record nel database richiede una sorta di query con parametri, una classe di servizio potrebbe essere un buon posto per accettare il modello di visualizzazione e creare una query che viene quindi eseguita dal repository.

Allo stesso modo, se si dispone di un modello di visualizzazione complesso per un modulo, una classe di servizio può incapsulare la logica di creazione, aggiornamento ed eliminazione dei record chiamando metodi sui modelli / entità di dominio, quindi persistendoli utilizzando un repository.

Andando nella direzione opposta, se il controller ha bisogno di ottenere un record dal suo ID, delegare a un oggetto di servizio per questo è come colpire una puntina da disegno con una mazza - è molto più del necessario.

Ho scoperto che il controller è nella posizione migliore per gestire la transazione o un oggetto Unit Of Work . Il controller o l'oggetto Unit Of Work delegherà quindi agli oggetti di servizio per operazioni complesse o andrà direttamente al repository per operazioni semplici (come trovare un record per ID).

public class ShoppingCartsController : Controller
{
    [HttpPost]
    public ActionResult Edit(int id, ShoppingCartForm model)
    {
        // Controller initiates a database session and transaction
        using (IStoreContext store = new StoreContext())
        {
            // Controller goes directly to a repository to find a record by Id
            ShoppingCart cart = store.ShoppingCarts.Find(id);

            // Controller creates the service, and passes the repository and/or
            // the current transaction
            ShoppingCartService service = new ShoppingCartService(store.ShoppingCarts);

            if (cart == null)
                return HttpNotFound();

            if (ModelState.IsValid)
            {
                // Controller delegates to a service object to manipulate the
                // Domain Model (ShoppingCart)
                service.UpdateShoppingCart(model, cart);

                // Controller decides to commit changes
                store.SaveChanges();

                return RedirectToAction("Index", "Home");
            }
            else
            {
                return View(model);
            }
        }
    }
}

Penso che un mix di servizi e lavorare direttamente con i repository sia perfettamente accettabile. Potresti incapsulare ulteriormente la transazione in un oggetto Unit Of Work se ne sentissi la necessità.

La ripartizione delle responsabilità è la seguente:

  • Il controller controlla il flusso dell'applicazione
    • Restituisce "404 non trovato" se il carrello non è presente nel database
    • Riesegue il rendering del modulo con messaggi di convalida se la convalida non riesce
    • Salva il carrello se tutto viene estratto
  • Il controller delega a una classe di servizio per eseguire la logica aziendale sui modelli di dominio (o entità). Gli oggetti di servizio non devono implementare la logica aziendale! Essi eseguono la logica di business.
  • I controllori possono delegare direttamente ai repository per operazioni semplici
  • Gli oggetti di servizio accettano i dati nel modello di vista e delegano a Modelli di dominio per eseguire la logica aziendale (ad es. L'oggetto di servizio chiama i metodi sui Modelli di dominio prima di chiamare i metodi nel repository)
  • Gli oggetti del servizio delegati ai repository per la persistenza dei dati
  • I controller dovrebbero:
    1. Gestire la durata di una transazione, o
    2. Creare un oggetto Unit Of Work per gestire la durata di una transazione

1
-1 per inserire DbContext in un controller anziché in un repository. Il repository ha lo scopo di gestire i fornitori di dati, quindi nessun altro deve nel caso in cui un fornitore di dati cambi (ad esempio da MySQL a file JSON piatti, cambiare in un unico posto)
Jimmy Hoffa,

@JimmyHoffa: In realtà sto guardando indietro al codice che ho scritto e, ad essere sincero, creo un oggetto "contestuale" per i miei repository, non necessario il database. Penso che DbContextsia un brutto nome in questo caso. Lo cambierò. Uso NHibernate e i repository (o il contesto se è utile) gestiscono la fine del database delle cose, quindi cambiare i meccanismi di persistenza non richiede cambiamenti di codice al di fuori del contesto.
Greg Burghardt,

sembri confondere il controller con il repository dall'aspetto del tuo codice ... Voglio dire, il tuo "contesto" è tutto sbagliato e non dovrebbe assolutamente esistere nel controller.
Jimmy Hoffa,

6
Non devo rispondere e non sono sicuro che questa sia una buona domanda per cominciare, ma voto in negativo perché penso che il tuo approccio sia un cattivo design. Nessun sentimento duro, scoraggio solo i contesti di proprietà dei controller. IMO un controller non dovrebbe iniziare e commettere transazioni come questa. Questo è il lavoro di un numero qualsiasi di altri posti, preferisco i controller delegare tutto ciò che non soddisfa semplicemente la richiesta HTTP da qualche altra parte.
Jimmy Hoffa,

1
Un repository, in genere è responsabile di tutte le informazioni sul contesto dei dati per garantire che nient'altro abbia bisogno di sapere nulla sui dati relativi a problemi diversi da ciò di cui il dominio stesso deve essere a conoscenza
Jimmy Hoffa,

1

Dipende dalla tua architettura. Uso Spring e la transazionalità è sempre gestita dai servizi.

Se chiamate repository direttamente per operazioni di scrittura (o semplici servizi senza logica che semplicemente delegano al repository), probabilmente state utilizzando diverse transazioni di database per un'operazione che deve essere eseguita in una sola. Ciò porterà a dati incoerenti nel database. Come regola generale, le operazioni del database dovrebbero funzionare o dovrebbero fallire, ma le operazioni a metà lavoro sono la causa del mal di testa.

Per questa ragione penso che chiamare i repository direttamente dai controller o usare semplici servizi di delega sia una cattiva pratica. Inizi a farlo solo per leggere, e molto presto tu, o uno o i tuoi compagni inizierete a farlo per le operazioni di scrittura.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.