Domanda

Supponiamo che tu abbia un'applicazione divisa in 3 livelli:GUI, logica aziendale e accesso ai dati.Nel livello della logica aziendale hai descritto i tuoi oggetti aziendali:getter, setter, accessori e così via...hai capito.L'interfaccia al livello della logica aziendale garantisce un utilizzo sicuro della logica aziendale, quindi tutti i metodi e gli accessori chiamati convalideranno l'input.

È fantastico quando scrivi per la prima volta il codice dell'interfaccia utente, perché hai un'interfaccia ben definita di cui ti puoi fidare.

Ma qui arriva la parte difficile: quando inizi a scrivere il livello di accesso ai dati, l'interfaccia per la logica aziendale non soddisfa le tue esigenze.È necessario disporre di più funzioni di accesso e getter per impostare i campi che sono/erano nascosti.Ora sei costretto a erodere l'interfaccia della tua logica aziendale;ora è possibile impostare i campi dal livello UI, che il livello UI non ha impostazioni aziendali.

A causa delle modifiche necessarie per il livello di accesso ai dati, l'interfaccia con la logica aziendale si è erosa al punto in cui è possibile impostare la logica aziendale anche con dati non validi.Pertanto, l'interfaccia non garantisce più un utilizzo sicuro.

Spero di aver spiegato il problema in modo abbastanza chiaro.Come si può prevenire l'erosione dell'interfaccia, mantenere l'occultamento e l'incapsulamento delle informazioni e allo stesso tempo soddisfare le diverse esigenze di interfaccia tra i diversi livelli?

È stato utile?

Soluzione

Se ho capito bene la domanda, hai creato un modello di dominio e vorresti scrivere un mappatore relazionale a oggetti per mappare tra i record nel tuo database e gli oggetti del tuo dominio.Tuttavia, sei preoccupato di inquinare il tuo modello di dominio con il codice "idraulico" che sarebbe necessario per leggere e scrivere nei campi del tuo oggetto.

Facendo un passo indietro, hai essenzialmente due scelte su dove inserire il codice di mappatura dei dati: all'interno della classe di dominio stessa o in una classe di mappatura esterna.La prima opzione è spesso chiamata modello Active Record e presenta il vantaggio che ciascun oggetto sa come persistere e ha accesso sufficiente alla propria struttura interna per consentirgli di eseguire la mappatura senza dover esporre campi non correlati all'azienda.

Per esempio

public class User
{
    private string name;
    private AccountStatus status;

    private User()
    {
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public AccountStatus Status
    {
        get { return status; }
    }

    public void Activate()
    {
        status = AccountStatus.Active;
    }

    public void Suspend()
    {
        status = AccountStatus.Suspended;
    }

    public static User GetById(int id)
    {
        User fetchedUser = new User();

        // Lots of database and error-checking code
        // omitted for clarity
        // ...

        fetchedUser.name = (string) reader["Name"];
        fetchedUser.status = (int)reader["statusCode"] == 0 ? AccountStatus.Suspended : AccountStatus.Active;

        return fetchedUser;
    }

    public static void Save(User user)
    {
        // Code to save User's internal structure to database
        // ...
    }
}

In questo esempio, abbiamo un oggetto che rappresenta un Utente con un Nome e un AccountStatus.Non vogliamo consentire l'impostazione diretta dello Status, forse perché vogliamo verificare che la modifica sia una transizione di stato valida, quindi non abbiamo un setter.Fortunatamente, il codice di mappatura nei metodi statici GetById e Save ha pieno accesso ai campi del nome e dello stato dell'oggetto.

La seconda opzione è avere una seconda classe responsabile della mappatura.Ciò ha il vantaggio di separare le diverse preoccupazioni relative alla logica aziendale e alla persistenza, il che può consentire al tuo progetto di essere più testabile e flessibile.La sfida con questo metodo è come esporre i campi nome e stato alla classe esterna.Alcune opzioni sono:1.Usa la riflessione (che non ha scrupoli sullo scavare in profondità nelle parti private del tuo oggetto) 2.Fornire setter pubblici con nomi specifici (ad es.Prefissali con la parola "privato") e spera che nessuno li usi accidentalmente 3.Se la tua lingua lo supporta, rendi i setter interni ma concedi l'accesso al modulo del mapping dei dati.Per esempio.utilizzare InternalsVisibleToAttribute in .NET 2.0 e versioni successive o le funzioni friend in C++

Per ulteriori informazioni, consiglierei il classico libro di Martin Fowler "Patterns of Enterprise Architecture"

Tuttavia, come avvertimento, prima di intraprendere il percorso di scrittura dei propri mapper, consiglio vivamente di utilizzare uno strumento ORM (Object Relational Mapper) di terze parti come nHibernate o Entity Framework di Microsoft.Ho lavorato su quattro diversi progetti in cui, per vari motivi, abbiamo scritto il nostro mapper ed è molto facile perdere molto tempo mantenendo ed estendendo il mapper invece di scrivere codice che fornisca valore all'utente finale.Finora ho utilizzato nHibernate su un progetto e, sebbene inizialmente la curva di apprendimento sia piuttosto ripida, l'investimento effettuato all'inizio viene ripagato considerevolmente.

Altri suggerimenti

Questo è un problema classico: separare il modello di dominio dal modello di database.Esistono diversi modi per attaccarlo, secondo me dipende davvero dalle dimensioni del tuo progetto.Potresti utilizzare il modello di repository come altri hanno detto.Se utilizzi .net o Java potresti utilizzare Hibernate O Ibernazione.

Quello che faccio è usare Sviluppo guidato dai test quindi scrivo prima i livelli dell'interfaccia utente e del modello e il livello dati viene deriso, quindi l'interfaccia utente e il modello sono costruiti attorno a oggetti specifici del dominio, quindi in seguito mappo questi oggetti a qualsiasi tecnologia sto utilizzando il livello dati.È una pessima idea lasciare che sia il database a determinare il design della tua app, scrivere prima l'app e pensare ai dati in seguito.

ps il titolo della domanda è un po' fuorviante

@Ghiaccio^^Calore:

Cosa intendi con che il livello dati non dovrebbe essere a conoscenza del livello della logica aziendale?Come riempiresti un oggetto aziendale con i dati?

L'interfaccia utente richiede un servizio alla ServiceClass nel livello aziendale, ovvero ottenere un elenco di oggetti filtrati da un oggetto con i dati dei parametri necessari.
Quindi ServiceClass crea un'istanza di una delle classi del repository nel livello dati e chiama GetList (filtri ParameterType).
Quindi il livello dati accede al database, estrae i dati e li mappa nel formato comune definito nell'assembly "dominio".
Il BL non ha più nulla da fare con questi dati, quindi li invia all'interfaccia utente.

Quindi l'interfaccia utente desidera modificare l'elemento X.Invia l'elemento (o oggetto aziendale) al servizio nel livello aziendale.Il livello aziendale convalida l'oggetto e, se è corretto, lo invia al livello dati per l'archiviazione.

L'interfaccia utente conosce il servizio nel livello aziendale che a sua volta conosce il livello dati.

L'interfaccia utente è responsabile della mappatura dell'input dei dati degli utenti da e verso gli oggetti e il livello dati è responsabile della mappatura dei dati nel database da e verso gli oggetti.Il livello Business rimane puramente aziendale.:)

Potrebbe essere una soluzione, poiché non corroderebbe l'interfaccia.Immagino che potresti tenere una lezione come questa:

public class BusinessObjectRecord : BusinessObject
{
}

Creo sempre un assembly separato che contiene:

  • Molte piccole interfacce (pensa a ICreateRepository, IReadRepository, IReadListRepsitory..l'elenco potrebbe continuare e la maggior parte di essi fa molto affidamento sui farmaci generici)
  • Molte interfacce concrete, come un IPersonRepository, che eredita da IReadRepository, hai capito il punto..
    Tutto ciò che non puoi descrivere solo con le interfacce più piccole, lo inserisci nell'interfaccia concreta.
    Finché utilizzi IPersonRepository per dichiarare il tuo oggetto, ottieni un'interfaccia pulita e coerente con cui lavorare.Ma la cosa interessante è che puoi anche creare una classe che richieda f.x.a ICreateRepository nel suo costruttore, quindi il codice finirà per essere molto facile da usare per fare cose davvero stravaganti.Qui sono presenti anche le interfacce per i Servizi nel livello business.
  • Alla fine inserisco tutti gli oggetti del dominio nell'assembly extra, solo per rendere la base di codice stessa un po' più pulita e accoppiata in modo più flessibile.Questi oggetti non hanno alcuna logica, sono solo un modo comune per descrivere i dati per tutti e 3+ i livelli.

A proposito.Perché definiresti metodi nel livello della logica aziendale per accogliere il livello dati?
Il livello dati non dovrebbe avere nemmeno motivo di sapere che esiste un livello aziendale.

Cosa intendi con che il livello dati non dovrebbe essere a conoscenza del livello della logica aziendale?Come riempiresti un oggetto aziendale con i dati?

Faccio spesso così:

namespace Data
{
    public class BusinessObjectDataManager
    {
         public void SaveObject(BusinessObject object)
         {
                // Exec stored procedure
         {
    }
}

Quindi il problema è che il livello aziendale deve esporre più funzionalità al livello dati e aggiungere questa funzionalità significa esporre troppo al livello UI?Se ho capito bene il tuo problema, sembra che tu stia cercando di soddisfare troppe esigenze con una singola interfaccia, e questo la sta solo facendo diventare disordinata.Perché non avere due interfacce nel livello aziendale?Una potrebbe essere un'interfaccia semplice e sicura per il livello dell'interfaccia utente.L'altro sarebbe un'interfaccia di livello inferiore per il livello dati.

È possibile applicare questo approccio a due interfacce a qualsiasi oggetto che deve essere passato sia all'interfaccia utente che ai livelli dati.

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

Potresti voler dividere le tue interfacce in due tipi, vale a dire:

  • Visualizza interfacce: ovvero interfacce che specificano le tue interazioni con l'interfaccia utente e
  • Interfacce dati: interfacce che ti consentiranno di specificare le interazioni con i tuoi dati

È possibile ereditare e implementare entrambi i set di interfacce in modo tale che:

public class BusinessObject : IView, IData

In questo modo, nel livello dati devi vedere solo l'implementazione dell'interfaccia di IData, mentre nell'interfaccia utente devi vedere solo l'implementazione dell'interfaccia di IView.

Un'altra strategia che potresti voler utilizzare è comporre i tuoi oggetti nell'interfaccia utente o nei livelli dati in modo tale che vengano semplicemente consumati da questi livelli, ad esempio,

public class BusinessObject : DomainObject

public class ViewManager<T> where T : DomainObject

public class DataManager<T> where T : DomainObject

Ciò a sua volta consente al tuo oggetto aziendale di rimanere all'oscuro sia del livello UI/Visualizzazione che del livello dati.

Continuerò la mia abitudine di andare controcorrente e dirò che dovresti chiederti perché stai costruendo tutti questi livelli di oggetti orribilmente complessi.

Penso che molti sviluppatori considerino il database come un semplice livello di persistenza per i loro oggetti e si preoccupino solo delle operazioni CRUD di cui tali oggetti hanno bisogno.Si stanno facendo troppi sforzi per "discordare l'impedenza" tra modelli oggettuali e modelli relazionali.Ecco un'idea:smettere di provare.

Scrivi procedure memorizzate per incapsulare i tuoi dati.Utilizzare set di risultati, DataSet, DataTable, SqlCommand (o Java/php/qualsiasi equivalente) secondo necessità dal codice per interagire con il database.Non hai bisogno di quegli oggetti.Un ottimo esempio è l'incorporamento di un SqlDataSource in una pagina .ASPX.

Non dovresti provare a nascondere i tuoi dati a nessuno.Gli sviluppatori devono comprendere esattamente come e quando interagiscono con l'archivio dati fisico.

I mappatori relazionali a oggetti sono il diavolo.Smetti di usarli.

La creazione di applicazioni aziendali è spesso un esercizio di gestione della complessità.Devi mantenere le cose il più semplici possibile, altrimenti avrai un sistema assolutamente non manutenibile.Se sei disposto a consentire un certo accoppiamento (che è comunque inerente a qualsiasi applicazione), puoi eliminare sia il livello della logica aziendale che il livello di accesso ai dati (sostituendoli con procedure memorizzate) e non avrai bisogno di nessuna di queste interfacce.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top