{"id":15147,"date":"2022-05-13T14:17:30","date_gmt":"2022-05-13T12:17:30","guid":{"rendered":"https:\/\/www.centigrade.de\/?post_type=blog&#038;p=15147"},"modified":"2022-06-21T11:43:11","modified_gmt":"2022-06-21T09:43:11","slug":"graphql-net-und-react-miteinander-verbinden-teil-iii-veda-versum-backend-authentication","status":"publish","type":"blog","link":"https:\/\/www.centigrade.de\/de\/blog\/graphql-net-und-react-miteinander-verbinden-teil-iii-veda-versum-backend-authentication\/","title":{"rendered":"GraphQL, .Net und React miteinander verbinden \u2013 Teil III: Veda Versum backend authentication."},"content":{"rendered":"<h2>1. Persistenz der Daten<\/h2>\r\n<p>Wie wir bereits definiert haben, besteht unsere API aus einer Haupteinheit &#8211; <em>VedaVersumCard<\/em>. Unsere Anwendung dient dazu, die Liste der Wissenskarten zu f\u00fchren und den Nutzern die M\u00f6glichkeit zu geben, diese Karten zu erstellen, zu lesen und zu bearbeiten. Um dies zu erm\u00f6glichen, haben wir eine Mutation und 3 Abfragen in unserer API definiert. Die Mutation <strong><em>&#8222;CardAction&#8220; <\/em><\/strong>soll die Kartendaten als Argument nehmen und diese Daten irgendwo speichern. Die Abfrage <strong><em>&#8222;allCards&#8220; <\/em><\/strong>soll die Liste aller vorhandenen Karten zur\u00fcckgeben. Die Abfrage &#8222;<strong><em>card<\/em><\/strong>&#8220; sollte eine Karte nach Karten-ID zur\u00fcckgeben, und die Abfrage &#8222;<strong><em>allCardsAssignedToUser<\/em><\/strong>&#8220; sollte die Benutzer-E-Mail als Eingabe nehmen und Karten zur\u00fcckgeben, die nach der Eigenschaft &#8222;<strong><em>assignedUsers<\/em><\/strong>&#8220; gefiltert sind, deren Wert dem eingegebenen Benutzer entspricht.<!--more--><\/p>\r\n<div id=\"attachment_15109\" style=\"width: 1216px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-15109\" class=\"size-full wp-image-15109\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild1-3.png\" alt=\"VedaVersum card entity\" width=\"1206\" height=\"716\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild1-3.png 1206w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild1-3-300x178.png 300w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild1-3-768x456.png 768w\" sizes=\"auto, (max-width: 1206px) 100vw, 1206px\" \/><p id=\"caption-attachment-15109\" class=\"wp-caption-text\">VedaVersum card entity<\/p><\/div>\r\n<p>Im Moment sind diese Mutation und die 3 Abfragen noch nicht implementiert und f\u00fchren keine relevanten Aktionen mit unserer Entit\u00e4t durch. Nun ist es an der Zeit, die Datenbank zu w\u00e4hlen, in der unsere Kartenliste gespeichert werden soll. Wir haben hier viele M\u00f6glichkeiten. Wir k\u00f6nnen eine relationale Datenbank wie Oracle oder MS SQL Server verwenden. Aber das sind Industriegiganten, die unter hoher Last arbeiten und Hunderte von Transaktionen pro Sekunde durchf\u00fchren. Das leichtgewichtige <a href=\"https:\/\/www.sqlite.org\/index.html\">SQLite<\/a> ist eher f\u00fcr kleine Anwendungen geeignet. Die wichtigsten Vorteile von SQLite sind folgende:<\/p>\r\n<ul>\r\n<li>es ist kostenlos und quelloffen<\/li>\r\n<li>Es speichert die Daten in einer einzigen Datei und ben\u00f6tigt keinen Server, auf dem die Datenbank-Engine l\u00e4uft.<\/li>\r\n<li>dotNet verf\u00fcgt \u00fcber ein hervorragendes Werkzeug f\u00fcr die Arbeit mit relationalen Datenbanken namens <a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/\">Entity Framework. <\/a><\/li>\r\n<\/ul>\r\n<p>Andererseits erfordert SQLite, wie jede relationale Datenbank, die Definition eines stark typisierten Datenbankschemas. Zum Beispiel k\u00f6nnen wir in unserer VedaVersum-Entit\u00e4t in Zukunft einige andere Eigenschaften wie das Flag &#8222;Archiviert&#8220; definieren. Im Falle einer relationalen Datenbank ist dies eine einschneidende \u00c4nderung, die einige Anstrengungen erfordert, um Schemamigrationen mit bereits bestehenden Datenbanken zu erm\u00f6glichen. Sie sind gerechtfertigt, wenn wir ein reichhaltiges, gut strukturiertes, denormalisiertes Datenmodell haben, das sich nicht allzu oft \u00e4ndert. Aber in unserem Fall sind die VedaVersum-Entit\u00e4ten nur eine Liste von Dokumenten. NoSQL-Datenbanken sind die perfekte Datenbank, um Listen von Dokumenten zu speichern. Aus diesem Grund werden wir <a href=\"https:\/\/www.mongodb.com\/\">MongoDB<\/a> verwenden. Dies ist der Hauptakteur in der Welt der NoSQL-Datenbanken. Sie ist kostenlos und Open-Source. Sie speichert Daten als Liste von JSON-Dokumenten und ben\u00f6tigt kein stark typisiertes Datenbankschema. Au\u00dferdem bietet sie wie alle seri\u00f6sen Datenbanken hohe Leistung, Skalierung und Backup-Tools.<\/p>\r\n<h3>2.1. MongoDB einrichten<\/h3>\r\n<p>Wenn Sie noch nie Mongo in Ihrem Entwicklungssystem verwendet haben, sollten Sie zuerst die Datenbank-Engine installieren. <a href=\"https:\/\/docs.mongodb.com\/manual\/installation\/\">Hier sind einige Anweisungen, wie Sie dies tun k\u00f6nnen<\/a>. Ich verwende <a href=\"https:\/\/www.docker.com\/products\/docker-desktop\">Docker Desktop<\/a> auf meinem Rechner und habe MongoDB als Docker-Container gestartet.<\/p>\r\n<p>Sobald wir die MongoDB-Engine zum Laufen gebracht haben, k\u00f6nnen wir sie in unserem Projekt verwenden. Es gibt einen <a href=\"https:\/\/docs.mongodb.com\/drivers\/csharp\/\">MongoDB-Treiber f\u00fcr DotNet<\/a>, den wir in unserem Projekt verwenden werden, also sollten wir zuerst ein <a href=\"https:\/\/www.nuget.org\/packages\/mongodb.driver\">Nuget-Paket<\/a> zu unserem Projekt hinzuf\u00fcgen. \u00a0<\/p>\r\n<p>Zweitens sollten wir einen neuen Parameter zu unserer <em>appsettings.json <\/em>mit der <a href=\"https:\/\/mongodb.github.io\/mongo-csharp-driver\/2.14\/reference\/driver\/connecting\/\">Verbindungszeichenfolge<\/a> hinzuf\u00fcgen.<\/p>\r\n<p>Und drittens sollten wir eine neue Schnittstelle <strong><em>IVedaVersumDataAccess <\/em><\/strong>und einen VedaVersumDataAccess-Dienst definieren, der diese Schnittstelle implementiert.<\/p>\r\n<p><br \/><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-15112\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild2-3.png\" alt=\"IVedaVersumDataAccess and VedaVersumDataAccess \" width=\"321\" height=\"136\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild2-3.png 321w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild2-3-300x127.png 300w\" sizes=\"auto, (max-width: 321px) 100vw, 321px\" \/><\/p>\r\n<h3>2.2. Implementierung des Datenzugriffs<\/h3>\r\n<p>Wir haben die Datenzugriffsschnittstelle mit CRUD-Operationen definiert. Wir werden diese Schnittstelle in unseren Mutationen und Abfragen verwenden. Und hier beschreibe ich kurz die Implementierung des Datenzugriffs selbst.<\/p>\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n  &quot;ConnectionStrings&quot;: {\r\n    &quot;mongo&quot;: &quot;mongodb:\/\/localhost:27017&quot;\r\n  }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p><a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/blob\/main\/VedaVersum.Backend\/DataAccess\/VedaVersumDataAccess.cs\">VedaVersumDataAccess.cs<\/a><\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public class VedaVersumDataAccess : IVedaVersumDataAccess\r\n    {\r\n        private const string DatabaseName = &quot;VedaVersum&quot;;\r\n        private const string VedaVersumCardsCollectionName = &quot;Cards&quot;;\r\n        private readonly IMongoDatabase _database;\r\n        public VedaVersumDataAccess(string mongoDbConnectionString, ILogger&lt;VedaVersumDataAccess&gt; logger)\r\n        {\r\n            var mongoConnectionUrl = new MongoUrl(mongoDbConnectionString);\r\n            var mongoClientSettings = MongoClientSettings.FromUrl(mongoConnectionUrl);\r\n            mongoClientSettings.ClusterConfigurator = cb =&gt; {\r\n                cb.Subscribe&lt;CommandStartedEvent&gt;(e =&gt; {\r\n                    logger.LogDebug($&quot;{e.CommandName} - {e.Command.ToJson()}&quot;);\r\n                });\r\n            };\r\n            var client = new MongoClient(mongoClientSettings);\r\n            _database = client.GetDatabase(DatabaseName);\r\n        }\r\n\r\n        \/\/\/ &lt;inheritdoc \/&gt;\r\n        public async Task DeleteCard(string cardId)\r\n        {\r\n            var cardsCollection = _database.GetCollection&lt;VedaVersumCard&gt;(VedaVersumCardsCollectionName);\r\n            await cardsCollection.DeleteOneAsync(Builders&lt;VedaVersumCard&gt;.Filter.Where(c =&gt; c.Id == cardId));\r\n        }\r\n\r\n        \/\/\/ &lt;inheritdoc \/&gt;\r\n        public async Task&lt;IEnumerable&lt;VedaVersumCard&gt;&gt; GetAll()\r\n        {\r\n            var cardsCollection = _database.GetCollection&lt;VedaVersumCard&gt;(VedaVersumCardsCollectionName);\r\n            var allCards = await cardsCollection.FindAsync(Builders&lt;VedaVersumCard&gt;.Filter.Empty);\r\n            return allCards.ToList();\r\n        }\r\n        \/\/ Code omitted for brevity\r\n    }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>startup.cs<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n            \/\/ DataAccess\r\n            var connectionString = Configuration.GetConnectionString(&quot;mongo&quot;);\r\n            services.AddTransient&lt;IVedaVersumDataAccess, VedaVersumDataAccess&gt;((p) =&gt; new VedaVersumDataAccess(\r\n                connectionString, \r\n                p.GetService&lt;ILogger&lt;VedaVersumDataAccess&gt;&gt;()!));\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Den vollst\u00e4ndigen Code finden Sie im <a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/tree\/main\/VedaVersum.Backend\">GitHub<\/a>-Repository. Wie Sie sehen k\u00f6nnen, ist es ziemlich einfach, MongoDB in ASP.Net Core-Anwendungen zu verwenden. Und hier sind unsere API-Methoden in Betrieb:<\/p>\r\n\r\n\r\n\r\n<p>Create Card:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"538\" height=\"343\" class=\"wp-image-15114\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild3-3.png\" alt=\"Create Card\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild3-3.png 538w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild3-3-300x191.png 300w\" sizes=\"auto, (max-width: 538px) 100vw, 538px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Read All Cards:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"550\" height=\"351\" class=\"wp-image-15116\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild4-3.png\" alt=\"Read All Cards\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild4-3.png 550w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild4-3-300x191.png 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Update card:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"550\" height=\"361\" class=\"wp-image-15118\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild5-2.png\" alt=\"Update card\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild5-2.png 550w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild5-2-300x197.png 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Wir haben die Datenpersistenz in unserer Anwendung implementiert! Juhu!\u00a0<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">3. GraphQL resolvers und data loaders<\/h2>\r\n\r\n\r\n\r\n<p>Nun ist es an der Zeit, ein wenig \u00fcber die Querverweise zwischen Datenobjekten und die Leistung zu sprechen. Wie Sie bereits bemerkt haben, verf\u00fcgt jede knowledge card \u00fcber eine Liste von zugeh\u00f6rigen card Ids. Die Karten werden in der Datenbank mit dieser Struktur gespeichert:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"592\" height=\"377\" class=\"wp-image-15122\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild6-2.png\" alt=\"Stored cards\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild6-2.png 592w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild6-2-300x191.png 300w\" sizes=\"auto, (max-width: 592px) 100vw, 592px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>So verweist &#8222;card two&#8220; auf &#8222;card on&#8220;, &#8222;card three&#8220; auf &#8222;card two und one&#8220; und so weiter. Wenn wir alle Karten abrufen, ist das vielleicht kein gro\u00dfes Problem. Aber was ist, wenn die Client-Anwendung nur die &#8222;card three&#8220; und alle ihre Abh\u00e4ngigkeiten abrufen m\u00f6chte, sieht unsere Abfrage wie folgt aus:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"592\" height=\"299\" class=\"wp-image-15124\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild7-2.png\" alt=\"card query\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild7-2.png 592w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild7-2-300x152.png 300w\" sizes=\"auto, (max-width: 592px) 100vw, 592px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>F\u00fcr verwandte Karten haben wir nur Ids, und um deren Inhalt zu erhalten, muss der Client dieselbe Abfrage zwei weitere Male aufrufen. Und wenn es mehr als zwei verwandte Karten gibt, steigt die Anzahl der Anfragen vom Client an den Server dramatisch an. Diese Situation ist bei REST-API \u00fcblich und wird als &#8222;n+1-Problem&#8220; bezeichnet.<\/p>\r\n<p>Gl\u00fccklicherweise ist eine der Aufgaben von GraphQL, dieses n+1 Problem zu beseitigen. Wir k\u00f6nnen einen Abfrage-Hook namens <strong><em>Resolver <\/em><\/strong>definieren. Dieser Resolver erm\u00f6glicht es dem Client, alle verwandten Objekte mit einer Anfrage abzurufen. F\u00fcgen wir diesen <a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/blob\/main\/VedaVersum.Backend\/Api\/VedaVersumCardObjectType.cs\">Resolver<\/a> zu unserer GraphQL-API f\u00fcr verwandte Karten hinzu:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public class VedaVersumCardObjectType: ObjectType&lt;VedaVersumCard&gt;\r\n    {\r\n        protected override void Configure(IObjectTypeDescriptor&lt;VedaVersumCard&gt; descriptor)\r\n        {\r\n            descriptor\r\n            .Field(&quot;relatedCards&quot;) \/\/ Name of the additional field\r\n            .Type&lt;ListType&lt;VedaVersumCardObjectType&gt;&gt;()\r\n            .Resolve(async context =&gt;\r\n            {\r\n                var parent = context.Parent&lt;VedaVersumCard&gt;();\r\n\r\n                if(parent.RelatedCardIds == null || parent.RelatedCardIds.Count == 0)\r\n                {\r\n                    return new List&lt;VedaVersumCard&gt;();\r\n                }\r\n\r\n                var dataAccess = context.Service&lt;IVedaVersumDataAccess&gt;();\r\n                return await dataAccess.GetCardsById(parent.RelatedCardIds);\r\n            });\r\n        }\r\n    }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Dieser Objekttyp erweitert unser VedaVersumCard-Objekt und f\u00fcgt ihm ein neues Feld hinzu. Und implementiert die Logik zum F\u00fcllen dieses Feldes. Nun m\u00fcssen wir diese neue Objekttyp-Definition zu unserem GraphQL-Server-Setup in <strong>startup.cs <\/strong>hinzuf\u00fcgen.<\/p>\r\n\r\n\r\n\r\n<p>&nbsp;<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"751\" height=\"521\" class=\"wp-image-15126\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild8-2.png\" alt=\"extend VedaVersumCard \" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild8-2.png 751w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild8-2-300x208.png 300w\" sizes=\"auto, (max-width: 751px) 100vw, 751px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Und wir k\u00f6nnen unsere Abfrage erneut ausf\u00fchren:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"480\" height=\"298\" class=\"wp-image-15128\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild9-2.png\" alt=\"Run Query\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild9-2.png 480w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild9-2-300x186.png 300w\" sizes=\"auto, (max-width: 480px) 100vw, 480px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Wie Sie feststellen konnten, gibt es ein zus\u00e4tzliches Feld in der Abfrage namens &#8222;relatedCards&#8220;, und wir k\u00f6nnen alle Informationen zu verwandten Karten mit einer einzigen Anfrage an den GraphQL-Server abrufen. Wir sind also unser n+1 Problem losgeworden. Bingo!<\/p>\r\n\r\n\r\n\r\n<p>Es gibt noch einen weiteren Punkt. GraphQL erm\u00f6glicht sowohl die Abfrage zusammengeh\u00f6riger Daten durch eine einzige Anfrage als auch die Konstruktion der Anfrage so, dass dieselbe API-Methode mehrfach mit unterschiedlichen Parametern, aber mit nur einer Anfrage aufgerufen werden kann. Ein Beispiel: Die Client-Anwendung hat die IDs von drei Karten. Der Kunde kann die Abfrage so konstruieren, dass er die Details aller 3 Karten mit dieser Abfrage erh\u00e4lt:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"513\" height=\"318\" class=\"wp-image-15130\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild10-1.png\" alt=\"construct query\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild10-1.png 513w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild10-1-300x186.png 300w\" sizes=\"auto, (max-width: 513px) 100vw, 513px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Es funktioniert perfekt. Ich kann alle Daten abrufen, die ich will, ohne das oben erw\u00e4hnte n+1-Problem. Aber wenn ich neugierig w\u00e4re und einen Logger an den MongoDB-Client anh\u00e4ngen w\u00fcrde, k\u00f6nnte ich sehen, dass der GraphQL-Server mit dieser einen Anfrage 3 nachfolgende Anfragen an die Datenbank gestellt hat.<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"698\" height=\"281\" class=\"wp-image-15132\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild11-1.png\" alt=\"database dbug\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild11-1.png 698w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild11-1-300x121.png 300w\" sizes=\"auto, (max-width: 698px) 100vw, 698px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Werfen wir noch einmal einen Blick auf unsere Abfragemethode:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        \/\/\/ &lt;summary&gt;\r\n        \/\/\/ Returns card by ID\r\n        \/\/\/ &lt;\/summary&gt;\r\n        public async Task&lt;VedaVersumCard?&gt; GetCardById(string cardId)\r\n        {\r\n            return _dataAccess.GetCardById(cardId);\r\n        }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Trotz der einzigen Abfrage an GraphQL wird der Resolver jedes Mal f\u00fcr jede ID ausgef\u00fchrt. Und jedes Mal verwendet er den Datenzugriff, um eine Karte aus der DB zu holen. Das n+1-Problem besteht also immer noch, aber innerhalb unseres Backend-Servers.<\/p>\r\n<p>Gl\u00fccklicherweise kann GraphQL auch dieses Problem l\u00f6sen. Es gibt ein weiteres Konzept namens <strong>DataLoader<\/strong>. Und hier k\u00f6nnen wir es <a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/blob\/main\/VedaVersum.Backend\/Api\/VedaVersumCardDataLoader.cs\">implementieren<\/a>:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public class VedaVersumCardDataLoader : BatchDataLoader&lt;string, VedaVersumCard&gt;\r\n    {\r\n        private readonly IVedaVersumDataAccess _vedaVersumDataAccess;\r\n\r\n        public VedaVersumCardDataLoader(\r\n            IVedaVersumDataAccess vedaVersumDataAccess,\r\n            IBatchScheduler batchScheduler,\r\n            DataLoaderOptions&lt;string&gt;? options = null) : base(batchScheduler, options)\r\n        {\r\n            _vedaVersumDataAccess = vedaVersumDataAccess;\r\n        }\r\n\r\n        \/\/ This method collects all CardIds during the single GraphQL query and executes database query once far all CardIds\r\n        protected override async Task&lt;IReadOnlyDictionary&lt;string, VedaVersumCard&gt;&gt; LoadBatchAsync(\r\n            IReadOnlyList&lt;string&gt; keys, CancellationToken cancellationToken)\r\n        {\r\n            var allCardsByIds = await _vedaVersumDataAccess.GetCardsById(keys);\r\n            return allCardsByIds.ToDictionary(c =&gt; c.Id);\r\n        }\r\n    }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Im Hintergrund kann der GraphQL-Query-Parser also alle CardIds w\u00e4hrend einer einzelnen GraphQL-Query erhalten und diesen <strong>DataLoader <\/strong>verwenden, um eine Batch-Datenbankabfrage auszuf\u00fchren. Wenden wir diesen DataLoader auf unseren Abfrageaufl\u00f6ser an:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        \/\/\/ &lt;summary&gt;\r\n        \/\/\/ Returns card by ID\r\n        \/\/\/ &lt;\/summary&gt;\r\n        public async Task&lt;VedaVersumCard?&gt; GetCard(string cardId, VedaVersumCardDataLoader dataLoader)\r\n        {\r\n            return await dataLoader.LoadAsync(cardId, CancellationToken.None);\r\n        }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Jetzt k\u00f6nnen wir diese GraphQL-Abfrage noch einmal ausf\u00fchren:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"513\" height=\"318\" class=\"wp-image-15134\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild12-1.png\" alt=\"Execute GraphQL\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild12-1.png 513w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild12-1-300x186.png 300w\" sizes=\"auto, (max-width: 513px) 100vw, 513px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Und uns die Protokolle anschauen:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"642\" height=\"259\" class=\"wp-image-15136\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild13-1.png\" alt=\"Execute GraphQL Logs\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild13-1.png 642w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild13-1-300x121.png 300w\" sizes=\"auto, (max-width: 642px) 100vw, 642px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Es gibt nur eine einzige Batch-Abfrage. Cool, wir haben das n+1-Problem auf allen Ebenen beseitigt und der Kunde kann unsere GraphQL-API mit verschiedenen Abfragekombinationen mit optimaler Leistung nutzen.<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">4. Unit testing<\/h2>\r\n\r\n\r\n\r\n<p>Zu diesem Zeitpunkt hat der Backend-Service bereits seine Arbeit aufgenommen. Das ist gro\u00dfartig! Und w\u00e4hrend wir alles entwickelt haben, haben wir die Anwendung gestartet und gepr\u00fcft, ob die Autorisierung funktioniert, ob die Persistenz funktioniert, ob Abfragen, Mutationen und Abonnements funktionieren. Und nat\u00fcrlich mussten wir sicherstellen, dass alle Dienste und ihre Abh\u00e4ngigkeiten richtig eingerichtet waren. Wir haben all diese Aktivit\u00e4ten mit &#8222;Strawberry Shake&#8220; IDE f\u00fcr GraphQL-Abfragen durchgef\u00fchrt. Wenn wir die Benutzeroberfl\u00e4che entwickeln, \u00fcberpr\u00fcfen wir einige UI-Aktivit\u00e4ten, Benutzerszenarien und wie alles mit dem laufenden Backend zusammenarbeitet. In der Entwicklungsphase machen wir das manuell und &#8222;gelegentlich&#8220;. Wir decken nicht jedes Szenario und jeden Anwendungsfall ab. Dies wird &#8222;Smoke Testing&#8220; genannt &#8211; nur um zu pr\u00fcfen, ob die Anwendung zumindest startet und das Hauptszenario ohne gruselige Laufzeitfehler durchlaufen kann. Diese Tests garantieren jedoch nicht, dass unsere Anwendung ordnungsgem\u00e4\u00df funktioniert und f\u00fcr den Produktionseinsatz bereit ist.<\/p>\r\n<p>Bei gro\u00dfen Anwendungen empfiehlt es sich in der Regel, Integrationstestszenarien zu erstellen, die die gesamte Anwendungsfunktionalit\u00e4t abdecken. Diese Integrationstests sollten von einer anderen Person erstellt werden, nicht von demjenigen, der die Anwendung entwickelt hat. In der Regel gibt es im Team eine Rolle namens Quality Assurance Engineer, die f\u00fcr die Testszenarien verantwortlich ist. Integrationstests k\u00f6nnen automatisiert oder manuell f\u00fcr jede Entwicklungsphase durchgef\u00fchrt werden. Integrationstests und Qualit\u00e4tssicherung sind ein gro\u00dfer und wichtiger Teil des Entwicklungsprozesses. Aber das ist eine andere, lange Geschichte.<\/p>\r\n<p>Es gibt jedoch noch einen weiteren wichtigen Teil des Entwicklungsprozesses, den Unit-Test. Dieser Teil liegt in der Verantwortung der Entwickler und sollte eng mit der Entwicklung der Hauptfunktionalit\u00e4t der Anwendung verbunden sein. Es ist g\u00e4ngige Praxis, den Prozess der <a href=\"https:\/\/en.wikipedia.org\/wiki\/Test-driven_development\">testgetriebenen Entwicklung<\/a> zu befolgen. Zum Beispiel haben wir in unserer Anwendung die Schnittstelle <strong><em>IVedaVersumDataAccess <\/em><\/strong>mit einigen Methoden zur Bearbeitung der Datenbank definiert. Wir haben diese Schnittstelle mit der Klasse <strong><em>VedaVersumDataAccess<\/em><\/strong> implementiert. Au\u00dferdem haben wir diese Schnittstelle in die Abfrage- und Mutationsaufl\u00f6ser injiziert. Wenn der Client eine Abfrage oder Mutation aufruft, wird der entsprechende Resolver eingeschaltet. Dieser Resolver sollte dann die entsprechende Datenzugriffsmethode aufrufen, die ein vorhersehbares Ergebnis zur\u00fcckliefern sollte. Wir haben das Szenario f\u00fcr den Unit-Test bereits beschrieben. Wir k\u00f6nnen die Testmethoden schreiben, die eine Instanz der Klasse Mutation erzeugen und <strong><em>IVedaVersumDataAccess <\/em><\/strong>in diese Klasse injizieren. Aber im Unit-Test m\u00fcssen wir nicht die echte Datenbank aufrufen. Wir k\u00f6nnen die reale Implementierungsklasse f\u00fcr den Datenzugriff durch eine Mock-Implementierung ersetzen. Und wir k\u00f6nnen diese Mock-Implementierung in der Testmethode manipulieren und pr\u00fcfen, ob eine Methode mit einigen erwarteten Parametern aufgerufen wurde usw.<\/p>\r\n<p>Daher kann ich einen Teil des Programms &#8222;abtrennen&#8220; und ein Testszenario schreiben, das diesen bestimmten Zwischenteil unserer Anwendung \u00fcberpr\u00fcft. Und ich kann so viele Szenarien schreiben, wie ich brauche, um die Logik aller Klassen (oder Einheiten) abzudecken, ohne die ganzen komplizierten Abh\u00e4ngigkeiten zwischen verschiedenen Anwendungsmodulen zu konstruieren.<\/p>\r\n<p>Wir haben zum Beispiel die Klasse <strong><em>VedaVersumMutation<\/em><\/strong>. Und die Datenmutationsmethode hat diesen Algorithmus:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"407\" height=\"336\" class=\"wp-image-15138\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild14-1.png\" alt=\"the VedaVersumMutation class\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild14-1.png 407w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild14-1-300x248.png 300w\" sizes=\"auto, (max-width: 407px) 100vw, 407px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Und um sicher zu sein, dass dieser Algorithmus richtig funktioniert, sollten wir f\u00fcr jeden Block in diesem Algorithmus ein Testszenario schreiben. Wie Sie sehen k\u00f6nnen, verwendet die Methode eine Datenbank und einen Abo-Senderdienst. Diese beiden Abh\u00e4ngigkeiten werden durch den Konstruktor in die Klasse injiziert:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public class VedaVersumMutation\r\n    {\r\n        private readonly ITopicEventSender _eventSender;\r\n        private readonly IVedaVersumDataAccess _dataAccess;\r\n\r\n\r\n        public VedaVersumMutation(ITopicEventSender eventSender, IVedaVersumDataAccess dataAccess)\r\n        {\r\n            _eventSender = eventSender;\r\n            _dataAccess = dataAccess;\r\n        }\r\n     \/\/ Code omitted for brevity\r\n    }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Um die Mutationsmethode <strong><em>CardAction <\/em><\/strong>zu testen, sollten wir ein Objekt der Klasse <strong><em>VedaVersumMutation<\/em><\/strong> erstellen und diese Methode aufrufen. Wir k\u00f6nnen aber auch Mock-Objekte von <em>DataAccess <\/em>und <em>EventSeder anstelle <\/em>von echten Objekten in die Tests einf\u00fcgen, die verwendet werden, wenn das Programm im &#8222;Produktionsmodus&#8220; l\u00e4uft. Mit diesen Mock-Objekten k\u00f6nnen wir \u00fcberpr\u00fcfen, ob die entsprechenden Methoden mit den entsprechenden Parametern aufgerufen wurden. Daher k\u00f6nnen wir nur diesen speziellen Algorithmus testen und m\u00fcssen uns nicht um die Datenbank und andere Abh\u00e4ngigkeiten k\u00fcmmern.<\/p>\r\n<p>Dazu habe ich ein Testprojekt erstellt, das das <a href=\"https:\/\/nunit.org\/\" target=\"_blank\" rel=\"noopener\">NUnit<\/a>-Testframework und die <a href=\"https:\/\/riptutorial.com\/moq\" target=\"_blank\" rel=\"noopener\">Moq<\/a>-Bibliothek zum Einrichten von Mock-Objekten verwendet. Dies kann Ihnen zeigen, wie Sie diese Methode testen k\u00f6nnen:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public class VedaVersumMutationTests\r\n    {\r\n        \/\/\/ &lt;summary&gt;\r\n        \/\/\/ Event sender mock object\r\n        \/\/\/ &lt;\/summary&gt;\r\n        private Mock&lt;ITopicEventSender&gt; _eventsSenderMock = new Mock&lt;ITopicEventSender&gt;();\r\n        \/\/\/ &lt;summary&gt;\r\n        \/\/\/ DataAccess mock object\r\n        \/\/\/ &lt;\/summary&gt;\r\n        private Mock&lt;IVedaVersumDataAccess&gt; _dataAccessMock = new Mock&lt;IVedaVersumDataAccess&gt;();\r\n        private VedaVersumMutation MutationClassToTest;\r\n\r\n        &#x5B;SetUp]\r\n        public void Setup()\r\n        {\r\n            MutationClassToTest = new VedaVersumMutation(_eventsSenderMock.Object, _dataAccessMock.Object);\r\n        }\r\n\r\n        &#x5B;Test]\r\n        public async Task ShouldCallInsertNewCardDataAccessMethodOnCardCreate()\r\n        {\r\n            \/\/ Arrange. Mocking dataAccess behavior\r\n            _dataAccessMock.Setup(d=&gt; d.InsertNewCard(It.IsAny&lt;string&gt;(), It.IsAny&lt;string&gt;(), null, It.IsAny&lt;User&gt;()))\r\n                .ReturnsAsync(ExpectedCardData);\r\n\r\n            \/\/ Action\r\n            var resultCard = await MutationClassToTest.CardAction(\r\n                VedaVersumCardAction.Create, \r\n                ExpectedCardData.Title,\r\n                ExpectedCardData.Content,\r\n                relatedCards: null,\r\n                cardId: null,\r\n                TestUser);\r\n\r\n            \/\/ Assert\r\n            Assert.NotNull(resultCard);\r\n            Assert.IsNotEmpty(resultCard.Id);\r\n            Assert.AreEqual(ExpectedCardData.Title, resultCard.Title);\r\n            \/\/ Check if data accessor&#039;s InsertNewCard method has been called only once and with appropriate parameters\r\n            _dataAccessMock.Verify(d =&gt; d.InsertNewCard(\r\n                ExpectedCardData.Title, \r\n                ExpectedCardData.Content,\r\n                null,\r\n                It.IsAny&lt;User&gt;()\r\n                ), Times.Once);\r\n            \/\/ Check if EventSender&#039;s method SendAsync has been called only once\r\n            _eventsSenderMock.Verify(s =&gt; s.SendAsync&lt;string, CardActionMessage&gt;(\r\n                nameof(VedaVersumSubscription.CardChanged), \r\n                It.Is&lt;CardActionMessage&gt;(m =&gt; m.Action == VedaVersumCardAction.Create),\r\n                It.IsAny&lt;CancellationToken&gt;()),\r\n                Times.Once);\r\n        }\r\n\r\n        &#x5B;TestCase(VedaVersumCardAction.Update)]\r\n        &#x5B;TestCase(VedaVersumCardAction.Delete)]\r\n        public void ShouldThrowExceptionIfActionIsNotCreateAndCardIdIsNull(VedaVersumCardAction action)\r\n        {\r\n            Assert.ThrowsAsync&lt;ArgumentNullException&gt;(() =&gt; MutationClassToTest.CardAction(\r\n                action, \r\n                ExpectedCardData.Title,\r\n                ExpectedCardData.Content,\r\n                relatedCards: null,\r\n                cardId: null,\r\n                TestUser));\r\n        }\r\n\r\n        \/\/ Other test scenarios are omitted\r\n    }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Das vollst\u00e4ndige Testprojekt finden Sie im <a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/tree\/main\/VedaVersum.Test\">Repository<\/a>. Jetzt k\u00f6nnen wir diese Tests mit dem Test Explorer in VS 2022 oder mit der Befehlszeile &#8222;dotnet test&#8220; starten und sicherstellen, dass alle Tests gr\u00fcn sind.<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Fazit<\/h2>\r\n\r\n\r\n\r\n<p>Wir haben heute mit jeder Menge Sachen gespielt. Wir haben die GraphQL-API implementiert, wir haben MongoDB als Datenpersistenz zu unserer API hinzugef\u00fcgt. Wir haben uns einige coole Funktionen von GraphQL angesehen, wie Resolver und Data Loader. Schlie\u00dflich haben wir einige Unit-Tests erstellt. Damit haben wir die Runde des typischen Backend-Entwicklungslebenszyklus abgeschlossen. Ich hoffe, Ihr habt unsere Reise genossen. Im n\u00e4chsten Teil unserer Serie werden wir die UI-Anwendung mit React erstellen.<\/p>\r\n<p>Bis bald!<\/p>\r\n<p>&nbsp;<\/p>\r\n\r\n\r\n","protected":false},"author":66,"featured_media":0,"template":"","tags":[887,888,236,890,514,112,617],"class_list":["post-15147","blog","type-blog","status-publish","hentry","tag-net-de","tag-api-de","tag-csharp","tag-graphql-de","tag-react","tag-software-entwickler","tag-ux-engineering"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/15147","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog"}],"about":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/types\/blog"}],"author":[{"embeddable":true,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/users\/66"}],"version-history":[{"count":7,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/15147\/revisions"}],"predecessor-version":[{"id":15215,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/15147\/revisions\/15215"}],"wp:attachment":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/media?parent=15147"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/tags?post=15147"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}