Come evitare completamente la logica di business in DAL?

2

Nella nostra applicazione ASP.NET MVC, proviamo davvero a separare le preoccupazioni da ogni livello (usiamo DDD). Tuttavia, sembra che non possiamo evitare di avere almeno qualche logica di business in DAL.

Per esempio, ci sono oggetti con nome, in cui i requisiti aziendali sono che ogni nome deve essere univoco (ma il nome non è una chiave). Se c'è una richiesta per creare un nuovo oggetto con nome, il servizio controlla se qualche oggetto con quel nome esiste nel repository, se non lo fa, fabbrica crea un nuovo oggetto e il repository lo mantiene (usiamo EF per i repository e le stored procedure per gli oggetti query ).

Il problema è con le condizioni di gara - se due richieste vengono contemporaneamente (è improbabile, ma possibile), entrambe le richieste vogliono creare un nuovo oggetto con lo stesso nome, in questo caso entrambi i controlli risultano negativi e due oggetti con lo stesso nome sarebbe stato creato Finora, lo gestiamo con vincoli nel database SQL, quindi se succede qualcosa del genere, un'eccezione viene gettata nel repository - ma questo approccio è semplicemente sbagliato, dal momento che tale requisito è strettamente richiesto dal business e non dovrebbe creare bolle al livello di persistenza; se cambiamo tale requisito, ad es. i nomi diventano maiuscole e minuscole, tale vincolo dovrebbe anche essere modificato, ecc.).

Come implementare correttamente tale scenario? Grazie.

    
posta Robert Goldwein 14.03.2014 - 20:35
fonte

3 risposte

6

BTW concorda con @Robert Harvey.

C'è un cambiamento sottile ma problematico nella tua domanda: la domanda iniziale è "come isolare la logica aziendale", ma poi si passa ad isolare "requisiti di aziendali" .

I requisiti aziendali permeeranno il tuo sistema. Di necessità. Durante lo sforzo di modellazione dei dati, ciò sarà guidato dai dati che l'azienda ha identificato e dai vincoli sui dati. Il tuo modello di dati rifletterà sempre i requisiti dell'azienda. Ad esempio, l'azienda potrebbe richiedere di acquisire i nomi Primo, Medio e Ultimo per i clienti. Quindi il tuo database conterrà quei campi e non c'è modo di isolarlo dai requisiti della tua attività.

Business logic , d'altra parte, è una bestia diversa. Cioè, la logica aziendale è un piccolo frammento di codice come il calcolo della tariffa mensile di un cliente. O un predicato, come se un cliente sia o meno idoneo per qualche promozione. Se il tuo DAL contiene i calcoli incorporati, hai superato una linea.

Fornire un meccanismo come l'unicità o la concorrenza sta sicuramente arrivando da un requisito aziendale, ma non è una logica aziendale. L'azienda non si cura di come fornisci simultaneità e unicità, purché tu lo faccia. Potresti dire che supporta la logica di business, ma non è specificato dall'azienda.

D'altro canto, un calcolo della percentuale di contribuzione annuale dell'utente, o qualsiasi altra cosa, è un codice che l'azienda specifica direttamente , ed è qualcosa che si desidera isolare per motivi di test, convalida, flessibilità , ecc.

    
risposta data 14.03.2014 - 22:31
fonte
6

So far, we handle that with constrains in SQL database, so if something like this happens, an exception is thrown in repository - but this approach is simply wrong, since such requirement is strictly business requirement and it shouldn't bubble to persistence layer.

Puoi creare quell'argomento se vuoi, ma il tuo database relazionale ha già i meccanismi di indicizzazione e di concorrenza necessari per renderlo molto conveniente e affidabile.

Se l'idea di rilevare duplicati in un database ti mette a disagio, pensa all'unicità come attributo dei dati. Applicare un tale attributo è una cosa perfettamente valida da fare in un database. Non sosteresti che il tipo di un campo dati (vale a dire testo, float, valuta) è una regola aziendale, vero?

    
risposta data 14.03.2014 - 20:38
fonte
0

Lo farei in questo modo:

public class NamedObjectRepostiory
{
    private IDataStore _dataStore; // this is just a wrapper around Entity Framework to make it more testable

    public NamedObjectRepostiory(IDataStore dataStore)
    {
        _dataStore = dataStore;
    }

    public void Save(NamedObject namedObject)
    {
        // This is basically a transaction scope/DbContext instance.  You can inject the IDataSession
        // rather than the IDataStore if you want scope to be managed by your DI
        // container, e.g. session per HTTP request, which is a common pattern
        using (var session = _dataStore.GetSession()) {
            if (session.Set<NamedObject>().Any(x => x.Name == namedObject.Name))
                throw new Exception("Cannot have 2 NamedObjects with the same name.");

            // save data...
        }
    }
}

Non è necessario alcun vincolo univoco (presupponendo che tutte le scritture passino attraverso l'applicazione, che hanno praticamente bisogno se stai facendo DDD).

    
risposta data 15.03.2014 - 19:36
fonte

Leggi altre domande sui tag