Erstellen einer Abstraktionsebene über der ORM-Ebene

12

Ich glaube, wenn Sie Ihre Repositorys haben, verwenden Sie ein ORM, das bereits genug von der Datenbank abstrahiert ist.

Wo ich jetzt arbeite, glaubt jedoch jemand, dass wir eine Ebene haben sollten, die das ORM abstrahiert, falls wir das ORM später ändern möchten.

Ist es wirklich notwendig oder ist es einfach viel Aufwand, eine Ebene zu erstellen, die auf vielen ORM funktioniert?

Bearbeiten

Nur um mehr Details zu geben:

  1. Wir haben POCO-Klasse und Entity-Klasse, die mit AutoMapper zugeordnet sind. Entitätsklassen werden von der Repository-Schicht verwendet. Die Repository-Schicht verwendet dann die zusätzliche Abstraktionsebene, um mit Entity Framework zu kommunizieren.
  2. Die Geschäftsschicht hat in keiner Weise direkten Zugriff auf Entity Framework. Auch ohne die zusätzliche Abstraktionsebene über das ORM muss diese die Serviceschicht verwenden, die die Repository-Schicht verwendet. In beiden Fällen ist die Geschäftsschicht vollständig vom ORM getrennt.
  3. Das Hauptargument ist, ORM in Zukunft ändern zu können. Da es wirklich in der Repository-Schicht lokalisiert ist, ist es für mich bereits gut getrennt und ich verstehe nicht, warum eine zusätzliche Abstraktionsebene erforderlich ist, um einen "Qualitäts" -Code zu haben.
Patrick Desjardins
quelle
3
Eine zusätzliche Schicht muss begründet werden, da sie sonst gegen YAGNI verstößt. Mit anderen Worten, jemand, der glaubt, dass Sie es brauchen, muss das beweisen
Mücke
2
Ich kann verstehen, dass eine Domänenschicht gewünscht wird, die nur eine gewünschte Teilmenge von Operationen verfügbar macht. ORMs haben in der Regel eine etwas zu große Oberfläche (sagen wir, Sie möchten keine Aktualisierungen für eine Entität zulassen, die nicht von einer anderen enthaltenden Entität geleitet wird). Eine solche Abstraktionsebene hilft dabei.
Oded
4
Sie benötigen wahrscheinlich eine zweite Abstraktionsebene für die erste Abstraktionsebene über dem ORM, nur für den Fall, dass Sie auch die erste Ebene ändern möchten.
David Peterman
1
@David Während wir Redudancy hinzufügen, ändern Sie alle Ihre if (boolean) in if (boolean == true) und wenn Sie mehr davon wieder erbrechen möchten, if (boolean == true == true ...) und so weiter
Brian
1
Interessanter verwandter Beitrag: ayende.com/blog/3955/repository-is-the-new-singleton
RMalke

Antworten:

12

Auf diese Weise liegt der Wahnsinn. Es ist sehr unwahrscheinlich, dass Sie jemals ORMs ändern müssen. Und wenn Sie sich jemals dazu entschließen, das ORM zu ändern, machen die Kosten für das Umschreiben der Zuordnungen einen winzigen Bruchteil der Kosten für die Entwicklung und Pflege Ihres eigenen Meta-ORM aus. Ich würde erwarten, dass Sie ein paar Skripte schreiben können, um 95% der Arbeit zu erledigen, die zum Wechseln von ORMs erforderlich ist.

Inhouse-Frameworks sind fast immer eine Katastrophe. Der Bau eines solchen im Vorgriff auf zukünftige Bedürfnisse ist fast eine garantierte Katastrophe. Erfolgreiche Frameworks werden aus erfolgreichen Projekten extrahiert und nicht im Voraus erstellt, um imaginären Anforderungen gerecht zu werden.

Kevin Cline
quelle
12

Das ORM bietet eine Abstraktion für Ihre Datenschicht, die unabhängig von ihrem RDBMS ist. Es reicht jedoch möglicherweise nicht aus, Ihre Geschäftsschicht von Ihrer Datenschicht zu "lösen". Insbesondere sollten Sie nicht zulassen, dass Objekte, die RDBMS-Tabellen zugeordnet sind, direkt in die Geschäftsschicht "gelangen".

Zumindest muss Ihre Geschäftsschicht auf Schnittstellen programmieren, die Ihre ORM-verwalteten, tabellenabgebildeten Objekte aus der Datenschicht möglicherweise implementieren könnten. Darüber hinaus müssen Sie möglicherweise eine schnittstellenbasierte Ebene für die Erstellung abstrakter Abfragen erstellen, um die nativen Abfragefunktionen Ihres ORM auszublenden. Das Hauptziel besteht darin, zu vermeiden, dass ein bestimmtes ORM über die Datenschicht hinaus in Ihre Lösung "eingebrannt" wird. Beispielsweise könnte es verlockend sein, HQL- Zeichenfolgen ( Hibernate Query Language ) in der Geschäftsschicht zu erstellen . Diese scheinbar unschuldige Entscheidung würde jedoch Ihre Geschäftsschicht an den Ruhezustand binden und so die Geschäfts- und die Datenzugriffsschicht zusammenbringen. Sie sollten versuchen, diese Situation so weit wie möglich zu vermeiden.

BEARBEITEN: In Ihrem Fall ist die zusätzliche Schicht im Repository Zeitverschwendung: Basierend auf Ihrem zweiten Punkt ist Ihre Geschäftsschicht ausreichend von Ihrem Repository isoliert. Das Bereitstellen einer zusätzlichen Isolierung würde zu unnötiger Komplexität ohne zusätzliche Vorteile führen.

Das Problem beim Erstellen einer zusätzlichen Abstraktionsschicht in Ihrem Repository besteht darin, dass die bestimmte "Marke" von ORM die Art und Weise bestimmt, wie Sie damit interagieren. Wenn Sie einen dünnen Wrapper erstellen, der wie Ihr ORM aussieht, aber unter Ihrer Kontrolle steht, ist das Ersetzen des zugrunde liegenden ORM ungefähr so ​​schwierig wie ohne diese zusätzliche Ebene. Wenn Sie andererseits eine Ebene erstellen, die Ihrem ORM nicht ähnelt, sollten Sie Ihre Wahl der objektrelationalen Zuordnungstechnologie in Frage stellen.

dasblinkenlight
quelle
Ich bin so froh, dass .NET dieses Problem gelöst hat, indem das Abfrageobjekt in die Plattform gebacken wurde. Der .NET-Port von Hibernate unterstützt dies sogar.
Michael Brown
2
@MikeBrown Ja, und .NET lieferte auch zwei eigene konkurrierende ORM-Technologien, die beide die LINQ-Technologie verwenden!
Dasblinkenlight
@dasblinkenlight Ich habe die Frage aktualisiert, um Ihnen zusätzliche Informationen zu geben.
Patrick Desjardins
In letzter Zeit habe ich den Ansatz gewählt, dass die Geschäftsschicht von einer Datenschicht abhängt, die auf karten- und satzartigen Schnittstellen basiert, um den Status zu speichern. Ich glaube jetzt, dass diese Schnittstellen das Anliegen der Staatsführung gut genug darstellen (inspiriert vom funktionalen Programmierstil) und durch eine eher dünne Implementierung eine schöne Trennung vom ORM der Wahl bieten.
Beluchin
2

Die UnitOfWork stellt normalerweise diese Abstraktion bereit. Es ist ein Ort, der geändert werden muss. Ihre Repositorys hängen über eine Schnittstelle davon ab. Wenn Sie jemals O / RM ändern müssen, implementieren Sie einfach eine neue UoW darüber. Eins und fertig.

Übrigens geht es über das reine Umschalten von O / RM hinaus. Denken Sie an Unit-Tests. Ich habe drei UnitOfWork-Implementierungen, eine für EF, eine für NH (weil ich die O / RMs während des Projekts für einen Client wechseln musste, der Oracle-Unterstützung wünschte) und eine für InMemory-Persistenz. Die InMemory-Persistenz war perfekt für Unit-Tests oder sogar für Rapid Prototyping, bevor ich bereit war, eine Datenbank dahinter zu stellen.

Das Framework ist einfach zu implementieren. Zuerst haben Sie Ihre generische IRepository-Schnittstelle

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

}

Und die IUnitOfWork-Oberfläche

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

Als nächstes folgt das Basis-Repository (Sie entscheiden, ob es abstrakt sein soll oder nicht

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

Die Implementierung von IUnitOfWork ist für EF, NH und In Memory ein Kinderspiel. Der Grund, warum ich IQueryable zurückgebe, ist der gleiche Grund, den Ayende in seinem Beitrag erwähnt hat. Der Client kann das Ergebnis mithilfe von LINQ weiter filtern, sortieren, gruppieren und sogar projizieren, und Sie profitieren weiterhin davon, dass alles serverseitig ausgeführt wird.

Michael Brown
quelle
1
Die Frage hier ist jedoch, ob diese darüber liegende Schicht nützlich ist oder nicht und der Gatekeeper für den gesamten Datenzugriff sein sollte.
Brian
Ich wünschte, ich könnte auf meinen Blog-Beitrag zur Implementierung von Unit Of Work / Repository verweisen. Es werden die genauen Bedenken des OP erörtert.
Michael Brown
Wenn Sie der Ebene einen Namen geben, bedeutet dies nicht, dass dies notwendig oder nützlich ist.
Kevin Cline
Beachten Sie laut OP, dass er eine zusätzliche Zuordnung zwischen Datenzugriff und Geschäftsschicht hat. Für mich sind meine Geschäftsobjekte und Entitätsobjekte gleich. EF und NH bieten erstaunliche Mapping-APIs, sodass die Datenmapping selten (wenn überhaupt) zu einem Problem wird.
Michael Brown
Wie übersetzt man einen beliebigen Ausdruck in einen effizienten ORM-Aufruf? Sie können nicht einfach alles abrufen und die Zeilen wegwerfen, die nicht zum Filter passen.
Kevin Cline