Im letzten Teil des Beitrags haben wir die Business Logik unseres Aufgaben Tools programmiert. In diesem Teil geht es darum, unsere Anwendung zum L...
For further actions, you may consider blocking this person and/or reporting abuse
Das ist ein wirklich schöner Artikel. Habe etwas lernen können!
Doch eine Sache verstehe ich nicht.
Wofür brauchen wir die Provider im Core-Modul?
@NgModule({
// ...
providers: [
AddToDoUseCase,
DeleteToDoUseCase,
EditToDoUseCase,
ShowToDoListUseCase,
]
})
export class CoreModule {
}
Das ginge doch auch ohne die Use-Cases. Die werden doch ohnehin durch "provideIn="root" zur Verfügung gestellt. Es sollte also auch ohne das funktionieren...
Danke für das Lob und den Hinweis 🙂 Ich dachte immer man müsste die immer im Modul registrieren aber tatsächlich reicht das providedIn schon aus, danke! Ich update den Code und den Artikel.
Super!
Was mich auch noch interessieren würde:
medium.com/intive-developers/appro...
Doch den Grund verstehe ich nicht. Es müsste doch gerade gewollt sein das Core-Module nicht zu haben, damit so wenig Framework wie möglich den Core "verschmutzt".
providers: [
{provide: ShowToDoListPresenter, useClass: TodoListPresenter}
]
z.B. wirklich in das PresentationModule (und nicht direkt per provideIn in den ShowToDoListPresenter), weil dort keine konkrete Referenz hingehört. Verstehe ich das richtig?
Das sehe ich vollkommen genau so wie du. Deswegen hab ich gestern das Core-Module auch schon rausgeworfen.
Ich hatte es ursprünglich so, wie ich es auch in Teil 1 geschrieben habe, dass ich die Services manuell verdrahtet habe. Das geschah im Core Module. Dadurch, dass die UseCases aber im Nachhinein eh mit
@Injectable({providedIn: 'root'})
dekoriert sind, ist das Modul natürlich überflüssig. Ich hab das gestern schon im Code und im Post angepasst :-)Zu dem verdrahten der Presentern ist mir leider noch kein schöner Weg eingefallen
okay - dann ist alles klar. Ich war nur so verwirrt, weil ich diese Art von Implementierung zwei Mal gefunden habe. Einmal hier und noch mal in dem englischen Artikel. Zwei Mal ein Core Modul... aber hatte wohl in beiden Fällen den gleichen Ursprung.
Noch eine andere Frage: Die Komponenten greifen bei dir direkt auf einen konkreten UseCase zu (es gibt keine abstrakten Klassen dafür (i.e. "interfaces" bei clean architecture))
D.h., dass deine Komponente (das wäre der "Controller") direkt auf die Use cases zugreift ohne Interface dazwischen, wie in dem Buch. War das Absicht oder einfach Pragmatismus? (Siehe Foto aus dem Buch "Clean Architecture") Foto: dev-to-uploads.s3.amazonaws.com/i/...
Den UseCase bzw. die boundaries noch mal zu abstrahieren ist überflüssig.
Der UseCase befindet sich ja im Domain Layer, somit kennt der Controller ja den UseCase, jedoch nicht anders herum.
Das InputBoundary ist in meinem Beispiel das
TRequest
Objekt im UseCase und das OutputBoundary wird durch denTPresenter
im UseCase vorgegeben 🙂github.com/devtronic/clean-archite...
Hallo, wenn der Controller alles im Core-Layer kennt, warum der Aufwand mit eines UseCase Interfaces und nicht simpler Funktionsaufruf und Aufbereitung des Rückgabewertes?
Hallo, ich kann dir gerade nicht ganz folgen. Poste mal ein Stück Code, wie du es ohne das Interface machen würdest.
Das Interface selber dient u.A. dafür, dass alle Use Cases nach dem selben Schema aufgebaut sind (TRequest, TPresenter und execute Methode).
Danke für die schnelle Antwort. Also statt
gibt's
dann
Vorteile:
ShowToDoListPresenter<any>
Nachteile:
Der UseCase ist ja dafür gedacht, dass man Geschäftslogik von den Implementierungsdetails trennt.
Um beim Beispiel "Todo Liste" zu bleiben, passen wir mal die Definition des Anwendungsfalls an:
Jetzt hast du zwei Möglichkeiten:
showLoadingIndicator()
undhideLoadingIndicator
odertoggleLoadingIndicator(isLoading: bool);
hat, und implementierst diesen in deiner Anwendung.Beides hat Vor- und Nachteile:
Hier muss man aber sagen, dass der Vorteil vom Presenter, nämlich dass das UI nicht die Geschäftslogik zerstören kann, den Nachteil bei weitem wieder ausgleicht.
Daneben hat der Presenter auch noch eine ganz Andere Aufgabe, welche leider aufgrund des Umfangs der Beispiele nicht von mir erklärt wurde:
Aktuell wird in dem Presenter nur das todos-Argument auf das todos-Feld im ViewModel zugewiesen. In der Regel ist es aber so, dass du die Daten, welche vom UseCase kommen, noch einmal aufbereitest, sodass im ViewModel wirklich nur Flache Daten liegen, welche direkt vom Template verwendet werden können. Als Beispiel: willst du eine Liste von Produkten mit Preisen anzeigen, hätte jedes Produkt im ViewModel bereits eine Property
displayPrice
welche den formatierten Wert inkl. Währung hält.Warum? Aus Gründen der Testbarkeit. Wir wissen alle wie hart es sein kann, UI zu testen. Wenn wir jedoch dem Template alles fertig geben, müssen wir, ganz naiv gesprochen, nur gegen das ViewModel testen um zu sehen, ob die Ausgabe passt.
Weitere Infos dazu findest du auch hier: Humble Object
Ich hoffe ich konnte es verständlich erklären 🙂
await
aufrufst. Z. B. beiAufruf
Im Prinzip das selbe wie
1) Der Use Case hat ja nur eine Aufgabe: er kümmert sich um die Geschäftslogik für diesen einen Anwendungsfall. Da die Geschäftslogik geschäftsübergreifend ist und nicht für diese eine Anwendung gilt, hast du gar keine Möglichkeit die Teilaufgaben sinnvoll auszulagern. (Du kannst und solltest natürlich das Integration Operation Segregation Principle (IOSP) anwenden und die einzelnen Aufgaben in Sub-Methoden unterteilen)
2) In diesem Fall sollte man anfangen zu verallgemeinern. Nehmen wir an, du hast noch 10 weitere Listen.
Dann kannst du ja einfach eine Komponente für alle Liste erzeugen, welche den UseCase "ShowList" hat. Der UseCase erwartet dann im Konstruktor ein kompatibles Repository mit einer allgemeinen Methode, welche die anzuzeigenden zurück liefert. (Kann ja z.B. in Form eines generischen Interface sein).
Der Presenter ist wie gewohnt bei der Komponente implementiert. Wie die Daten dann in eine darstellbare Form gelangen ist wieder ein anderes Thema (wobei bei einer Liste im Grunde ja ein String-Array genügt, welches die Propertynamen der anzuzeigenden Eigenschaften enthält).
Anders sieht es natürlich aus, wenn bei wirklich jedem Use Case ein Ladeindikator angezeigt werden soll.
In dem Fall würde ich im core einen abstrakten "BusyService" bauen, welcher die beiden Methoden show & hide des Presenters enthält. Der Service kann dann im UseCase injected und angesprochen werden werden.
Bei der implementierung würde ich vermutlich auf ein BehaviorSubject zurückgreifen, welches im äußersten Template per async-Pipe verwendet wird. In der show Methode würde ich den Wert inkrementieren und beim hide dekrementieren. Das hat den Vorteil, dass verschaltete show & hide calls sauber funktionieren. (Im Template dann einfach auf > 0 prüfen).
3) Das Thema Routing habe ich bisher so gelöst, dass ich einen abstrakten "RouterService" im core/service abgelegt habe. Darin ist dann pro Seite eine eigene Methode. z.B. showDashboard() oder showTodo(todoId).
Implementiert wird das dann im infrastructure-Modul. (ähnlich wie beim Interaction service)
4) Quasi als Vorsorge 😁 Es könnte ja durchaus sein... Nein es wird definitiv so sein, dass irgendwann ein hübscher asynchroner Dialog den synchronen
prompt
Dialog ersetzt.Wir wissen ja: Die einzige Konstante ist die Veränderung
ok danke. Wie sieht es denn aus, wenn ich drei Views auf die Todos habe?, Z.B. Menü-Button "Revert Todos" falls mehr als 0 vorhanden,
TodoListComponent
und View Anzahl Todos im Footer. Ich möchte ja jetzt nicht dreimal die Daten vom Server laden, noch für jede Komponente auf der Seite raten, wurdepresenter.reset
undusecase.execute
schon aufgerufen. Ist ein Usecase jetzt Todo anzeigen oder Seite anzeigen?Du darfst natürlich auch mit Subscriptions arbeiten. (Um in der Geschäftslogik nicht von RxJs abzuhängen, würde ich für die verwendeten RxJs-Komponenten minimale, kompatible Interfaces bauen. Z.B. für die (Un-) Subscribe Methoden.)
Im Repository fügst du dann ein Property
length$
hinzu, welches die Anzahl der Elemente im Repository emittiert und im UseCase subscribed wird. (Ebenso kannst du auch ein Propertyelements$
verwenden, welches die aktuellen Elemente emittiert. )Im Presenter fügst du dann noch eine Methode
displayCount(count: number)
hinzu, welches vom UseCase bei einer Änderung vonlength$
gecalled wird.Somit rufst du die Daten nicht doppelt und dreifach ab.
Ein UseCase stellt ein fachliches Ziel ("business goal") dar. Siehe auch hier: de.wikipedia.org/wiki/Anwendungsfall
Das kann sowohl Todo anzeigen als auch das anzeigen der Liste der Todos sein.
Zwei richtig gute Beiträge! Für mich als „Angular Newbie“ sehr verständlich zu lesen.