Heim >Backend-Entwicklung >C++ >Datenzugriff im Code über Repositorys, sogar mit ORMs

Datenzugriff im Code über Repositorys, sogar mit ORMs

Susan Sarandon
Susan SarandonOriginal
2024-12-25 11:21:10748Durchsuche

Data access in code, using repositories, even with ORMs

Einführung

In der .NET-Welt ist Entity Framework (EF) eine der am häufigsten verwendeten Methoden für den Zugriff auf Datenbanken, ein Object Relational Mapper (ORM), der eng in die Sprachsyntax integriert ist. Durch die Verwendung der in .NET-Sprachen nativen Language Integrated Queries (LINQ) fühlt sich der Datenzugriff wie die Arbeit mit normalen .NET-Sammlungen an, ohne dass große SQL-Kenntnisse erforderlich sind. Das hat seine Vor- und Nachteile, über die ich hier nicht schimpfen möchte. Aber eines der Probleme, die dadurch immer wieder entstehen, ist Verwirrung hinsichtlich der Struktur des Softwareprojekts, der Abstraktionsebenen und letztendlich der Unit-Tests.

In diesem Beitrag wird versucht zu erklären, warum die Repository-Abstraktion IMMER nützlich ist. Beachten Sie, dass viele Leute Repository als Begriff für abstrahierten Datenzugriff verwenden. Es gibt zwar auch ein Repository-Softwaremuster, das sich auf ähnliche Dinge bezieht, aber nicht dasselbe ist. Hier nenne ich ein Repository eine Reihe von Schnittstellen, die die Implementierungsdetails des Datenzugriffs abstrahieren, und ignoriere das Entwurfsmuster vollständig.

Geschichte

Sie können dies gerne überspringen, wenn Sie sich dessen bewusst sind, aber ich muss zunächst darauf eingehen, wie wir überhaupt auf die Idee von Repositories gekommen sind.

In der Vorgeschichte wurde Code einfach so geschrieben, wie er ist, ohne Struktur, mit allem, was man tun wollte oder zumindest hoffte. Es gab keine automatisierten Tests, sondern nur manuelles Hacken und Testen, bis es funktionierte. Jede Anwendung wurde in der verfügbaren Version geschrieben, wobei Bedenken hinsichtlich der Hardwareanforderungen wichtiger waren als Codestruktur, Wiederverwendung oder Lesbarkeit. Das hat die Dinosaurier getötet! Wahre Tatsache.

Langsam zeichneten sich Muster ab. Insbesondere bei Geschäftsanwendungen gab es diese offensichtliche Trennung von Geschäftscode, Datenpersistenz und Benutzeroberfläche. Diese wurden Schichten genannt und bald in verschiedene Projekte aufgeteilt, nicht nur, weil sie unterschiedliche Anliegen abdeckten, sondern auch, weil die für ihre Erstellung erforderlichen Fähigkeiten besonders unterschiedlich waren. Das UI-Design unterscheidet sich stark von der Code-Logikarbeit und stark von SQL oder der Sprache oder dem System, das zum Speichern von Daten verwendet wurde.

Daher erfolgte die Interaktion zwischen dem Unternehmen und der Datenschicht durch Abstraktion in Schnittstellen und Modelle. Als Business-Klasse würden Sie nicht nach der Liste der Einträge in einer Tabelle fragen, sondern nach einer gefilterten Liste komplexer Objekte. Es läge in der Verantwortung der Datenschicht, auf die gespeicherten Daten zuzugreifen und sie auf etwas abzubilden, das für das Unternehmen verständlich ist. Diese Abstraktionen wurden als Repositories bezeichnet.

Auf den unteren Ebenen des Datenzugriffs haben sich Muster wie CRUD schnell durchgesetzt: Sie definierten strukturierte Persistenzcontainer wie Tabellen und erstellten, lasen, aktualisierten oder löschten Datensätze. Im Code wird diese Art von Logik auf Sammlungen wie List, Dictionary oder Array abstrahiert. Daher herrschte auch die Meinung vor, dass sich Repositories wie Sammlungen verhalten sollten, vielleicht sogar generisch genug, um keine anderen Methoden als das eigentliche Erstellen, Lesen, Aktualisieren und Löschen zu haben.

Ich bin jedoch entschieden anderer Meinung. Als Abstraktionen des Datenzugriffs aus dem Unternehmen sollten sie möglichst weit von den Mustern des Datenzugriffs entfernt sein und stattdessen auf Basis der Geschäftsanforderungen modelliert werden. Hier begann insbesondere die Denkweise des Entity Frameworks, aber auch vieler anderer ORMs, mit der ursprünglichen Idee des Repositorys in Konflikt zu geraten, was in der Forderung gipfelte, niemals Repositorys mit EF zu verwenden, was als Antimuster bezeichnet wurde.

Mehr Schichten

Viel Verwirrung entsteht durch Eltern-Kind-Beziehungen zwischen Modellen. Wie eine Abteilungseinheit mit Personen darin. Sollte ein Abteilungs-Repository ein Modell zurückgeben, das Personen enthält? Vielleicht auch nicht. Wie wäre es also, wenn wir Repositorys in Abteilungen (ohne Personen) und Personen unterteilen und dann eine separate Abstraktion hätten, um sie dann auf Geschäftsmodelle abzubilden?

Die Verwirrung nimmt tatsächlich zu, wenn wir die Geschäftsschicht in Unterschichten aufteilen. Beispielsweise handelt es sich bei dem, was die meisten Leute als Geschäftsdienst bezeichnen, um eine Abstraktion, bei der es darum geht, eine bestimmte Geschäftslogik nur auf eine bestimmte Art von Geschäftsmodell anzuwenden. Nehmen wir an, Ihre App funktioniert mit Menschen, also haben Sie ein Modell namens Person. Die Klasse zur Verwaltung von Personen wird ein PeopleService sein, der über ein PeopleRepository Geschäftsmodelle aus der Persistenzschicht abruft, aber auch andere Dinge erledigt, einschließlich einer Zuordnung zwischen Datenmodellen und Geschäftsmodellen oder spezifischer Arbeit, die sich nur auf Personen bezieht, wie z. B. deren Berechnung Gehälter. Die meisten Geschäftslogiken verwenden jedoch mehrere Arten von Modellen, sodass Dienste letztlich als Mapping-Wrapper über Repositorys dienen und kaum zusätzliche Verantwortung tragen.

Stellen Sie sich nun vor, Sie verwenden EF, um auf die Daten zuzugreifen. Sie müssen bereits eine DbContext-Klasse deklarieren, die Sammlungen von Entitäten enthält, die Sie SQL-Tabellen zuordnen. Sie verfügen über LINQ zum Iterieren, Filtern und Zuordnen, das im Hintergrund effizient in SQL-Befehle umgewandelt wird und Ihnen das liefert, was Sie benötigen, komplett mit hierarchischen Eltern-Kind-Strukturen. Diese Konvertierung kümmert sich auch um die Zuordnung interner Geschäftsdatentypen, wie z. B. spezifische Aufzählungen oder seltsame Datenstrukturen. Warum brauchen Sie also überhaupt Repositories, vielleicht sogar Dienste?

Ich glaube, dass mehr Abstraktionsebenen zwar als sinnloser Aufwand erscheinen mögen, sie aber das menschliche Verständnis des Projekts verbessern und die Geschwindigkeit und Qualität der Veränderung verbessern. Offensichtlich besteht ein Gleichgewicht. Ich habe Systeme gesehen, die offensichtlich mit der Anforderung entworfen wurden, dass alle Software-Entwurfsmuster überall verwendet werden müssen. Abstraktion ist nur dann sinnvoll, wenn sie die Lesbarkeit des Codes und die Trennung von Bedenken verbessert.

Grund

Einer der Kontexte, in denen EF umständlich wird, sind Unit-Tests. DbContext ist ein kompliziertes System mit vielen Abhängigkeiten, die man mit großem Aufwand manuell nachahmen müsste. Deshalb kam Microsoft auf eine Idee: In-Memory-Datenbankanbieter. Um also etwas zu testen, verwenden Sie einfach eine In-Memory-Datenbank und fertig.

Beachten Sie, dass diese Testmethode auf Microsoft-Seiten jetzt mit „nicht empfohlen“ gekennzeichnet ist. Beachten Sie auch, dass EF selbst in diesen Beispielen durch Repositorys abstrahiert wird.

In-Memory-Datenbanktests funktionieren zwar, sie bringen jedoch mehrere Probleme mit sich, die nicht einfach zu beheben sind:

  • Das Einrichten eines speicherinternen DbContext erfordert alle Abhängigkeiten zu vorhandenen Entitäten
  • Das Einrichten und Starten der Speicherdatenbank für jeden Test ist langsam
  • Um eine gültige Datenbankausgabe zu erhalten, müssen Sie viel mehr einrichten, als Sie atomar testen möchten

Am Ende richten die Leute also alles in der Datenbank mit einer „Hilfsmethode“ ein und erstellen dann Tests, die mit dieser undurchschaubaren und komplexen Methode beginnen, um selbst die kleinste Funktionalität zu testen. Jeder Code, der EF-Code enthält, kann ohne dieses Setup nicht getestet werden.

Ein Grund für die Verwendung von Repositorys besteht also darin, die Testabstraktion über DbContext zu verschieben. Jetzt benötigen Sie überhaupt keine Datenbank mehr, sondern nur noch ein Repository-Mock. Testen Sie dann Ihr Repo selbst in Integrationstests anhand einer echten Datenbank. Die In-Memory-Datenbank kommt einer echten Datenbank sehr nahe, unterscheidet sich aber auch geringfügig.

Ein weiterer Grund, von dem ich zugeben muss, dass er im wirklichen Leben selten von tatsächlichem Wert ist, ist, dass Sie möglicherweise die Art und Weise ändern möchten, wie Sie auf die Daten zugreifen. Vielleicht möchten Sie zu NoSql oder einem speicherverteilten Cache-System wechseln. Oder, was viel wahrscheinlicher ist: Sie haben mit einer Datenbankstruktur begonnen, vielleicht einer monolithischen Datenbank, und möchten diese nun in mehrere Datenbanken mit unterschiedlichen Tabellenstrukturen umgestalten. Lassen Sie mich Ihnen gleich sagen, dass dies ohne Repositorys UNMÖGLICH sein wird.

Und spezifisch für Entity Framework sind die Entitäten, die Sie erhalten, aktive Datensätze, die der Datenbank zugeordnet sind. Sie nehmen eine Änderung in einem vor und speichern die Änderungen für ein anderes, und plötzlich wird auch die erste Entität in der Datenbank aktualisiert. Oder vielleicht auch nicht, weil Sie etwas nicht eingefügt haben oder sich der Kontext geändert hat.

Die Befürworter von EF loben die Verfolgung von Entitäten immer als eine sehr positive Sache. Nehmen wir an, Sie rufen eine Entität aus der Datenbank ab, tätigen dann Geschäfte, aktualisieren die Entität und speichern sie. Bei einem Repo würden Sie die Daten abrufen, dann Geschäfte tätigen und dann die Daten erneut abrufen, um ein kleines Update durchzuführen. EF würde es im Speicher behalten und wissen, dass es vor Ihrer Änderung nicht aktualisiert wurde, sodass es es nie zweimal lesen würde. Das stimmt. Sie beschreiben einen Speichercache für die Datenbank, der Datenbankänderungen irgendwie erkennt und alles verfolgt, was Sie in der Datenbank verarbeiten, sofern nicht anders angegeben, Datenbankeinträge bidirektional komplexen C#-Entitäten zuordnet und Änderungen hin und her verfolgt, während er tief eingebettet ist im Geschäftsgesetzbuch. Persönlich glaube ich, dass diese Fülle an Verantwortlichkeiten und die mangelnde Trennung von Belangen weitaus schädlicher sind als jede dadurch erzielte Leistung. Darüber hinaus kann die gesamte Funktionalität mit einigem anfänglichen Aufwand immer noch in einem Repository abstrahiert werden, oder vielleicht sogar in einer anderen Ebene des Speicher-Cachings für ein Repository, während klare Grenzen zwischen Geschäft, Caching und Datenzugriff gewahrt bleiben.

Tatsächlich besteht die eigentliche Schwierigkeit bei all dem darin, die Grenzen zwischen Systemen zu bestimmen, die unterschiedliche Anliegen haben sollten. Beispielsweise kann man durch die Verlagerung der Filterlogik auf gespeicherte Prozeduren in der Datenbank viel Leistung gewinnen, dadurch geht jedoch die Testbarkeit und Lesbarkeit des verwendeten Algorithmus verloren. Das Gegenteil, die Verlagerung der gesamten Logik in Code mithilfe von EF oder einem anderen Mechanismus, ist weniger leistungsfähig und manchmal nicht durchführbar. Oder wo ist der Punkt, an dem Datenentitäten zu Geschäftsentitäten werden (siehe das Beispiel oben mit Abteilung und Person)?

Die vielleicht beste Strategie besteht darin, zunächst diese Grenzen zu definieren und dann zu entscheiden, welche Technologie und welches Design da hineinpassen.

Mein Fazit

Ich glaube, dass Service- und Repository-Abstraktionen immer verwendet werden sollten, auch wenn das Repository Entity Framework oder ein anderes darunter liegendes ORM verwendet. Es läuft alles auf die Trennung der Belange hinaus. Ich würde Entity Framework niemals als nützliche Software-Abstraktion betrachten, da es so viel Ballast mit sich bringt und daher häufig ein Repository verwendet wird, um es im Code zu abstrahieren. EF ist eine nützliche Abstraktion, aber für den Datenbankzugriff, nicht in Software.

Meine Philosophie beim Schreiben von Software besteht darin, dass Sie mit den Anwendungsanforderungen beginnen, Komponenten für diese Anforderungen erstellen und alle Funktionen auf niedrigerer Ebene mit Schnittstellen abstrahieren. Anschließend wiederholen Sie den Vorgang auf der nächsten Ebene und stellen dabei stets sicher, dass der Code lesbar ist und kein Verständnis der verwendeten Komponenten oder der auf der aktuellen Ebene verwendeten Komponenten erfordert. Wenn das nicht der Fall ist, haben Sie die Bedenken schlecht getrennt. Da für keine Geschäftsanwendung jemals die Verwendung einer bestimmten Datenbank oder eines ORM erforderlich war, sollte die Abstraktion der Datenschicht das gesamte Wissen darüber verbergen.

Was will das Unternehmen? Eine gefilterte Personenliste? var people = service.GetFilteredListOfPeople(filter); nicht weniger, nicht mehr. und die Service-Methode würde einfach return mapPeople(repo.GetFilteredListOfPeople(mappedFilter)); wieder nicht weniger und nicht mehr. Wie das Repo die Leute bekommt, rettet oder irgendetwas anderes tut, ist nicht die Angelegenheit des Dienstes. Sie möchten Caching und implementieren dann einen Caching-Mechanismus, der IPeopleRepository implementiert und eine Abhängigkeit von IPeopleRepository aufweist. Wenn Sie eine Zuordnung wünschen, implementieren Sie die richtigen IMapper-Schnittstellen. Und so weiter.

Ich hoffe, dass ich in diesem Artikel nicht zu ausführlich war. Ich habe Codebeispiele ausdrücklich herausgehalten, da es sich hierbei eher um ein konzeptionelles und nicht um ein Softwareproblem handelt. Entity Framework ist vielleicht das Ziel der meisten meiner Beschwerden hier, aber das gilt für jedes System, das Ihnen auf magische Weise bei kleinen Dingen hilft, aber die wichtigen Dinge kaputt macht.

Ich hoffe, das hilft!

Das obige ist der detaillierte Inhalt vonDatenzugriff im Code über Repositorys, sogar mit ORMs. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn