Ho provato a porre questa domanda prima su StakOverflow in un modo più concreto, ma dopo essere stato indicato qui ho capito che dovrei riformularlo in termini più generali; tuttavia, puoi comunque consultare la domanda originale , se voglio più dettagli concreti sul mio caso d'uso specifico.
Assumi un'interfaccia di reporting relativamente complessa. Per semplicità, supponiamo di presentare i dati risultanti solo in forma tabellare, ma stiamo visualizzando un aggregato che non è mappato su oggetti di dominio esistenti. L'utente può selezionare date di inizio / fine da un calendario, può limitare il proprio report in base a criteri specifici e può ordinare i risultati per colonne specifiche; tutti questi sono opzionali. La loro richiesta viene elaborata da diversi livelli di codice, ma dopo l'elaborazione ci ritroviamo con una singola query SQL che contiene condizioni di filtro, direttive di ordinamento e istruzioni JOIN per aggregare dati da diverse tabelle.
Sul database, sto usando un ORM (NHibernate) che gestisce la persistenza e mi costringe a implementare entità rigorose / oggetti di dominio; questo è chiaro e funziona già bene.
Dall'altra parte (verso l'utente), ho la vista che presenta cose all'utente e un controller che interpreta i dati in / out. Questo è anche chiaro, ed è gestito correttamente.
La mia domanda è questa: cosa dovrebbe accadere tra questi livelli? Qual è l'approccio raccomandato per un corretto incapsulamento? Il mio approccio intuitivo è stato quello di consentire alla mia business logic (BL) di chiamare vari helper di query che gradualmente costruivano varie parti dell'oggetto query, a seconda dell'input dell'utente. Questo ha iniziato a sollevare bandiere rosse quando ho finito per dover gestire i JOIN in modo condizionale nel BL, che ovviamente è fuori uso.
Sono d'accordo con la risposta di Frédéric alla mia domanda precedente su StackOverflow : sposta tutto il codice che costruisce la query più in profondità, più vicino al livello di persistenza e mantiene libero il BL da qualsiasi conoscenza sul modello. Questo è certamente un approccio più pulito, e potrei facilmente definire un Data Transfer Object (DTO) per portare i risultati del database "verso l'alto", dal livello di persistenza verso il controller e, infine, al livello di vista. Tuttavia, l'utente può inserire molti input opzionali; ciò significa che avrò bisogno anche di una classe ausiliaria che trasporta i dati al contrario, dal livello controller, attraverso il livello di servizio in cui vivono i BL, e fino in fondo agli helper della query stessi sul livello di persistenza. Non so come vengano chiamati, quindi chiamiamoli "QTO", per "Query Transfer Objects" ("query" nel senso di "query utente", non nel senso di SQL). Il livello di persistenza quindi interpreterà questi QTO in SQL, eseguirà l'SQL e convertirà il set di risultati in DTO che si ripristinano.
Il mio problema con questo approccio è che aggiungerei solo un paio di classi intermedie per il trasporto dei dati, ma la stessa gestione sarebbe la stessa (solo su un livello diverso). Avrei ancora bisogno di aggiungere tutti questi JOIN in modo condizionale, aggiungere criteri in modo condizionale, aggiungere condizionamenti di ordinamento in modo condizionale - solo ora dovrei fare lo sforzo extra di compilare i QTO in modo condizionale anche nel controller! E il futuro refactoring non sarebbe più semplice: qualsiasi cambiamento nella struttura del QTO interesserebbe sia il controller che i livelli del modello - ed è quasi garantito che eventuali modifiche rilevanti alla struttura del database porterebbero modifiche sia a QTO che a DTOs bene.
Nella tua esperienza, qual è un buon modo di progettarlo? Sono troppo ingegnerizzato? In che modo vengono trattati i miei problemi di complessità nel design che stai utilizzando / suggerendo? O ci sono dei benefici che non vedo che controbilanciano la complessità aggiunta?