sql >> Databáze >  >> NoSQL >> MongoDB

Návrhové vzory pro vrstvu přístupu k datům

Běžný přístup k ukládání dat v Javě není, jak jste si všimli, vůbec příliš objektově orientovaný. To samo o sobě není ani špatné, ani dobré:"objektová orientace" není ani výhoda, ani nevýhoda, je to jen jedno z mnoha paradigmat, které někdy pomáhá s dobrým návrhem architektury (a někdy ne).

Důvod, proč DAO v Javě nejsou obvykle objektově orientované, je přesně to, čeho chcete dosáhnout – uvolnění vaší závislosti na konkrétní databázi. V lépe navrženém jazyce, který umožňoval vícenásobnou dědičnost, lze toto, nebo samozřejmě, provést velmi elegantně objektově orientovaným způsobem, ale s javou se to zdá být větší potíže, než stojí za to.

V širším smyslu, non-OO přístup pomáhá oddělit vaše data na úrovni aplikace od způsobu, jakým jsou uložena. To je více než (ne) závislost na specifikách konkrétní databáze, ale také na schématech úložiště, což je zvláště důležité při používání relačních databází (nezačínejte s ORM):můžete mít dobře navržené relační schéma bezproblémově převedeno do aplikačního modelu OO vaším DAO.

Většina DAO je dnes v Javě v podstatě to, co jste zmínili na začátku - třídy plné statických metod. Jeden rozdíl je v tom, že místo aby byly všechny metody statické, je lepší mít jedinou statickou „tovární metodu“ (pravděpodobně v jiné třídě), která vrací (jedinou) instanci vašeho DAO, která implementuje konkrétní rozhraní. , používaný kódem aplikace pro přístup k databázi:

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);
     }
}

Proč to dělat tímto způsobem na rozdíl od statických metod? Co když se rozhodnete přejít na jinou databázi? Přirozeně byste vytvořili novou třídu DAO, která by implementovala logiku pro vaše nové úložiště. Pokud byste používali statické metody, museli byste nyní projít veškerý svůj kód, přistupovat k DAO a změnit jej tak, aby používal vaši novou třídu, že? To může být velká bolest. A co když pak změníte názor a budete chtít přejít zpět na starou db?

S tímto přístupem vše, co musíte udělat, je změnit GreatDAOFactory.getDAO() a vytvořit instanci jiné třídy a veškerý kód vaší aplikace bude používat novou databázi bez jakýchkoli změn.

V reálném životě se to často provádí bez jakýchkoli změn v kódu:tovární metoda získá název implementační třídy prostřednictvím nastavení vlastnosti a vytvoří jej pomocí reflexe, takže vše, co musíte udělat pro přepnutí implementací, je upravit vlastnost. soubor. Ve skutečnosti existují frameworky - jako spring nebo guice - které za vás spravují tento mechanismus „injekce závislosti“, ale nebudu zabíhat do podrobností, zaprvé proto, že to opravdu přesahuje rámec vaší otázky, a také proto, že nejsem nutně přesvědčen, že výhody, které získáte z používání tyto rámce stojí za problémy s integrací s nimi pro většinu aplikací.

Další (pravděpodobně pravděpodobněji využitelnou) výhodou tohoto „továrního přístupu“ na rozdíl od statického je testovatelnost. Představte si, že píšete unit test, který by měl otestovat logiku vaší App třídy nezávisle na jakémkoli podkladovém DAO. Nechcete, aby používal nějaké skutečné základní úložiště z několika důvodů (rychlost, nutnost jej nastavovat a čistit doslovy, možné kolize s jinými testy, možnost znečištění výsledků testů problémy v DAO, nesouvisející s App , která se ve skutečnosti testuje atd.).

Chcete-li to provést, potřebujete testovací rámec, jako je Mockito , který vám umožňuje „vysmívat se“ funkčnosti jakéhokoli objektu nebo metody a nahradit je „fiktivním“ objektem s předdefinovaným chováním (podrobnosti vynechám, protože to je opět mimo rámec). Můžete tedy vytvořit tento fiktivní objekt, který nahradí vaše DAO, a vytvořit GreatDAOFactory vraťte svou figurínu místo skutečné věci voláním GreatDAOFactory.setDAO(dao) před testem (a jeho obnovením po něm). Pokud byste místo třídy instance používali statické metody, nebylo by to možné.

Další výhodou, která je trochu podobná přepínání databází, které jsem popsal výše, je „vybavení“ vašeho dao dalšími funkcemi. Předpokládejme, že se vaše aplikace s rostoucím množstvím dat v databázi zpomaluje a vy se rozhodnete, že potřebujete vrstvu mezipaměti. Implementujte třídu wrapper, která používá skutečnou instanci dao (která je jí poskytnuta jako parametr konstruktoru) pro přístup k databázi a ukládá objekty, které čte, do paměti, takže je lze rychleji vrátit. Poté můžete vytvořit svůj GreatDAOFactory.getDAO vytvořit instanci tohoto obalu, aby ho aplikace mohla využít.

(Nazývá se to „vzor delegování“ ... a vypadá to jako bolest v zadku, zvláště když máte ve svém DAO definováno mnoho metod:budete je muset implementovat všechny do obalu, i když chcete změnit chování pouze jedné Alternativně můžete jednoduše podtřídu vašeho dao a přidat do něj ukládání do mezipaměti tímto způsobem. To by bylo mnohem méně nudné kódování předem, ale může to být problematické, když se rozhodnete změnit databázi, nebo v horším případě mít možnost přepínání implementací tam a zpět.)

Jednou stejně široce používanou (ale podle mého názoru podřadnou) alternativou k "tovární" metodě je vytvoření dao členská proměnná ve všech třídách, které ji potřebují:

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

Tímto způsobem musí kód, který vytváří instanci těchto tříd, vytvořit instanci objektu dao (stále by mohl používat továrnu) a poskytnout jej jako parametr konstruktoru. Rámce pro vkládání závislostí, které jsem zmínil výše, obvykle dělají něco podobného.

To poskytuje všechny výhody přístupu „tovární metody“, který jsem popsal dříve, ale jak jsem řekl, podle mého názoru není tak dobrý. Nevýhody zde spočívají v nutnosti napsat konstruktor pro každou z vašich tříd aplikací, dělat přesně to samé znovu a znovu a také není možné snadno vytvářet instance tříd, když je to potřeba, a do určité míry ztratila čitelnost:s dostatečně velkou kódovou základnou , čtenář vašeho kódu, který jej nezná, bude mít problém pochopit, která skutečná implementace dao se používá, jak se vytváří instance, zda se jedná o singleton, implementaci zabezpečenou proti vláknům, zda udržuje stav nebo mezipaměti cokoliv, jak se rozhodují o výběru konkrétní implementace atd.




  1. Nelze se připojit ke kontejneru MongoDB z jiného kontejneru Docker

  2. Je možné iterovat mongo kurzor dvakrát?

  3. Mongodb dotaz konkrétní měsíc|rok ne datum

  4. Agregační funkce duplikuje položky v ng-repeat při obnovení stránky. Je třeba zjistit, jak zastavit duplicitu. Mangusta Angularjs Mongodb