Lage
Ich habe heute Abend eine Antwort auf eine Frage zu StackOverflow gegeben.
Die Frage:
Die Bearbeitung eines vorhandenen Objekts sollte in der Repository-Ebene oder im Service erfolgen.
Zum Beispiel, wenn ich einen Benutzer habe, der Schulden hat. Ich möchte seine Schulden ändern. Soll ich es im UserRepository oder im Service zum Beispiel BuyingService tun, indem ich ein Objekt abrufe, es bearbeite und speichere?
Meine Antwort:
Sie sollten die Verantwortung für die Mutation eines Objekts in dasselbe Objekt übernehmen und das Repository zum Abrufen dieses Objekts verwenden.
Beispielsituation:
class User {
private int debt; // debt in cents
private string name;
// getters
public void makePayment(int cents){
debt -= cents;
}
}
class UserRepository {
public User GetUserByName(string name){
// Get appropriate user from database
}
}
Ein Kommentar, den ich erhalten habe:
Geschäftslogik sollte wirklich in einem Service sein. Nicht in einem Modell.
Was sagt das Internet?
Das hat mich auf die Suche gebracht, da ich nie wirklich (bewusst) eine Service-Schicht verwendet habe. Ich habe angefangen, das Service-Layer-Muster und das Muster der Arbeitseinheit zu lesen, kann aber noch nicht sagen, dass ich davon überzeugt bin, dass ein Service-Layer verwendet werden muss.
Nehmen Sie zum Beispiel diesen Artikel von Martin Fowler über das Antimuster eines anämischen Domänenmodells:
Es gibt Objekte, von denen viele nach den Substantiven im Domänenbereich benannt sind, und diese Objekte sind mit den reichen Beziehungen und Strukturen verbunden, über die echte Domänenmodelle verfügen. Der Haken kommt, wenn man sich das Verhalten ansieht und merkt, dass es an diesen Objekten kaum ein Verhalten gibt, so dass sie kaum mehr sind als Säcke voller Getter und Setter. Tatsächlich enthalten diese Modelle häufig Entwurfsregeln, die besagen, dass den Domänenobjekten keine Domänenlogik hinzugefügt werden soll. Stattdessen gibt es eine Reihe von Dienstobjekten, die die gesamte Domänenlogik erfassen. Diese Dienste stehen über dem Domänenmodell und verwenden das Domänenmodell für Daten.
(...) Die Logik, die sich in einem Domänenobjekt befinden sollte, ist Domänenlogik - Validierungen, Berechnungen, Geschäftsregeln - wie auch immer Sie es nennen möchten.
Für mich schien das genau das zu sein, worum es in der Situation ging: Ich befürwortete die Manipulation der Daten eines Objekts, indem ich Methoden innerhalb dieser Klasse einführte, die genau das tun. Mir ist jedoch klar, dass dies so oder so gegeben sein sollte, und es hat wahrscheinlich mehr damit zu tun, wie diese Methoden aufgerufen werden (unter Verwendung eines Repositorys).
Ich hatte auch das Gefühl, dass in diesem Artikel (siehe unten) eine Serviceebene eher als Fassade betrachtet wird , die die Arbeit an das zugrunde liegende Modell delegiert, als als eine tatsächliche arbeitsintensive Ebene.
Application Layer [sein Name für Service Layer]: Definiert die Aufgaben, die die Software ausführen soll, und weist die ausdrucksstarken Domänenobjekte an, Probleme zu lösen. Die Aufgaben, für die diese Schicht zuständig ist, sind für das Unternehmen von Bedeutung oder für die Interaktion mit den Anwendungsschichten anderer Systeme erforderlich. Diese Schicht wird dünn gehalten. Es enthält keine Geschäftsregeln oder Kenntnisse, sondern koordiniert nur Aufgaben und delegiert die Arbeit an Kollaborationen von Domänenobjekten in der nächsten Ebene. Es gibt keinen Status, der die Geschäftssituation widerspiegelt, aber es gibt einen Status, der den Fortschritt einer Aufgabe für den Benutzer oder das Programm widerspiegelt.
Welche verstärkt hier :
Service-Schnittstellen. Dienste stellen eine Dienstschnittstelle bereit, an die alle eingehenden Nachrichten gesendet werden. Sie können sich eine Serviceschnittstelle als Fassade vorstellen, die die in der Anwendung implementierte Geschäftslogik (normalerweise die Logik in der Geschäftsschicht) potenziellen Verbrauchern zugänglich macht.
Und hier :
Die Serviceschicht sollte keine Anwendung oder Geschäftslogik enthalten und sich in erster Linie auf einige wenige Aspekte konzentrieren. Es sollte Business Layer-Aufrufe einschließen, Ihre Domain in eine gemeinsame Sprache übersetzen, die Ihre Kunden verstehen können, und das Kommunikationsmedium zwischen Server und anforderndem Client handhaben.
Dies ist ein schwerwiegender Gegensatz zu anderen Ressourcen , die sich mit der Service-Schicht befassen:
Die Serviceschicht sollte aus Klassen mit Methoden bestehen, die Arbeitseinheiten mit Aktionen sind, die zu derselben Transaktion gehören.
Oder die zweite Antwort auf eine Frage, die ich bereits verlinkt habe:
Irgendwann möchte Ihre Anwendung eine Geschäftslogik. Möglicherweise möchten Sie die Eingabe auch validieren, um sicherzustellen, dass keine bösen oder fehlerhaften Daten angefordert werden. Diese Logik gehört in Ihre Service-Schicht.
"Lösung"?
Nach den Richtlinien in dieser Antwort habe ich den folgenden Ansatz gefunden, bei dem eine Service-Schicht verwendet wird:
class UserController : Controller {
private UserService _userService;
public UserController(UserService userService){
_userService = userService;
}
public ActionResult MakeHimPay(string username, int amount) {
_userService.MakeHimPay(username, amount);
return RedirectToAction("ShowUserOverview");
}
public ActionResult ShowUserOverview() {
return View();
}
}
class UserService {
private IUserRepository _userRepository;
public UserService(IUserRepository userRepository) {
_userRepository = userRepository;
}
public void MakeHimPay(username, amount) {
_userRepository.GetUserByName(username).makePayment(amount);
}
}
class UserRepository {
public User GetUserByName(string name){
// Get appropriate user from database
}
}
class User {
private int debt; // debt in cents
private string name;
// getters
public void makePayment(int cents){
debt -= cents;
}
}
Fazit
Insgesamt hat sich hier nicht viel geändert: Der Code vom Controller ist in die Service-Schicht übergegangen (was eine gute Sache ist, also gibt es einen Vorteil bei diesem Ansatz). Dies scheint jedoch nichts mit meiner ursprünglichen Antwort zu tun zu haben.
Mir ist klar, dass Entwurfsmuster Richtlinien sind und keine in Stein gemeißelten Regeln, die wann immer möglich umgesetzt werden sollen. Dennoch habe ich keine definitive Erklärung für die Serviceschicht gefunden und wie sie zu betrachten ist.
Ist es ein Mittel, einfach die Logik aus dem Controller zu extrahieren und stattdessen in einen Service zu integrieren?
Soll es einen Vertrag zwischen dem Controller und der Domain bilden?
Sollte es eine Schicht zwischen der Domain und der Service-Schicht geben?
Und zu guter Letzt: Nach dem Originalkommentar
Geschäftslogik sollte wirklich in einem Service sein. Nicht in einem Modell.
Ist das richtig?
- Wie würde ich meine Geschäftslogik in einem Service anstelle des Modells einführen?
quelle
Antworten:
Um die Zuständigkeiten eines Dienstes zu definieren , müssen Sie zunächst definieren, was ein Dienst ist.
Service ist kein kanonischer oder allgemeiner Softwarebegriff. Tatsächlich
Service
ähnelt das Suffix eines Klassennamens dem vielfach bösartigen Manager : Es sagt fast nichts darüber aus, was das Objekt tatsächlich tut .In Wirklichkeit sollte ein Service sehr architekturspezifisch sein:
In einer traditionellen Schichtarchitektur ist Service buchstäblich gleichbedeutend mit Business Logic Layer . Es ist die Ebene zwischen Benutzeroberfläche und Daten. Daher werden alle Geschäftsregeln in Dienste einbezogen. Die Datenschicht sollte nur grundlegende CRUD-Operationen verstehen, und die UI-Schicht sollte sich nur mit der Zuordnung von Präsentations-DTOs zu und von den Geschäftsobjekten befassen.
In einer verteilten RPC-Architektur (SOAP, UDDI, BPEL usw.) ist der Dienst die logische Version eines physischen Endpunkts . Es handelt sich im Wesentlichen um eine Sammlung von Vorgängen, die der Betreuer als öffentliche API bereitstellen möchte. In verschiedenen Leitfäden für bewährte Verfahren wird erläutert, dass ein Service- Vorgang tatsächlich ein Vorgang auf Unternehmensebene und nicht CRUD sein sollte, und ich stimme dem eher zu.
Da jedoch das Routing alles kann ernsthaft Leistung durch einen tatsächlichen Remote - Service verletzt, ist es normalerweise am besten nicht um diese Dienste zu haben tatsächlich die Business - Logik selbst umzusetzen; Stattdessen sollten sie eine "interne" Gruppe von Geschäftsobjekten umschließen. Ein einzelner Service kann ein oder mehrere Geschäftsobjekte umfassen.
In einer MVP / MVC / MVVM / MV * -Architektur sind überhaupt keine Dienste vorhanden. In diesem Fall bezieht sich der Begriff auf ein beliebiges generisches Objekt, das in einen Controller oder ein Ansichtsmodell eingefügt werden kann. Die Geschäftslogik ist in Ihrem Modell . Wenn Sie "Serviceobjekte" erstellen möchten, um komplizierte Vorgänge zu orchestrieren, wird dies als Implementierungsdetail angesehen. Leider implementieren viele Leute MVC wie folgt, aber es wird als Antimuster ( anämisches Domänenmodell ) angesehen, da das Modell selbst nichts tut, sondern nur eine Reihe von Eigenschaften für die Benutzeroberfläche.
Einige Leute denken fälschlicherweise, dass die Verwendung einer 100-Zeilen-Controller-Methode und das Einfügen all dieser Methoden in einen Dienst auf irgendeine Weise zu einer besseren Architektur führen. Das tut es wirklich nicht. Alles, was es tut, ist, eine weitere, wahrscheinlich unnötige Indirektionsebene hinzuzufügen. In der Praxis erledigt der Controller die Arbeit immer noch, nur über ein schlecht benanntes "Helfer" -Objekt. Ich kann Jimmy Bogards Präsentation " Wicked Domain Models" nur wärmstens empfehlen, um ein anschauliches Beispiel für die Umwandlung eines anämischen Domänenmodells in ein nützliches zu geben. Dabei müssen Sie sorgfältig prüfen, welche Modelle Sie verfügbar machen und welche Vorgänge im Geschäftskontext tatsächlich gültig sind .
Wenn Ihre Datenbank beispielsweise Aufträge enthält und Sie eine Spalte für den Gesamtbetrag haben, sollte es Ihrer Anwendung wahrscheinlich nicht gestattet sein, dieses Feld tatsächlich in einen beliebigen Wert zu ändern, da (a) es sich um den Verlauf handelt und (b) dies der Fall sein soll bestimmt durch das, was ist in der Reihenfolge sowie vielleicht einige andere zeitkritische Daten / Regeln. Das Erstellen eines Dienstes zum Verwalten von Bestellungen löst dieses Problem nicht unbedingt, da der Benutzercode weiterhin das tatsächliche Bestellobjekt erfassen und den Betrag darauf ändern kann. Stattdessen sollte die Bestellung selbst dafür verantwortlich sein, dass sie nur auf sichere und konsistente Weise geändert werden kann.
In DDD sind Dienste speziell für den Fall gedacht, dass Sie über einen Vorgang verfügen, der nicht ordnungsgemäß zu einem aggregierten Stamm gehört . Hier muss man vorsichtig sein, denn oft kann die Notwendigkeit eines Dienstes bedeuten, dass man nicht die richtigen Wurzeln verwendet hat. Angenommen, Sie verwenden einen Service, um Vorgänge über mehrere Roots hinweg zu koordinieren oder um Probleme zu lösen, die das Domänenmodell überhaupt nicht betreffen (z. B. das Schreiben von Informationen in eine BI / OLAP-Datenbank).
Ein bemerkenswerter Aspekt des DDD-Dienstes ist, dass er Transaktionsskripten verwenden darf . Wenn Sie an großen Anwendungen arbeiten, werden Sie wahrscheinlich auf Instanzen stoßen, in denen es einfacher ist, mit einer T-SQL- oder PL / SQL-Prozedur etwas zu erreichen, als mit dem Domänenmodell zu tun zu haben. Dies ist in Ordnung und gehört zu einem Dienst.
Dies ist eine radikale Abkehr von der Definition von Diensten in Schichtenarchitektur. Eine Service-Schicht kapselt Domänenobjekte. Ein DDD-Dienst kapselt alles, was nicht in den Domänenobjekten enthalten ist, und macht keinen Sinn.
In einer serviceorientierten Architektur wird ein Service als technische Autorität für eine Geschäftsfähigkeit betrachtet. Das heißt, es ist der ausschließliche Eigentümer einer bestimmten Teilmenge der Geschäftsdaten und nichts anderes darf diese Daten berühren - nicht einmal, um sie nur zu lesen .
Services sind notwendigerweise ein End-to-End-Angebot in einer SOA. Das heißt, ein Dienst ist weniger eine bestimmte Komponente als ein ganzer Stapel , und Ihre gesamte Anwendung (oder Ihr ganzes Unternehmen) besteht aus einer Reihe dieser Dienste, die nebeneinander ausgeführt werden und sich nur auf der Messaging- und der UI-Ebene überschneiden. Jeder Service verfügt über eigene Daten, eigene Geschäftsregeln und eine eigene Benutzeroberfläche. Sie müssen nicht miteinander orchestrieren, weil sie auf das Geschäft ausgerichtet sein sollen - und wie das Geschäft selbst hat jeder Dienst seine eigenen Verantwortlichkeiten und arbeitet mehr oder weniger unabhängig von den anderen.
Nach der SOA-Definition ist also jede Geschäftslogik irgendwo im Service enthalten, aber auch das gesamte System . Dienste in einer SOA können Komponenten und Endpunkte enthalten. Es ist jedoch ziemlich gefährlich, einen Code als Dienst zu bezeichnen, da dies in Konflikt mit der Bedeutung des ursprünglichen "S" steht.
Da SOA in der Regel sehr auf Messaging ausgerichtet ist, sind die Vorgänge, die Sie möglicherweise zuvor in einem Service gepackt haben, in der Regel in Handlern zusammengefasst , die Vielzahl ist jedoch unterschiedlich. Jeder Handler verarbeitet einen Nachrichtentyp und eine Operation. Es ist eine strikte Interpretation des Single-Responsibility-Prinzips , sorgt jedoch für eine hervorragende Wartbarkeit, da jede mögliche Operation in einer eigenen Klasse liegt. So müssen Sie nicht wirklich brauchen zentralisierte Business - Logik, da Befehle Geschäftsbetrieb eher als technische diejenigen darstellt.
Letztendlich wird es in jeder Architektur, die Sie wählen, eine Komponente oder Schicht geben, die den größten Teil der Geschäftslogik enthält. Wenn die Geschäftslogik überall verstreut ist, haben Sie nur Spaghetti-Code. Ob Sie diese Komponente als Service bezeichnen oder nicht und wie sie in Bezug auf Anzahl oder Größe der Vorgänge ausgelegt ist, hängt von Ihren architektonischen Zielen ab.
Es gibt keine richtigen oder falschen Antworten, sondern nur die für Ihre Situation zutreffenden.
quelle
Was Ihren Titel betrifft , halte ich die Frage nicht für sinnvoll. Das MVC-Modell besteht aus Daten und Geschäftslogik. Zu sagen, dass Logik im Service und nicht im Modell enthalten sein sollte, ist wie zu sagen: "Der Passagier sollte auf dem Sitz sitzen, nicht im Auto."
Andererseits ist der Begriff "Modell" ein überladener Begriff. Vielleicht haben Sie nicht MVC-Modell gemeint, sondern Modell im Sinne von Data Transfer Object (DTO). AKA eine Einheit. Davon spricht Martin Fowler.
Aus meiner Sicht spricht Martin Fowler von Dingen in einer idealen Welt. In der realen Welt von Hibernate und JPA (im Java-Land) sind die DTOs eine extrem undichte Abstraktion. Ich würde gerne meine Geschäftslogik in mein Unternehmen integrieren. Es würde die Dinge viel sauberer machen. Das Problem ist, dass diese Entitäten in einem verwalteten / zwischengespeicherten Zustand existieren können, der sehr schwer zu verstehen ist und Ihre Bemühungen ständig verhindert. Zusammenfassend meine Meinung: Martin Fowler empfiehlt den richtigen Weg, aber die ORMs hindern Sie daran.
Ich denke Bob Martin hat einen realistischeren Vorschlag und er gibt es in diesem Video, das nicht kostenlos ist . Er spricht davon, Ihre DTOs frei von Logik zu halten. Sie speichern die Daten einfach und übertragen sie auf eine andere Ebene, die viel objektorientierter ist und die DTOs nicht direkt verwendet. Dies verhindert, dass die undichte Abstraktion Sie beißt. Die Schicht mit den DTOs und die DTOs selbst sind nicht OO. Aber sobald Sie diese Schicht verlassen, werden Sie so OO, wie es Martin Fowler befürwortet.
Der Vorteil dieser Trennung besteht darin, dass die Persistenzschicht abstrahiert wird. Sie könnten von JPA zu JDBC (oder umgekehrt) wechseln, ohne dass sich die Geschäftslogik ändern müsste. Es hängt nur von den DTOs ab, es ist egal, wie diese DTOs bevölkert werden.
Um Themen leicht zu ändern, müssen Sie berücksichtigen, dass SQL-Datenbanken nicht objektorientiert sind. ORMs haben jedoch normalerweise eine Entität - das ist ein Objekt - pro Tabelle. Sie haben also von Anfang an einen Kampf verloren. Nach meiner Erfahrung können Sie die Entität niemals objektorientiert so darstellen, wie Sie es möchten.
Was " einen Dienst" angeht, wäre Bob Martin dagegen, eine Klasse namens zu haben
FooBarService
. Das ist nicht objektorientiert. Was macht ein Service? Alles, was damit zu tun hatFooBars
. Es kann auch beschriftet werdenFooBarUtils
. Ich denke, er würde eine Service-Schicht befürworten (ein besserer Name wäre die Business-Logik-Schicht), aber jede Klasse in dieser Schicht hätte einen aussagekräftigen Namen.quelle
Ich arbeite gerade auf der grünen Wiese und wir mussten erst gestern einige architektonische Entscheidungen treffen. Lustigerweise musste ich einige Kapitel von 'Patterns of Enterprise Application Architecture' noch einmal durchgehen.
Das haben wir uns ausgedacht:
Am Ende haben wir Folgendes:
Client -> Service -> Domain -> Daten
Wir können die Client-, Service- oder Datenschicht durch angemessenen Arbeitsaufwand ersetzen. Wenn Ihre Domänenlogik im Service enthalten war und Sie beschlossen haben, Ihre Service-Schicht zu ersetzen oder sogar zu entfernen, müssen Sie die gesamte Geschäftslogik an einen anderen Ort verschieben. Eine solche Anforderung ist selten, kann aber vorkommen.
Nachdem ich das alles gesagt habe, denke ich, dass dies ziemlich nahe an dem liegt, was Martin Fowler damit gemeint hat
Das folgende Diagramm veranschaulicht dies ziemlich gut:
http://martinfowler.com/eaaCatalog/serviceLayer.html
quelle
Dies ist eines der Dinge, die wirklich vom Anwendungsfall abhängen. Der Hauptzweck einer Serviceschicht ist die Konsolidierung der Geschäftslogik. Dies bedeutet, dass mehrere Controller denselben UserService.MakeHimPay () aufrufen können, ohne sich wirklich darum zu kümmern, wie die Zahlung ausgeführt wird. Was in dem Dienst vor sich geht, kann so einfach wie das Ändern einer Objekteigenschaft sein, oder es kann komplexe Logik ausführen, die sich mit anderen Diensten befasst (z. B. Aufrufen von Diensten von Drittanbietern, Aufrufen von Validierungslogik oder einfach nur Speichern von etwas in der Datenbank). )
Dies bedeutet nicht, dass Sie ALLE Logik von den Domänenobjekten entfernen müssen. Manchmal ist es einfach sinnvoller, wenn eine Methode für das Domänenobjekt einige Berechnungen für sich selbst ausführt. In Ihrem letzten Beispiel ist der Service eine redundante Schicht über dem Repository / Domain-Objekt. Es bietet einen schönen Puffer gegen Anforderungsänderungen, ist aber wirklich nicht notwendig. Wenn Sie glauben, einen Dienst zu benötigen, versuchen Sie, die einfache Logik "Eigenschaft X für Objekt Y ändern" anstelle des Domänenobjekts auszuführen. Die Logik für die Domänenklassen fällt in der Regel eher in den Bereich "Diesen Wert aus Feldern berechnen", als dass alle Felder über Getter / Setter verfügbar gemacht werden.
quelle
Der einfachste Weg zu veranschaulichen, warum Programmierer sich davor scheuen, Domänenlogik in die Domänenobjekte einzufügen, besteht darin, dass sie normalerweise mit der Situation konfrontiert werden, dass "Wo lege ich die Überprüfungslogik ab?" Nehmen Sie zum Beispiel dieses Domain-Objekt:
Wir haben also eine grundlegende Validierungslogik im Setter (kann nicht negativ sein). Das Problem ist, dass Sie diese Logik nicht wirklich wiederverwenden können. Irgendwo gibt es einen Bildschirm oder ein ViewModel oder einen Controller, der eine Validierung durchführen muss, bevor er die Änderung an dem Domänenobjekt festschreibt, da er den Benutzer informieren muss, bevor oder wenn er auf die Schaltfläche Speichern klickt, dass dies nicht möglich ist. und warum . Das Testen auf eine Ausnahme, wenn Sie den Setter aufrufen, ist ein hässlicher Hack, da Sie eigentlich die gesamte Validierung hätten durchführen müssen, bevor Sie die Transaktion überhaupt gestartet haben.
Aus diesem Grund verschieben die Benutzer die Validierungslogik auf eine Art von Dienst, z
MyEntityValidator
. Dann können die Entität und die aufrufende Logik einen Verweis auf den Validierungsdienst erhalten und ihn wiederverwenden.Wenn Sie dies nicht tun und die Validierungslogik dennoch wiederverwenden möchten, werden Sie sie in statische Methoden der Entitätsklasse einfügen:
Dies würde Ihr Domain-Modell weniger "anämisch" machen und die Überprüfungslogik neben der Eigenschaft beibehalten, was großartig ist, aber ich denke, niemand mag die statischen Methoden wirklich.
quelle
Ich denke, die Antwort ist klar, wenn Sie Martin Fowlers Artikel über das anämische Domänenmodell lesen .
Das Entfernen der Geschäftslogik, bei der es sich um die Domäne handelt, aus dem Domänenmodell bricht im Wesentlichen das objektorientierte Design.
Lassen Sie uns das grundlegendste objektorientierte Konzept betrachten: Ein Objekt kapselt Daten und Operationen. Das Schließen eines Kontos ist beispielsweise eine Operation, die ein Kontoobjekt für sich selbst ausführen sollte. Daher ist es keine objektorientierte Lösung, wenn eine Serviceschicht diese Operation ausführt. Es ist prozedural und das ist es, worauf sich Martin Fowler bezieht, wenn er über ein anämisches Domänenmodell spricht.
Wenn Sie das Konto von einer Serviceebene schließen lassen, anstatt das Kontoobjekt selbst schließen zu lassen, haben Sie kein echtes Kontoobjekt. Ihr Konto "Objekt" ist lediglich eine Datenstruktur. Was Sie am Ende haben, wie Martin Fowler vorschlägt, ist ein Bündel Taschen mit Gettern und Setzern.
quelle
Wie würden Sie Ihre Geschäftslogik in der Serviceschicht implementieren? Wenn Sie eine Zahlung von einem Benutzer ausführen, erstellen Sie eine Zahlung und ziehen nicht nur einen Wert von einer Immobilie ab.
Ihre Zahlungsmethode "Zahlung ausführen" muss einen Zahlungsdatensatz erstellen, die Schulden dieses Benutzers erhöhen und all dies in Ihren Repositorys beibehalten. Dies in einer Servicemethode zu tun, ist unglaublich einfach, und Sie können den gesamten Vorgang auch in eine Transaktion einschließen. Dasselbe in einem aggregierten Domänenmodell zu tun, ist viel problematischer.
quelle
Die tl; dr-Version:
Meine Erfahrungen und Meinungen besagen, dass Objekte mit Geschäftslogik Teil des Domänenmodells sein sollten. Das Datenmodell sollte wahrscheinlich überhaupt keine Logik haben. Die Dienste sollten wahrscheinlich beides miteinander verbinden und sich mit übergreifenden Belangen (Datenbanken, Protokollierung usw.) befassen. Die akzeptierte Antwort ist jedoch die praktischste.
Die längere Version, auf die andere anspielen, ist, dass das Wort "Modell" nicht eindeutig ist. Der Beitrag wechselt zwischen Datenmodell und Domänenmodell, als ob sie gleich wären, was ein sehr häufiger Fehler ist. Es kann auch eine leichte Mehrdeutigkeit in Bezug auf das Wort "Service" geben.
In der Praxis sollten Sie keinen Dienst haben, der Änderungen an Domänenobjekten vornimmt. Der Grund dafür ist, dass Ihr Dienst wahrscheinlich über eine Methode für jede Eigenschaft Ihres Objekts verfügt, um den Wert dieser Eigenschaft zu ändern. Dies ist ein Problem, da dann, wenn Sie eine Schnittstelle für Ihr Objekt haben (oder auch nicht), der Dienst nicht mehr dem Open-Closed-Prinzip folgt. Immer wenn Sie Ihrem Modell mehr Daten hinzufügen (unabhängig von Domain und Daten), müssen Sie Ihrem Service mehr Funktionen hinzufügen. Es gibt bestimmte Möglichkeiten, aber dies ist der häufigste Grund, warum "Unternehmensanwendungen" fehlschlagen, insbesondere wenn diese Organisationen der Meinung sind, dass "Unternehmen" "eine Schnittstelle für jedes Objekt im System" bedeutet. Können Sie sich vorstellen, einer Schnittstelle neue Methoden hinzuzufügen? dann auf zwei oder drei verschiedene Implementierungen (die In-App-Implementierung, die Mock-Implementierung und die Debug-Implementierung, die In-Memory-Implementierung?), nur für eine einzelne Eigenschaft Ihres Modells? Klingt für mich nach einer schrecklichen Idee.
Es gibt hier ein längeres Problem, auf das ich nicht näher eingehen werde, aber das Wesentliche ist folgendes: Die objektorientierte Hardcore-Programmierung besagt, dass niemand außerhalb des relevanten Objekts den Wert einer Eigenschaft innerhalb des Objekts ändern darf, auch nicht " siehe "den Wert der Eigenschaft innerhalb des Objekts. Dies kann vermieden werden, indem die Daten schreibgeschützt werden. Sie können immer noch auf Probleme stoßen, wenn viele Benutzer die Daten nur zum Lesen verwenden und Sie den Datentyp ändern müssen. Es ist möglich, dass sich alle Verbraucher ändern müssen, um dem Rechnung zu tragen. Aus diesem Grund wird empfohlen, dass Sie keine öffentlichen oder sogar geschützten Eigenschaften / Daten haben, wenn Sie APIs erstellen, die von jedermann und jedem verwendet werden sollen. es ist genau der Grund, warum OOP letztendlich erfunden wurde.
Ich denke, die Mehrheit der Antworten hier, abgesehen von der als akzeptiert gekennzeichneten, trübt das Problem. Der als akzeptiert markierte ist gut, aber ich hatte immer noch das Bedürfnis zu antworten und zuzustimmen, dass Kugel 4 der richtige Weg ist.
quelle
Die Antwort ist, dass es vom Anwendungsfall abhängt. In den meisten allgemeinen Szenarien würde ich mich jedoch an die Geschäftslogik halten, die in der Serviceschicht liegt. Das Beispiel, das Sie angegeben haben, ist wirklich einfach. Wenn Sie jedoch erst einmal über entkoppelte Systeme oder Dienste nachdenken und darüber hinaus Transaktionsverhalten hinzufügen, möchten Sie, dass dies als Teil der Service-Schicht geschieht.
Die Quellen, die Sie für die Serviceschicht ohne Geschäftslogik angegeben haben, führen eine weitere Schicht ein, die die Geschäftsschicht ist. In vielen Szenarien werden die Service-Schicht und die Business-Schicht zu einer komprimiert. Es hängt wirklich davon ab, wie Sie Ihr System entwerfen möchten. Sie können die Arbeit in drei Schichten erledigen und das Dekorieren fortsetzen und Geräusche hinzufügen.
Im Idealfall können Sie Dienste modellieren, die Geschäftslogik umfassen, um an Domänenmodellen zu arbeiten und den Status beizubehalten . Sie sollten versuchen, die Dienste so weit wie möglich zu entkoppeln.
quelle
In MVC wird Modell als Geschäftslogik definiert. Die Behauptung, dass es sich um einen anderen Ort handeln sollte, ist falsch, es sei denn, er verwendet MVC nicht. Ich betrachte Serviceschichten als ähnlich wie ein Modulsystem. Damit können Sie eine Reihe verwandter Funktionen in einem netten Paket zusammenfassen. Die Interna dieser Service-Schicht würden ein Modell haben, das die gleiche Arbeit leistet wie Sie.
quelle
Das Konzept der Service-Schicht kann aus DDD-Sicht betrachtet werden. Aaronaught erwähnte es in seiner Antwort, ich gehe nur ein bisschen darauf ein.
Ein gängiger Ansatz besteht darin, einen Controller zu haben, der für einen bestimmten Client-Typ spezifisch ist. Angenommen, es könnte ein Webbrowser sein, es könnte eine andere Anwendung sein, es könnte ein Funktionstest sein. Die Anforderungs- und Antwortformate können variieren. Daher verwende ich den Anwendungsdienst als Tool für die Verwendung einer hexagonalen Architektur . Ich injiziere dort Infrastrukturklassen, die spezifisch für eine konkrete Anfrage sind. So könnte beispielsweise mein Controller aussehen, der Webbrowser-Anfragen bearbeitet:
Wenn ich einen Funktionstest schreibe, möchte ich einen gefälschten Zahlungs-Client verwenden und benötige wahrscheinlich keine HTML-Antwort. So könnte mein Controller aussehen:
Der Anwendungsservice ist also eine Umgebung, die ich für die Ausführung von Geschäftslogik eingerichtet habe. Hier werden die Modellklassen aufgerufen - unabhängig von einer Infrastrukturimplementierung.
Beantworten Sie also Ihre Fragen aus dieser Perspektive:
Nein.
Nun, man kann es so nennen.
Nee.
Es gibt jedoch einen radikal anderen Ansatz, der die Nutzung jeglicher Art von Diensten vollständig verweigert. Zum Beispiel behauptet David West in seinem Buch Object Thinking , dass jedes Objekt alle notwendigen Ressourcen haben sollte, um seine Arbeit zu erledigen. Dieser Ansatz führt beispielsweise zum Verwerfen von ORM .
quelle
Für die Aufzeichnung.
SRP:
In diesem Fall können Sie die folgenden Schritte ausführen:
Wenn die Schulden keine Berechnung erfordern:
Wenn jedoch eine Berechnung erforderlich ist, gilt Folgendes:
oder auch
Aber auch, wenn die Berechnung in der Persistenzschicht erfolgt, ist eine solche Speicherprozedur dann
In diesem Fall möchten wir den Benutzer aus der Datenbank abrufen und die Schulden dann aktualisieren. Wir sollten dies in mehreren Schritten tun (in der Tat in zwei Schritten) und es ist nicht erforderlich, sie in die Funktion eines Dienstes einzuschließen / einzuschließen.
Und wenn es erforderlich ist, es dann zu speichern, können wir einen dritten Schritt hinzufügen
Über die vorgeschlagene Lösung
a) Wir sollten keine Angst haben, dem Endentwickler zu überlassen, ein paar zu schreiben, anstatt es in eine Funktion zu kapseln.
b) Und was Interface angeht, einige Entwickler lieben Interface und es geht ihnen gut, aber in einigen Fällen werden sie überhaupt nicht benötigt.
c) Das Ziel eines Dienstes ist es, einen Dienst ohne Attribute zu erstellen, hauptsächlich weil wir Shared / Static-Funktionen verwenden können. Es ist auch einfach zu Unit-Test.
quelle
"We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function.
"? Ich kann Lewis Black nur zitieren" ohne mein Pferd hätte ich dieses Jahr nicht in der Schule verbracht. "