Tworzenie warstwy abstrakcji na warstwie ORM

12

Uważam, że jeśli masz repozytoria, użyj ORM, który jest już wystarczająco abstrakcyjny z bazy danych.

Jednak tam, gdzie teraz pracuję, ktoś uważa, że ​​powinniśmy mieć warstwę, która wyodrębnia ORM na wypadek, gdybyśmy chcieli później zmienić ORM.

Czy to naprawdę konieczne, czy po prostu dużo pracy nad stworzeniem warstwy, która będzie działać na wielu ORM?

Edytować

Aby podać więcej szczegółów:

  1. Mamy klasy POCO i Entity Class, które są mapowane za pomocą AutoMapper. Klasa encji jest używana przez warstwę repozytorium. Warstwa repozytorium wykorzystuje następnie dodatkową warstwę abstrakcji do komunikacji z Entity Framework.
  2. Warstwa biznesowa w żaden sposób nie ma bezpośredniego dostępu do Entity Framework. Nawet bez dodatkowej warstwy abstrakcji nad ORM, ta musi korzystać z warstwy usługi, która korzysta z warstwy repozytorium. W obu przypadkach warstwa biznesowa jest całkowicie oddzielona od ORM.
  3. Głównym argumentem jest możliwość zmiany ORM w przyszłości. Ponieważ jest on naprawdę zlokalizowany wewnątrz warstwy repozytorium, dla mnie jest już dobrze oddzielony i nie rozumiem, dlaczego potrzebna jest dodatkowa warstwa abstrakcji, aby mieć kod „jakości”.
Patrick Desjardins
źródło
3
dodatkowa warstwa wymaga uzasadnienia, w przeciwnym razie narusza YAGNI. Innymi słowy, ktoś wierzący , że go potrzebujesz, ma obowiązek udowodnić, że
komara
2
Rozumiem, że potrzebuję warstwy domeny, która ujawnia tylko pożądany podzbiór operacji - ORM-y wydają się być nieco zbyt szerokim obszarem powierzchni (powiedzmy, że nie chcesz zezwalać na aktualizacje encji nie kierowanej przez inną encję zawierającą). Posiadanie takiej warstwy abstrakcji pomaga w tym.
Oded,
4
Prawdopodobnie będziesz potrzebować drugiej warstwy abstrakcji dla pierwszej warstwy abstrakcji nad ORM na wypadek, gdybyś chciał również zmienić pierwszą warstwę.
David Peterman
1
@David Gdy dodajemy redudancję, zmień wszystkie swoje if (boolean) na if (boolean == true) i jeśli chcesz cofnąć więcej tego samego, if (boolean == true == true ...) i tak dalej
brian
1
Ciekawy powiązany post: ayende.com/blog/3955/repository-is-the-new-singleton
RMalke

Odpowiedzi:

12

W ten sposób leży szaleństwo. Jest bardzo mało prawdopodobne, że kiedykolwiek będziesz musiał zmienić ORM. A jeśli kiedykolwiek zdecydujesz się zmienić ORM, koszt przepisywania mapowań będzie niewielkim ułamkiem kosztów opracowania i utrzymania własnej meta-ORM. Spodziewałbym się, że możesz napisać kilka skryptów, aby wykonać 95% pracy potrzebnej do zmiany ORM.

Wewnętrzne ramy prawie zawsze są katastrofą. Budowanie w oczekiwaniu na przyszłe potrzeby jest niemal gwarantowaną katastrofą. Udane ramy są pobierane z udanych projektów, a nie budowane z wyprzedzeniem w celu zaspokojenia wyimaginowanych potrzeb.

Kevin Cline
źródło
12

ORM zapewnia abstrakcję, aby warstwa danych była niezależna od RDBMS, ale może nie wystarczyć, aby „oddzielić” warstwę biznesową od warstwy danych. W szczególności nie należy zezwalać obiektom mapowanym na tabele RDBMS na „wyciek” bezpośrednio do warstwy biznesowej.

Warstwa biznesowa musi co najmniej zaprogramować interfejsy, które potencjalnie mogą zarządzać obiekty mapowane w tabeli ORM z warstwy danych. Ponadto może być konieczne utworzenie opartej na interfejsie warstwy abstrakcyjnego budynku zapytania, aby ukryć natywne możliwości zapytań w ORM. Głównym celem jest uniknięcie „wypalenia” dowolnego ORM do twojego rozwiązania poza jego warstwą danych. Na przykład kuszące może być tworzenie ciągów HQL ( Hibernate Query Language ) w warstwie biznesowej. Jednak ta z pozoru niewinna decyzja wiązałaby warstwę biznesową z Hibernacją, łącząc w ten sposób warstwy biznesowe i warstwy dostępu do danych; powinieneś starać się unikać tej sytuacji w jak największym stopniu.

EDYCJA: W twoim przypadku dodatkowa warstwa w repozytorium jest stratą czasu: w oparciu o punkt drugi, twoja warstwa biznesowa jest wystarczająco izolowana od repozytorium. Zapewnienie dodatkowej izolacji wprowadziłoby niepotrzebną złożoność, bez dodatkowych korzyści.

Problem z budowaniem dodatkowej warstwy abstrakcji w repozytorium polega na tym, że konkretna „marka” ORM dyktuje sposób interakcji z nią. Jeśli zbudujesz cienkie opakowanie, które wygląda jak Twoja ORM, ale jest pod twoją kontrolą, zastąpienie leżącej poniżej ORM będzie mniej więcej tak trudne, jak byłoby bez tej dodatkowej warstwy. Z drugiej strony, jeśli budujesz warstwę, która nie przypomina niczego w rodzaju ORM, powinieneś zakwestionować swój wybór technologii mapowania relacyjno-obiektowego.

dasblinkenlight
źródło
Cieszę się, że .NET rozwiązał ten problem, wypalając obiekt zapytania na platformie. Port .NET Hibernacji nawet go obsługuje.
Michael Brown,
2
@MikeBrown Tak, a .NET dostarczył również dwie własne konkurencyjne technologie ORM, obie wykorzystujące technologię LINQ!
dasblinkenlight,
@dasblinkenlight Zaktualizowałem pytanie, aby uzyskać dodatkowe informacje.
Patrick Desjardins,
Ostatnio przyjęłam podejście polegające na tym, że warstwa biznesowa zależy od warstwy danych opartej na interfejsach przypominających mapę i zestaw do przechowywania stanu. Wierzę teraz, że te interfejsy stanowią wystarczającą troskę o utrzymanie stanu (zainspirowane funkcjonalnym stylem programowania) i zapewniają ładne oddzielenie od wybranej ORM poprzez raczej cienką implementację.
beluchin
2

UnitOfWork zazwyczaj zapewnia tę abstrakcję. Jest to jedno miejsce, które musi się zmienić, twoje repozytoria zależą od niego za pośrednictwem interfejsu. Jeśli kiedykolwiek będziesz musiał zmienić O / RM, po prostu zaimplementuj nowy UoW. Jeden i gotowe.

BTW to coś więcej niż zmiana O / RM, pomyśl o testowaniu jednostkowym. Mam trzy implementacje UnitOfWork, jedną dla EF, jedną dla NH (ponieważ faktycznie DID musiałem przełączać O / RM w trakcie projektu dla klienta, który chciał obsługiwać Oracle), i jedną dla trwałości InMemory. Trwałość InMemory była idealna do testów jednostkowych, a nawet do szybkiego prototypowania, zanim byłam gotowa umieścić bazę danych.

Ramy są łatwe do wdrożenia. Najpierw masz ogólny interfejs IRepository

public interface IRepository<T>
  where T:class
{
  IQueryable<T> FindBy(Expression<Func<T,Bool>>filter);
  IQueryable<T> GetAll();
  T FindSingle(Expression<Func<T,Bool>> filter);
  void Add(T item);
  void Remove(T item);

}

I interfejs IUnitOfWork

public interface IUnitOfWork
{
   IQueryable<T> GetSet<T>();
   void Save();
   void Add<T>(T item) where T:class;
   void Remove<T>(T item) where T:class;
}

Następnie znajduje się podstawowe repozytorium (wybór, czy ma być abstrakcyjny)

public abstract class RepositoryBase<T>:IRepository<T>
  where T:class
{
   protected readonly IUnitOfWork _uow;

   protected RepositoryBase(IUnitOfWork uow)
   { 
      _uow=uow;
   }

   public IQueryable<T> FindBy(Expression<Func<T,Bool>>filter)
   {
      return _uow.GetSet<T>().Where(filter);
   }

   public IQueryable<T> GetAll()
   {
      return _uow.GetSet<T>();
   }

   public T FindSingle(Expression<Func<T,Bool>> filter)
   {
      return _uow.GetSet<T>().SingleOrDefault(filter);
   }

   public void Add(T item)
   {
      return _uow.Add(item);
   }

   public void Remove(T item)
   {
      return _uow.Remove(item);
   }
}

Wdrażanie IUnitOfWork jest dziecinnie proste dla EF, NH i In Memory. Powód, dla którego zwracam IQueryable, jest z tego samego powodu, o czym Ayende wspomniał w swoim poście, że klient może dalej filtrować, sortować, grupować, a nawet wyświetlać wyniki za pomocą LINQ, a Ty nadal korzystasz z tego po stronie serwera.

Michael Brown
źródło
1
Ale pytanie tutaj polega na ustaleniu, czy powyższa warstwa jest przydatna i powinna być strażnikiem dostępu do wszystkich danych.
brian
Chciałbym móc wskazać mój post na blogu dotyczący implementacji jednostki pracy / repozytorium. Omawia dokładne obawy PO.
Michael Brown,
Nadanie nazwie warstwy nie oznacza, że ​​jest to konieczne lub przydatne.
kevin cline
Uwaga: według OP ma on dodatkowe mapowanie między dostępem do danych a warstwą biznesową. Dla mnie moje obiekty biznesowe i obiekty encji są takie same. EF i NH zapewniają niesamowite interfejsy API do mapowania, dzięki czemu mapowanie danych rzadko (jeśli w ogóle) staje się problemem.
Michael Brown,
Jak przetłumaczyć dowolne wyrażenie na wydajne wywołanie ORM? Nie możesz po prostu pobrać wszystkiego i wyrzucić wierszy, które nie pasują do filtra.
kevin cline