sql >> Database >  >> NoSQL >> MongoDB

Ontwerppatronen voor gegevenstoegangslaag

Welnu, de gebruikelijke benadering van gegevensopslag in Java is, zoals u opmerkte, helemaal niet erg objectgericht. Dit is op zich niet slecht of goed:"objectgerichtheid" is geen voordeel of nadeel, het is slechts een van de vele paradigma's die soms helpen bij een goed architectuurontwerp (en soms niet).

De reden dat DAO's in Java meestal niet objectgeoriënteerd zijn, is precies wat u wilt bereiken - uw afhankelijkheid van de specifieke database verminderen. In een beter ontworpen taal, die meervoudige overerving mogelijk maakte, kan dit, of natuurlijk, heel elegant op een objectgeoriënteerde manier worden gedaan, maar met Java lijkt het gewoon meer moeite te zijn dan het waard is.

In bredere zin helpt de niet-OO-benadering uw gegevens op toepassingsniveau los te koppelen van de manier waarop deze zijn opgeslagen. Dit is meer dan (niet) afhankelijkheid van de bijzonderheden van een bepaalde database, maar ook de opslagschema's, wat vooral belangrijk is bij het gebruik van relationele databases (laat me niet beginnen over ORM):je kunt een goed ontworpen relationeel schema hebben naadloos vertaald in het applicatie OO-model door uw DAO.

Dus wat de meeste DAO's tegenwoordig in Java zijn, zijn in wezen wat je in het begin noemde - klassen vol statische methoden. Een verschil is dat, in plaats van alle methoden statisch te maken, het beter is om een ​​enkele statische "fabrieksmethode" te hebben (waarschijnlijk in een andere klasse), die een (enkele) instantie van uw DAO retourneert, die een bepaalde interface implementeert , gebruikt door applicatiecode om toegang te krijgen tot de database:

public interface GreatDAO {
    User getUser(int id);
    void saveUser(User u);
}
public class TheGreatestDAO implements GreatDAO {
   protected TheGeatestDAO(){}
   ... 
}
public class GreatDAOFactory {
     private static GreatDAO dao = null;
     protected static synchronized GreatDao setDAO(GreatDAO d) {
         GreatDAO old = dao;
         dao = d;
         return old;
     }
     public static synchronized GreatDAO getDAO() {
         return dao == null ? dao = new TheGreatestDAO() : dao;
     }
}

public class App {
     void setUserName(int id, String name) {
          GreatDAO dao =  GreatDAOFactory.getDao();
          User u = dao.getUser(id);
          u.setName(name);
          dao.saveUser(u);
     }
}

Waarom het op deze manier doen in tegenstelling tot statische methoden? Welnu, wat als u besluit over te stappen naar een andere database? Natuurlijk zou u een nieuwe DAO-klasse maken, waarbij u de logica voor uw nieuwe opslag implementeert. Als je statische methoden zou gebruiken, zou je nu al je code moeten doorlopen, toegang krijgen tot de DAO en deze moeten wijzigen om je nieuwe klasse te gebruiken, toch? Dit kan een enorme pijn zijn. En wat als u van gedachten verandert en terug wilt naar de oude db?

Met deze aanpak hoeft u alleen maar de GreatDAOFactory.getDAO() te wijzigen en laat het een instantie van een andere klasse maken, en al uw toepassingscode zal de nieuwe database gebruiken zonder enige wijzigingen.

In het echte leven wordt dit vaak gedaan zonder enige wijziging in de code:de fabrieksmethode haalt de naam van de implementatieklasse op via een eigenschapsinstelling en instantieert deze met behulp van reflectie, dus alles wat u hoeft te doen om van implementatie te wisselen, is een eigenschap bewerken het dossier. Er zijn eigenlijk frameworks - zoals spring of guice - die dit "afhankelijkheidsinjectie"-mechanisme voor u beheren, maar ik zal eerst niet in details treden, omdat het echt buiten het bestek van uw vraag valt, en ook omdat ik er niet per se van overtuigd ben dat het voordeel dat u krijgt van het gebruik van die frameworks zijn de moeite waard om ermee te integreren voor de meeste toepassingen.

Een ander (waarschijnlijk, meer kans om te profiteren van) voordeel van deze "fabrieksbenadering" in tegenstelling tot statisch is testbaarheid. Stel je voor dat je een eenheidstest schrijft, die de logica van je App moet testen klasse onafhankelijk van een onderliggende DAO. Je wilt om verschillende redenen geen echte onderliggende opslag gebruiken (snelheid, moeten instellen en opruimen achteraf, mogelijke botsingen met andere tests, mogelijkheid om testresultaten te vervuilen met problemen in DAO, niet gerelateerd aan App , die daadwerkelijk wordt getest, enz.).

Om dit te doen, wil je een testraamwerk, zoals Mockito , waarmee je de functionaliteit van elk object of elke methode kunt "spotten" door het te vervangen door een "dummy" -object, met vooraf gedefinieerd gedrag (ik zal de details overslaan, omdat dit opnieuw buiten het bereik valt). U kunt dus dit dummy-object maken dat uw DAO vervangt en de GreatDAOFactory maken retourneer je dummy in plaats van het echte werk door GreatDAOFactory.setDAO(dao) te bellen vóór de test (en daarna herstellen). Als u statische methoden zou gebruiken in plaats van de instantieklasse, zou dit niet mogelijk zijn.

Nog een voordeel, dat een beetje lijkt op het wisselen van databases die ik hierboven heb beschreven, is het "pimpen" van je dao met extra functionaliteit. Stel dat uw toepassing langzamer wordt naarmate de hoeveelheid gegevens in de database groeit, en u besluit dat u een cachelaag nodig heeft. Implementeer een wrapper-klasse, die de echte dao-instantie gebruikt (die eraan wordt geleverd als een constructorparameter) om toegang te krijgen tot de database, en de objecten die het leest in het geheugen opslaat, zodat ze sneller kunnen worden geretourneerd. U kunt dan uw GreatDAOFactory.getDAO . maken instantiëren van deze wrapper, zodat de toepassing ervan kan profiteren.

(Dit wordt "delegatiepatroon" genoemd ... en lijkt pijn in de kont, vooral als je veel methoden hebt gedefinieerd in je DAO:je zult ze allemaal in de wrapper moeten implementeren, zelfs om het gedrag van slechts één te veranderen Als alternatief kunt u uw dao eenvoudig subclasseren en er op deze manier caching aan toevoegen.Dit zou een stuk minder saaie codering vooraf zijn, maar kan problematisch worden als u besluit de database te wijzigen, of, erger nog, om een ​​optie te hebben van implementaties heen en weer schakelen.)

Een even algemeen gebruikt (maar naar mijn mening inferieur) alternatief voor de "fabrieks"-methode is het maken van de dao een lidvariabele in alle klassen die het nodig hebben:

public class App {
   GreatDao dao;
   public App(GreatDao d) { dao = d; }
}

Op deze manier moet de code die deze klassen instantieert, het dao-object instantiëren (kan nog steeds de fabriek gebruiken) en het als een constructorparameter leveren. De afhankelijkheidsinjectiekaders die ik hierboven noemde, doen meestal iets soortgelijks als dit.

Dit biedt alle voordelen van de "fabrieksmethode"-benadering, die ik eerder heb beschreven, maar, zoals ik al zei, naar mijn mening niet zo goed is. De nadelen hier zijn dat je voor elk van je app-klassen een constructor moet schrijven, exact hetzelfde steeds opnieuw moet doen, en ook niet in staat bent om de klassen gemakkelijk te instantiëren wanneer dat nodig is, en wat verloren leesbaarheid:met een code die groot genoeg is , een lezer van uw code, die er niet bekend mee is, zal moeite hebben om te begrijpen welke daadwerkelijke implementatie van de dao wordt gebruikt, hoe deze wordt geïnstantieerd, of het een singleton is, een thread-veilige implementatie, of deze de status bijhoudt, of caches wat dan ook, hoe de beslissingen over het kiezen van een bepaalde implementatie worden genomen enz.




  1. Hoe wijzig je de gebruikersrechten van MongoDB?

  2. Waarom werkt findRandom() mongoose voor de node.js-methode niet?

  3. Hoe gebruik je aggregatie voor MongoDB om te filteren op $ en/$or?

  4. Krijgt subdocumenten door geoNear - MongoDB