Laden...

[Review] Best Practices/Architektur anhand von Bespielprojekt (WebApi + OData + EF + SPA (Aurelia))

Erstellt von t0ms3n vor 7 Jahren Letzter Beitrag vor 7 Jahren 4.899 Views
T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 7 Jahren
[Review] Best Practices/Architektur anhand von Bespielprojekt (WebApi + OData + EF + SPA (Aurelia))

Hallo zusammen,

da gefühlt in letzter Zeit doch diverse Fragen (auch von mir z.B. Entity Framework-Klassen und Darstellung in der View) bzgl. APIs (und ähnliches) gekommen sind, habe ich ein Projekt zu erstellen, welches die Themen WebApi/OData auffasst.
In vielen der Antworten wird ja entsprechend auf WebApi / REST / OData hingewiesen.
Bei den allermeisten Beispielen die ich selbst dazu bisher jedoch gefunden und bearbeitet habe, hörten diese sehr oft nach den Grundzügen auf und basierten auf den denkbar einfachsten Datenmodellen.

Das eigentliche Projekt habe ich auf GitHub bereitgestellt, sodass interessierte daran mitwirken können. (vielleicht kann es noch jemand Anhängen, der nicht an die 256KB Grenze gebunden ist!?).

Zum Projekt selbst sei gesagt:

  • Das Thema ist eine minimale Applikationsverwaltung bestehend aus Applikation, Applikationsversionen und Applikationsverwaltern.
  • Es fehlt noch sehr an Kommentaren, diese werde ich morgen Abend versuchen nachzubessern.
  • Gerade der SPA Teil ist doch eher Neuland, hier gibt es sicherlich viel Optimierungspotential. Ich bin mir bewusst, dass dort nicht jede Funktion komplett ausprogrammiert ist. Diese soll hauptsächlich jedoch auch nur dazu dienen, die WebApi zu konsumieren.

Offene Fragen:

  • Im OData Bereich ist es mir weiterhin rätselhaft, wie es möglich ist, dort nicht die Entitäten der Datenbank zu nutzen und trotzdem die OData Funktionalitäten zu erhalten
  • Ich habe versucht die Verarbeitung der Requests in die BLL auszulagern. Wie handhabt ihr dies bei z.B. Patch? Nutzt ihr nicht das Delta zum Patchen oder übergebt ihr dieses in den BLL hinein?
  • Wie wird in z.B. der SPA ein Batching umgesetzt, sodass erst beim Speichern der Applikation auch hinzugefügte/entfernte Manager verarbeitet werden?

Schön fände ich es, wenn dort sogar Verbesserungen/Best Practices eingearbeitet werden, sodass man dies hin und wieder als Orientierung nutzen kann.

Auf jeden Fall freue ich mich über jedes aufgedeckte Pitfall!

Ich bin mir nicht sicher, ob CodeReview der richtige Ort ist...irgendwie aber schon. Web-Entwicklung selbst fand ich auch nicht so richtig passend. Alternativ könnte es auch unter Projekte oder Smalltalk laufen 😃.

2.207 Beiträge seit 2011
vor 7 Jahren

Hallo t0ms3n,

was mir in der API auffällt: Wieso gibst du beim erfolgreichen POST ein "NoContent" zurück? Besser wäre, die eingetragene Resource oder einen Link dazu, besser sogar beides (Header/Body) an den Client zurück zu schicken. So kann er entscheiden, was er tun will: Resource anzeigen, zur Resource navigieren oder eben gar nix. --> "CreatedAtRouteResult" oder "Created(...)" heisst das glaube ich.

Beim PUT kann man auch drüber reden, wobei der Client dort ja schon die aktualisierte Resource hat. Trotzdem sieht man dort manchmal, dass auch die aktualisierte Resource wieder retourniert wird.

Wieso hast du weiter geschrieben, dass ASP.NET Core installiert sein soll? Core unterstützt derzeit noch kein OData... "normales" ASP.NET sollte da reichen in dem Fall 😉

Das ist mir spontan aufgefallen...

Gruss

Coffeebean

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 7 Jahren

Hallo Coffeebean,

POST -> Wo? Diese sollten alle ein Created mit der Entity zurückgeben. Aber stimmt die Location war noch etwas.

Du sagst auch im Body. Also nicht die erstellte Entity zurückgeben, sondern einfach nur die Location?
Im Fall des ApplicationController also in der Richtung:


            string location = $"/odata/Applications({application.Id})";
            // Mit Entity
            return Created(location, application);
            // Oder Location auch im Body
            return Created(location, location);

ASP.NET Core ist das SPA Projekt. Das war eigentlich nicht nötig, ich hatte initial allerdings die API mit EF Core und ASP.NET Core begonnen. Dies hatte bis zu einem gewissen Punkt auch gut funktioniert 😃.

2.207 Beiträge seit 2011
vor 7 Jahren

Hallo t0ms3n,

POST -> Wo? Naja, du hast ja POST-Requests im Controller 😉 Also im Controller.

Du sagst auch im Body. Also nicht die erstellte Entity zurückgeben, sondern einfach nur die Location?

Du sagst auch im Body.

Ich würde beides zurückgeben. Die Entity im Body und im Header den Link zur Resource. Damit der Client selber entscheiden kann, was er machen will.

return Created(addedEntity);

Gibt im Header

http://localhost:3153/odata/[Controller](5)

mit und im Body die Resource. Bekommt man bei OData praktisch out-of-the-box mit. Noch ein Argument mehr für OData 😃

Gruss

Coffeebean

16.806 Beiträge seit 2008
vor 7 Jahren

Beispiel ist schon gut; würde aber schon einige Dinge anders machen:

  • Bin kein Freund des ModelState. Validierung gehört in den Service und nicht in die Action.
  • Du gibst Entitäten durch OData raus. Das funktioniert in diesem Fall; aber nicht in der Realität.
    Stell Dir vor Du hast ne Usertabelle in das Passwort steht. Willst Du das Feld direkt aus der API raus geben? Eher nicht.
  • Wenn ich Projekte wie "Common" oder "Helper" oder "Util" sehe dann stellen sich mir die Haare auf. Jeder Code hat einen Zweck und Common gibt es nicht. Auch DAL hat meines Erachtens nichts in der Projektorganisation (Namespace) zu suchen.

... alles weitere kann ich nicht mehr ansehen, da Du offensichtlich mitten im Beitrag das Repository gelöscht hast und ich jetzt einen Fehler 404 erhalte 😉

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 7 Jahren

... alles weitere kann ich nicht mehr ansehen, da Du offensichtlich mitten im Beitrag das Repository gelöscht hast und ich jetzt einen Fehler 404 erhalte 😉

Sorry, sollte nun wieder da sein, wollte etwas am initalen Commit anpassen. Da ich kein Git Profi bin erschien mir dies so am einfachsten 😃.

Du gibst Entitäten durch OData raus.

Genau das würde ich gerne vermeiden, aber dabei habe ich selbst noch nicht eine für kleinere Projekte aufwandsgerechte Möglichkeit gefunden (dazu auch die offene Frage von mir).

Wenn ich Projekte wie "Common" oder "Helper" oder "Util...

Und diese noch granularer aufteilen? Ich sehe diese immer also Projekte, welche in allen Teilen der Applikation verwendet werden.

Danke für das Feedback bis hier her!

16.806 Beiträge seit 2008
vor 7 Jahren

Das ist ein trugschluss; es wird nichts in allen Teilen verwendet. Das Argument kommt immer wieder (hatte ich neulich auf Twitter), aber das ist doch gar nicht so.
Wenn es so ist, dann ist in meinen Augen das Design völlig falsch.

Beispiel DI:
Der DAL muss nichts einem DI bekannt machen. Was auch?
Der DAL gibt nur bekannt: das sind meine DAL Interfaces und das sind potentielle Implementierungen.

zB

  • MyProject.Database.Contracts
    -- IUnitOfWork
    -- IPersonRepository
  • MyProject.Database.MsSql
    -- SqlUnitOfWOrk
    -- PersonRepository
  • MyProject.Database.MySql
    -- MySqlUnitOfWOrk
    -- PersonRepository
  • MyProject.Database.NoSql
    -- NoSqlUnitOfWOrk
    -- PersonRepository

//SideKick
Manche - vor allem (Ex-)Leute aus Java - haben Projekte mit Interfaces (Contracts) und Impl-Namespaces.
Die heissen dann:

  • MyProject.Database.Impl

In meinen Augen eine volle Katastrophe.
Man erkennt dem Impl gar nicht an, was es ist - und zusätzlich verbaut man sich seine Modularität. Angenommen hinter Impl steckt Mssql. Wie heisst dann die Implementierung von MySql? Impl2...?
// SideKick

Jedenfalls mein gezeigtes Beispiel hier ist in meinen die saubere Trennung von Contracts und Implementierung.
DI liegt nun in der Verantwortung der laufenden Anwendung und ist kein gemeinsames Gut!

Um die Wiederverwendbarkeit zu erhöhen kann man natürlich ein MyProject.Register Projekt haben, an dem das dann zentral Konfiguriert wird.
Aber DI ist kein Common!

Übrigens sind bei Dir teilweise Interfaces und Implementierung im gleichen Projekt.
Macht i.d.R. wenig sinn.

Änlich Deine Modelle. Warum sind diese in Common?
Du baust Dir hier einen Monolithen. Kommt in Dein Common dann auch noch UI-spezifisches?
Dann referenzierst Du nämlich in Deinem Business-Code eine DLL, die die UI kennt, was nicht so dolle ist.

Les Dir mal verschiedene Sachen zum Thema Project Organizing durch.
Da ist auch vieles bei Deinem Code, was dann noch gescheit ausgelagert gehört. zB mischt Du auch die Position der Exception Klassen.
Eine EntityException gehört zum DAL und nicht zum BLL. Und vieles ähnliche.

Ich hör auch schon den ersten Schreien: bla bla bla zu viel Aufwand für ein kleines Projekt.
Ja, wenn man Nackt anfängt, dann ist das erstmal Aufwand. Den machste aber ein Mal und zeitgleich kannste die einzelnen Elemente viel einfacher für andere Dinge wieder verwenden.
Wie oft hab ich schon gesehen, dass die Leute aus einer Common Code raus kopiert und woanders übernommen haben, weil sie eben nicht alles von Common wollten.
Das ist nachher genau das, was Probleme macht und Geld und damit seine Wirtschaftlichkeit kostet. Da könnt ich an die Decke gehen, bei solch einer Faulheit.

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 7 Jahren

Beispiel DI:
Der DAL muss nichts einem DI bekannt machen. Was auch?
Der DAL gibt nur bekannt: das sind meine DAL Interfaces und das sind potentielle Implementierungen.

Mein gedanklicher Fehler liegt darin, dass für mich der Layer entscheidet (weiß) welche Implementierung er wie benötigt. Und die Applikation eben nur sagen muss: "Okay, ich möchte diesen BLL nutzen.".

Im Sinn der Composite Root wäre hier die Verantwortliche Applikation ja die WebApi. Durch die Nutzung von MEF wollte ich eigentlich erreichen, das die WebApi keine Referenz auf z.B. den DAL benötigt.
Wie sähe denn das Register-Projekt aus? Einfach alles referenzieren und entsprechend initialisieren klingt mir zu einfach ^^. Das würde allerdings helfen, dass die WebApi selbst keine Kenntniss vom DAL hat.

Übrigens sind bei Dir teilweise Interfaces und Implementierung im gleichen Projekt.
Macht i.d.R. wenig sinn.

Hmm, ja vermutlich merkt man diesen Nutzen erst dann, wenn man unterschiedliche Implementierungen in unterschiedlichen Projekten für die Interfaces hat.

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 7 Jahren

Ich habe das Sample nun einmal überholt und einige der genannten Punkte einfließen lassen. Die SPA ist aktuell eher weniger lauffähig. Diese ist noch auf die DAL Entities ausgelegt und wirdim Laufe der Woche angepasst.

Grundsätzlich sollten folgende Punkte eingeflossen sein:

  • Die API liefert nun nicht mehr die DAL-Entities aus. Stattdessen werden die QueryOptions soweit nötig ausgewertet und entsprechend an den BLL weitergereicht. Die Projektion zwischen DAL und BLL findet dort dann unter Verwendung von AutoMapper statt.
  • Die Validation ist in den BLL übergegangen
  • Die Interfaces/Modelles sind sofern sinnvoll in eigene Projekte (.Contracts) ausgelagert worden