MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Шаблони за проектиране за слой за достъп до данни

Е, общият подход за съхранение на данни в java, както отбелязахте, изобщо не е много обектно-ориентиран. Това само по себе си не е нито лошо, нито добро:"обектно-ориентираността" не е нито предимство, нито недостатък, това е просто една от многото парадигми, които понякога помагат с добрия архитектурен дизайн (а понякога не).

Причината, поради която DAO в java обикновено не са обектно-ориентирани, е точно това, което искате да постигнете - облекчаване на зависимостта ви от специфичната база данни. В по-добре проектиран език, който позволява множествено наследяване, това, или разбира се, може да се направи много елегантно по обектно-ориентиран начин, но с java просто изглежда, че създава повече проблеми, отколкото си струва.

В по-широк смисъл подходът, който не е OO, помага за отделяне на вашите данни на ниво приложение от начина, по който се съхраняват. Това е нещо повече от (не)зависимост от спецификата на конкретна база данни, но също и от схемите за съхранение, което е особено важно при използване на релационни бази данни (не ме карайте да започвам с ORM):можете да имате добре проектирана релационна схема безпроблемно преведени в OO модела на приложението от вашия DAO.

И така, това, което повечето DAO са в Java в днешно време, е по същество това, което споменахте в началото - класове, пълни със статични методи. Една разлика е, че вместо да правите всички методи статични, по-добре е да имате един статичен „фабричен метод“ (вероятно в различен клас), който връща (единичен) екземпляр на вашия DAO, който имплементира определен интерфейс , използвани от кода на приложението за достъп до базата данни:

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

Защо да го правите по този начин, за разлика от статичните методи? Е, какво ще стане, ако решите да преминете към друга база данни? Естествено, ще създадете нов DAO клас, прилагайки логиката за вашето ново хранилище. Ако сте използвали статични методи, сега ще трябва да преминете през целия си код, да получите достъп до DAO и да го промените, за да използвате новия си клас, нали? Това може да бъде огромна болка. И какво, ако след това промените решението си и искате да се върнете към старата база данни?

С този подход всичко, което трябва да направите, е да промените GreatDAOFactory.getDAO() и го накарайте да създаде екземпляр от различен клас и целият ви код на приложение ще използва новата база данни без никакви промени.

В реалния живот това често се прави без никакви промени в кода:фабричният метод получава името на класа на изпълнение чрез настройка на свойство и го инстанцира чрез отражение, така че всичко, което трябва да направите, за да превключите реализациите, е да редактирате свойство файл. Всъщност има рамки - като spring или guice - които управляват този механизъм за „инжектиране на зависимост“ вместо вас, но няма да навлизам в подробности, първо, защото наистина е извън обхвата на вашия въпрос, а също така, защото не съм непременно убеден, че ползата, която получавате от използването тези рамки си заслужават труда да се интегрират с тях за повечето приложения.

Друго (вероятно от което е по-вероятно да се възползва) предимство на този "фабричен подход" за разлика от статичния е възможността за тестване. Представете си, че пишете модулен тест, който трябва да тества логиката на вашето App клас независимо от който и да е основен DAO. Не искате да използва никакво реално основно хранилище по няколко причини (скорост, необходимост от настройване и почистване на последващите думи, възможни сблъсъци с други тестове, възможност за замърсяване на резултатите от теста с проблеми в DAO, несвързани с App , който всъщност се тества и т.н.).

За да направите това, искате тестова рамка, като Mockito , който ви позволява да "изсмивате" функционалността на всеки обект или метод, като го замените с "фиктивен" обект с предварително дефинирано поведение (ще пропусна подробностите, защото това отново е извън обхвата). Така че можете да създадете този фиктивен обект, който да замени вашия DAO, и да направите GreatDAOFactory върнете вашия манекен вместо истинското нещо, като извикате GreatDAOFactory.setDAO(dao) преди теста (и възстановяването му след това). Ако използвате статични методи вместо класа на екземпляра, това не би било възможно.

Още едно предимство, което е донякъде подобно на превключването на бази данни, което описах по-горе, е „сводничеството“ на вашето dao с допълнителна функционалност. Да предположим, че вашето приложение става по-бавно с нарастването на количеството данни в базата данни и вие решавате, че имате нужда от кеш слой. Приложете обвиващ клас, който използва реалния екземпляр на dao (предоставен му като параметър на конструктора) за достъп до базата данни и кешира обектите, които чете в паметта, така че да могат да бъдат върнати по-бързо. След това можете да направите своя GreatDAOFactory.getDAO инстанцирайте тази обвивка, за да може приложението да се възползва от нея.

(Това се нарича „модел на делегиране“ ... и изглежда като болка в дупето, особено когато имате много методи, дефинирани във вашия DAO:ще трябва да имплементирате всички от тях в обвивката, дори за да промените поведението само на един .Алтернативно можете просто да подкласирате вашето dao и да добавите кеширане към него по този начин.Това би било много по-малко скучно кодиране предварително, но може да стане проблематично, когато решите да промените базата данни или, още по-лошо, да имате опция за превключване на реализации напред и назад.)

Една също толкова широко използвана (но, по мое мнение, по-ниска) алтернатива на метода "фабрика" е правенето на dao членска променлива във всички класове, които се нуждаят от нея:

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

По този начин кодът, който инстанцира тези класове, трябва да инстанцира dao обекта (все още може да използва фабриката) и да го предостави като параметър на конструктора. Рамките за инжектиране на зависимости, които споменах по-горе, обикновено правят нещо подобно на това.

Това предоставя всички предимства на подхода на "фабричния метод", който описах по-рано, но, както казах, не е толкова добър според мен. Недостатъците тук са, че трябва да напишете конструктор за всеки от вашите класове на приложения, като правите едно и също точно нещо отново и отново, а също така не можете лесно да инстанцирате класовете, когато е необходимо, и известна загуба на четливост:с достатъчно голяма кодова база , читателят на вашия код, който не е запознат с него, ще има трудности да разбере коя реална реализация на dao се използва, как се инстанцира, дали е сингълтон, безопасна за нишки реализация, дали запазва състояние или кешира нещо, как се вземат решенията за избор на конкретна реализация и т.н.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. функцията mongoose findOne връща undefined

  2. намери по _id с Mongoose

  3. django.core.exceptions.ImproperlyConfigured:'django_mongodb_engine' не е наличен бекенд на база данни

  4. Свойството с изтичане на срока на действие на Mongoose не работи правилно

  5. Mongodb как да вмъкна САМО ако не съществува (няма актуализация, ако съществува)?