Dove si inserisce il "livello di logica aziendale" in un'applicazione MVC?


86

Innanzitutto, prima che qualcuno urli stupido, ho avuto difficoltà a riassumerlo in un titolo semplice. Un altro titolo potrebbe essere stato "Qual è la differenza tra un modello di dominio e un modello MVC?" o "Cos'è un modello?"

Concettualmente, ritengo che un modello sia i dati utilizzati dalle viste e dal controller. Oltre a ciò, sembra che ci siano molte opinioni divergenti su ciò che costituisce il modello. Che cos'è un modello di dominio, rispetto a un modello di app, rispetto a un modello di visualizzazione, rispetto a un modello di servizio, ecc.

Ad esempio, in una recente domanda che ho posto sul pattern del repository, mi è stato detto di punto in bianco che il repository fa parte del modello. Tuttavia, ho letto altre opinioni secondo cui il modello dovrebbe essere separato dal modello di persistenza e dal livello di logica aziendale. Dopotutto, il pattern Repository non dovrebbe separare il metodo di persistenza concreta dal modello? Altre persone dicono che c'è una differenza tra il modello Domain e il modello MVC.

Facciamo un semplice esempio. AccountController incluso nel progetto predefinito MVC. Ho letto diverse opinioni secondo cui il codice account incluso è di cattivo design, viola SRP, ecc. Ecc. Se si progettasse un modello di appartenenza "corretto" per un'applicazione MVC, quale sarebbe?

Come separereste i servizi ASP.NET (provider di appartenenza, provider di ruoli, ecc.) Dal modello? O lo faresti affatto?

Per come la vedo io, il modello dovrebbe essere "puro", magari con logica di validazione .. ma dovrebbe essere separato dalle regole aziendali (diverse dalla validazione). Ad esempio, supponiamo che tu abbia una regola aziendale che dice che qualcuno deve essere inviato via email quando viene creato un nuovo account. A mio avviso, questo non appartiene veramente al modello. Allora da dove viene?

Qualcuno vuole far luce su questo problema?


1
Ecco perché dovresti porre quattro domande separate.
John Farrell

3
La parola chiave è "quasi". È davvero la stessa domanda, con forse sotto-domande usate per illustrare la domanda principale.
Erik Funkenbusch

3
Modello - Visualizza - Controller. È reposirory / BL View? No. È il controller? No. Cosa resta :)? È MVC, non MSVC, non MRVC, non MBLVC. Ci sono solo tre strati. Quindi il repository fa parte del modello, BL fa parte del modello. E puoi creare una separazione aggiuntiva, ma viene eseguita all'interno del livello del modello.
LukLed

3
@LukeLed, @bslm - Non proprio. MVC non dice che non possono esserci altri livelli con cui il controller o il modello interagisce.
John Farrell

3
@LukLed - Non sono d'accordo - MVC è semplicemente un modello di livello di presentazione. Non ha alcun impatto su come strutturi gli altri livelli come BLL e DAL.
Cory House,

Risposte:


69

Il modo in cui l'ho fatto - e non sto dicendo che sia giusto o sbagliato, è avere la mia vista e quindi un modello che si applica alla mia vista. Questo modello ha solo ciò che è rilevante per la mia vista, comprese le annotazioni dei dati e le regole di convalida. Il controller ospita solo la logica per la creazione del modello. Ho un livello di servizio che ospita tutta la logica aziendale. I miei controller chiamano il mio livello di servizio. Oltre a questo c'è il mio livello di repository.

Gli oggetti del mio dominio sono alloggiati separatamente (nel loro progetto, in realtà). Hanno le proprie annotazioni dei dati e regole di convalida. Il mio repository convalida gli oggetti nel mio dominio prima di salvarli nel database. Poiché ogni oggetto nel mio dominio eredita da una classe base che ha la convalida incorporata, il mio repository è generico e convalida tutto (e richiede che erediti dalla classe base).

Potresti pensare che avere due serie di modelli sia una duplicazione del codice, e lo è in una certa misura. Tuttavia, esistono casi perfettamente ragionevoli in cui l'oggetto dominio non è appropriato per la visualizzazione.

Il caso in questione è quando si lavora con le carte di credito: devo richiedere un cvv durante l'elaborazione di un pagamento, ma non posso memorizzare il cvv (è una multa di $ 50.000 per farlo). Ma voglio anche che tu sia in grado di modificare la tua carta di credito: cambio di indirizzo, nome o data di scadenza. Ma non mi darai il numero o il cvv quando lo modifichi, e certamente non inserirò il numero della tua carta di credito in chiaro sulla pagina. Il mio dominio ha questi valori necessari per salvare una nuova carta di credito perché me li dai, ma il mio modello di modifica non include nemmeno il numero della carta o il cvv.

Un altro vantaggio di così tanti livelli è che, se progettato correttamente, è possibile utilizzare structuremap o un altro contenitore IoC e scambiare i pezzi senza influire negativamente sull'applicazione.

A mio parere, il codice del controller dovrebbe essere solo codice mirato alla visualizzazione. Mostra questo, nascondi quello, ecc. Il livello di servizio dovrebbe ospitare la logica di business della tua app. Mi piace avere tutto in un unico posto, quindi è facile cambiare o modificare una regola aziendale. Il livello del repository dovrebbe essere relativamente stupido, privo di logica aziendale e solo interrogare i dati e restituire gli oggetti del dominio. Separando i modelli di visualizzazione dal modello di dominio, hai molta più flessibilità quando si tratta di regole di convalida personalizzate. Significa anche che non devi scaricare tutti i dati nella tua vista in campi nascosti e spingerli avanti e indietro tra il client e il server (o ricostruirli sul back-end).

<% if (!String.IsNullOrEmpty(Model.SomeObject.SomeProperty) && 
    Model.SomeObject.SomeInt == 3 && ...) { %>

Mentre tutto sembra sparso e stratificato, ha uno scopo per essere progettato in questo modo. È perfetto? non proprio. Ma lo preferisco ad alcuni progetti precedenti di chiamata di repository dal controller e di logica aziendale mista nel controller, nel repository e nel modello.


Quasi un mirror di quello che ho nella nostra applicazione MVC aziendale. Un'architettura a più livelli. L'app MVC interagisce solo con oggetti e servizi di business nelle aree N-Tier.
Ed DeGagne

Per lo più lo stesso qui. Progetti separati per definizioni, modelli, viewmodels, DAL, ecc. L'unica differenza è che il mio DAL include la logica per appiattire i dati per il Web per ottimizzare la distribuzione di dati complessi per report o visualizzazioni personalizzate dei clienti. Ora evito di tenere le cose nella cache dell'applicazione per tabelle di ricerca, ecc., Con Web farm e cloud di Azure in gioco.
Robert Achmann

1
@ Josh, sarebbe utile se potessi mostrare una schermata del tuo progetto di esempio?
shaijut

@ Josh e se il tuo progetto non avesse un database. Interagisce con i riferimenti al servizio. Tutti i metodi e le classi di dominio provengono da questi riferimenti. Questo scenario è adatto per la struttura a strati?
user6395764

17

Troppo spesso mi sono chiesto come si inseriscono esattamente gli elementi MVC in una struttura di un'applicazione web tradizionale, in cui si hanno visualizzazioni (pagine), controller, servizi e oggetti dati (modello). Come hai detto, ci sono molte versioni di questo.

Credo che la confusione esista a causa dell'architettura sopra menzionata, ampiamente accettata, che utilizza il "modello di dominio anemico" (presunto) -anti pattern. Non entrerò in molti dettagli sull '"anti-patternness" del modello di dati anemico (puoi guardare un mio sforzo per spiegare le cose qui (basato su Java, ma pertinente per qualsiasi linguaggio)). Ma in breve, significa che il nostro modello contiene solo dati e la logica di business è posta nei servizi / manager.

Ma supponiamo di avere un'architettura guidata dal dominio e che i nostri oggetti di dominio siano come dovrebbero essere, con logica di stato e di business. E in questa prospettiva guidata dal dominio le cose vanno a posto:

  • la vista è l'interfaccia utente
  • il controller raccoglie gli input dell'interfaccia utente, richiama metodi sul modello e invia una risposta all'interfaccia utente
  • il modello è i nostri componenti di business: contenere i dati, ma anche avere una logica di business.

Immagino che risponda alle tue domande principali. Le cose si complicano quando aggiungiamo altri livelli, come il livello del repository. Si suggerisce spesso che dovrebbe essere invocato dalla logica di business inserita nel modello (e quindi ogni oggetto di dominio ha un riferimento a un repository). Nel mio articolo che ho collegato sostengo che questa non è proprio una best practice. E che in effetti non è una brutta cosa avere un livello di servizio. A proposito, la progettazione guidata dal dominio non esclude il livello di servizio, ma dovrebbe essere "sottile" e coordinare solo gli oggetti del dominio (quindi nessuna logica di business lì).

Per il paradigma del modello di dati anemico, ampiamente adottato (nel bene e nel male), il modello sarebbe sia il livello di servizio che gli oggetti dati.


Ottimo punto! Un'osservazione: c'è lo stesso casino con i servizi. Almeno i servizi possono essere servizi dell'applicazione e servizi di dominio. Il servizio applicativo è solo un thin wrapper, che raccoglie informazioni dai repository, ecc. Il servizio di dominio fornisce la logica di business, ovvero utilizza una combinazione di modelli di dominio o solo cose che non sempre si adattano al modello di dominio.
Artru

cosa succede se il tuo progetto non dispone di database. Interagisce con i riferimenti al servizio. Tutti i metodi e le classi di dominio provengono da questi riferimenti. Questo scenario è adatto per la struttura a strati?
user6395764

3

Secondo me,

Modello -

Non dovrebbe contenere logica aziendale, dovrebbe essere collegabile (scenario simile a WCF). Viene utilizzato per associare alla visualizzazione, quindi dovrebbe avere proprietà.

Logica di business -

Dovrebbe essere posizionato su "Domain Services Layer", è un livello separato del tutto. Inoltre, aggiungerà un altro livello qui "Servizi applicativi".

I servizi app comunicano al livello dei servizi di dominio per applicare la logica di business e infine restituire il modello.

Quindi, il controller chiederà al servizio dell'applicazione il modello e il flusso andrà come,

    Controller->Application Services(using domain services)->Model

2

Il pattern MVC e il framework Asp.net non fanno distinzione su cosa dovrebbe essere il Modello.

Gli esempi di MS includono classi di persistenza nel modello. La tua domanda sull'appartenenza al modello. Dipende. Le classi nel tuo modello sono di proprietà di qualcosa? Esiste un collegamento tra chi effettua il login e quali dati vengono visualizzati? Esiste un filtraggio dei dati parte di un sistema di autorizzazioni modificabile? Chi ha aggiornato o modificato per ultimo un oggetto fa parte del tuo dominio come in qualcun altro ha bisogno di vederlo o qualcosa per il supporto di backend?

Anche l'esempio di posta elettronica dipende. Hai familiarità con gli eventi di dominio o gli eventi in particolare? Hai un servizio separato per inviare e-mail? L'atto di inviare un'e-mail fa parte del tuo dominio o è un problema a livello di applicazione al di fuori dell'ambito del tuo sistema? L'interfaccia utente deve sapere se un'e-mail è stata inviata correttamente o meno? Le e-mail che non vengono inviate devono essere ritentate? Il contenuto dell'e-mail inviata deve essere archiviato per i requisiti di supporto o servizio clienti?

Questo tipo di domande sono eccessivamente ampie e soggettive, ma rispondo in modo che tu e tutti coloro che ti hanno votato possiate capirlo.

I tuoi requisiti / tempistiche / risorse si riversano nell'architettura del tuo sistema. Anche il modello di entrate può avere un effetto. Devi anche considerare lo schema per cui stai scattando. DDD è molto diverso dalle applicazioni di persistenza come modello e tutto lo slop in mezzo è valido anche per alcune app. Stai scattando per testare l'app? Tutto questo ha un effetto.

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.