MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Wzorce projektowe dla warstwy dostępu do danych

Cóż, powszechne podejście do przechowywania danych w javie jest, jak zauważyłeś, wcale nie bardzo obiektowe. To samo w sobie nie jest ani złe, ani dobre:​​„obiektowość” nie jest ani zaletą, ani wadą, to tylko jeden z wielu paradygmatów, które czasami pomagają w dobrym projektowaniu architektury (a czasami nie).

Powodem, dla którego DAO w Javie nie są zazwyczaj zorientowane obiektowo, jest dokładnie to, co chcesz osiągnąć — rozluźnienie zależności od specyfiki bazy danych. W lepiej zaprojektowanym języku, który pozwalał na wielokrotne dziedziczenie, można to zrobić bardzo elegancko w sposób obiektowy, ale w przypadku javy wydaje się to po prostu sprawiać więcej kłopotów niż jest to warte.

W szerszym sensie podejście nie-OO pomaga oddzielić dane na poziomie aplikacji od sposobu ich przechowywania. To więcej niż (nie) zależność od specyfiki konkretnej bazy danych, ale także od schematów przechowywania, co jest szczególnie ważne przy korzystaniu z relacyjnych baz danych (nie zaczynaj od ORM):możesz mieć dobrze zaprojektowany schemat relacyjny bezproblemowo przetłumaczone na model OO aplikacji przez Twoje DAO.

Tak więc to, czym większość DAO jest obecnie w javie, jest zasadniczo tym, o czym wspomniałeś na początku - klasami, pełnymi statycznych metod. Jedna różnica polega na tym, że zamiast czynić wszystkie metody statycznymi, lepiej jest mieć jedną statyczną „metodę fabryczną” (prawdopodobnie w innej klasie), która zwraca (pojedynczą) instancję twojego DAO, która implementuje określony interfejs , używany przez kod aplikacji w celu uzyskania dostępu do bazy danych:

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

Dlaczego robi się to w ten sposób, w przeciwieństwie do metod statycznych? A co, jeśli zdecydujesz się przełączyć na inną bazę danych? Oczywiście utworzysz nową klasę DAO, implementując logikę dla nowego magazynu. Gdybyś używał metod statycznych, musiałbyś teraz przejrzeć cały kod, uzyskać dostęp do DAO i zmienić go tak, aby używał nowej klasy, prawda? To może być ogromny ból. A co, jeśli zmienisz zdanie i zechcesz wrócić do starej bazy danych?

Przy takim podejściu wszystko, co musisz zrobić, to zmienić GreatDAOFactory.getDAO() i sprawić, by utworzyła instancję innej klasy, a cały kod aplikacji będzie korzystał z nowej bazy danych bez żadnych zmian.

W prawdziwym życiu często odbywa się to bez żadnych zmian w kodzie:metoda fabryczna pobiera nazwę klasy implementacji za pomocą ustawienia właściwości i tworzy jej instancję za pomocą odbicia, więc wszystko, co musisz zrobić, aby przełączyć implementacje, to edytować właściwość plik. W rzeczywistości istnieją frameworki - takie jak spring lub poradnik - które zarządzają tym mechanizmem "dependency injection" za Ciebie, ale nie będę się wdawał w szczegóły, po pierwsze dlatego, że to naprawdę wykracza poza zakres Twojego pytania, a także dlatego, że niekoniecznie jestem przekonany, że korzyści płyną z używania te frameworki są warte zachodu z integracją z nimi dla większości aplikacji.

Inną (prawdopodobnie bardziej prawdopodobną do wykorzystania) zaletą tego „podejścia fabrycznego” w przeciwieństwie do statycznego jest testowalność. Wyobraź sobie, że piszesz test jednostkowy, który powinien przetestować logikę Twojej aplikacji klasy niezależnie od bazowego DAO. Nie chcesz, aby używał żadnej rzeczywistej pamięci masowej z kilku powodów (szybkość, konieczność konfiguracji i czyszczenia posłów, możliwe kolizje z innymi testami, możliwość zanieczyszczenia wyników testów problemami w DAO, niezwiązanych z Aplikacja , który jest aktualnie testowany itp.).

Aby to zrobić, potrzebujesz frameworka testowego, takiego jak Mockito , który pozwala na "wyśmiewanie" funkcjonalności dowolnego obiektu lub metody, zastępując go "fikcyjnym" obiektem o predefiniowanym zachowaniu (pominę szczegóły, ponieważ to znowu wykracza poza zakres). Możesz więc stworzyć ten fikcyjny obiekt, który zastąpi twoje DAO i stworzyć GreatDAOFactory zwróć swój manekin zamiast prawdziwego, wywołując GreatDAOFactory.setDAO(dao) przed testem (i przywrócenie go po). Gdybyś używał metod statycznych zamiast klasy instancji, nie byłoby to możliwe.

Jeszcze jedną korzyścią, która jest podobna do przełączania baz danych, którą opisałem powyżej, jest "podrasowanie" twojego dao dodatkową funkcjonalnością. Załóżmy, że aplikacja działa wolniej wraz ze wzrostem ilości danych w bazie danych i decydujesz, że potrzebujesz warstwy pamięci podręcznej. Zaimplementuj klasę opakowującą, która używa rzeczywistej instancji dao (dostarczonej jako parametr konstruktora) w celu uzyskania dostępu do bazy danych i buforuje odczytywane obiekty w pamięci, aby można je było szybciej zwrócić. Następnie możesz utworzyć GreatDAOFactory.getDAO utworzyć wystąpienie tego opakowania, aby aplikacja mogła z niego skorzystać.

(Nazywa się to „wzorem delegowania” ... i wydaje się być uciążliwe, zwłaszcza gdy masz wiele metod zdefiniowanych w swoim DAO:będziesz musiał zaimplementować je wszystkie w opakowaniu, nawet aby zmienić zachowanie tylko jednego Ewentualnie możesz po prostu podklasy swojego dao i dodać do niego buforowanie w ten sposób.Byłoby to znacznie mniej nudne kodowanie z góry, ale może stać się problematyczne, gdy zdecydujesz się zmienić bazę danych lub, co gorsza, mieć opcję przełączanie implementacji tam iz powrotem).

Jedną równie szeroko stosowaną (ale moim zdaniem gorszą) alternatywą dla metody „fabrycznej” jest wykonanie dao zmienna składowa we wszystkich klasach, które tego potrzebują:

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

W ten sposób kod, który tworzy instancję tych klas, musi utworzyć instancję obiektu dao (nadal może używać fabryki) i podać go jako parametr konstruktora. Frameworki do wstrzykiwania zależności, o których wspomniałem powyżej, zwykle robią coś podobnego do tego.

Zapewnia to wszystkie zalety podejścia „metoda fabryczna”, które opisałem wcześniej, ale jak powiedziałem, moim zdaniem nie jest tak dobre. Wady tutaj to konieczność napisania konstruktora dla każdej klasy aplikacji, wykonującego w kółko dokładnie to samo, a także brak możliwości łatwego tworzenia instancji klas w razie potrzeby oraz utrata czytelności:z wystarczająco dużą bazą kodu , czytelnik Twojego kodu, który go nie zna, będzie miał trudności ze zrozumieniem, jaka rzeczywista implementacja dao jest używana, jak jest tworzona, czy jest to implementacja singletona, bezpieczna wątkowo, czy utrzymuje stan, czy pamięć podręczna cokolwiek, w jaki sposób podejmowane są decyzje o wyborze konkretnej implementacji itp.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Jak używać updateOption z arrayFilters w spring-data mongodb?

  2. Unikalny atrybut SailsJS i Mongo jest ignorowany

  3. Pobierz dane z mongodb za pomocą sterownika C#

  4. Pobieranie com.mongodb.MongoException$DuplicateKey w mongodb z javą za pomocą upsert

  5. Jak wdrożyć MongoDB w celu zapewnienia wysokiej dostępności