it-swarm.com.de

Wie genau ist "Geschäftslogik sollte in einem Service sein, nicht in einem Modell"?

Situation

Ich habe heute Abend eine Antwort auf eine Frage zu StackOverflow gegeben.

Die Frage :

Die Bearbeitung eines vorhandenen Objekts sollte in der Repository-Schicht oder im Service erfolgen.

Zum Beispiel, wenn ich einen Benutzer habe, der Schulden hat. Ich möchte seine Schulden ändern. Sollte ich es in UserRepository oder im Service tun, zum Beispiel BuyingService, indem ich ein Objekt abrufe, es bearbeite und speichere?

Meine Antwort :

Sie sollten die Verantwortung für die Mutation eines Objekts zu demselben Objekt überlassen und das Repository verwenden, um dieses Objekt abzurufen.

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 Dienst sein. Nicht in einem Modell.

Was sagt das Internet?

Das hat mich also auf die Suche gebracht, da ich nie wirklich (bewusst) eine Service-Schicht verwendet habe. Ich habe angefangen, das Muster der Serviceschicht und das Muster der Arbeitseinheit zu lesen, aber bisher kann ich nicht sagen, dass ich davon überzeugt bin, dass eine Serviceschicht verwendet werden muss.

Nehmen wir zum Beispiel dieser Artikel von Martin Fowler zum Anti-Muster eines anämischen Domänenmodells:

Es gibt Objekte, von denen viele nach den Substantiven im Domänenraum benannt sind, und diese Objekte sind mit den reichen Beziehungen und Strukturen verbunden, die echte Domänenmodelle haben. Der Haken kommt, wenn Sie sich das Verhalten ansehen und feststellen, dass diese Objekte kaum ein Verhalten aufweisen, sodass sie kaum mehr als Taschen voller Getter und Setter sind. In der Tat enthalten diese Modelle häufig Entwurfsregeln, die besagen, dass Sie keine Domänenlogik in die Domänenobjekte einfügen sollen. Stattdessen gibt es eine Reihe von Serviceobjekten, die die gesamte Domänenlogik erfassen. Diese Dienste leben über dem Domänenmodell und verwenden das Domänenmodell für Daten.

(...) Die Logik, die in einem Domänenobjekt enthalten sein sollte, ist Domänenlogik - Validierungen, Berechnungen, Geschäftsregeln - wie auch immer Sie es nennen möchten.

Dies schien mir 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 Service-Schicht eher als Fassade betrachtet wird, die die Arbeit an das zugrunde liegende Modell delegiert, als als eine tatsächliche arbeitsintensive Schicht.

Anwendungsschicht [sein Name für Dienstschicht]: 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 verantwortlich ist, sind für das Unternehmen von Bedeutung oder für die Interaktion mit den Anwendungsebenen 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 die Zusammenarbeit von Domänenobjekten in der nächsten Ebene. Es hat keinen Status, der die Geschäftslage widerspiegelt, aber es kann einen Status haben, der den Fortschritt einer Aufgabe für den Benutzer oder das Programm widerspiegelt.

Welches ist verstärkt hier :

Serviceschnittstellen. Dienste stellen eine Dienstschnittstelle bereit, an die alle eingehenden Nachrichten gesendet werden. Sie können sich eine Serviceschnittstelle als eine 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 Service-Schicht sollte frei von Anwendungs- oder Geschäftslogik sein und sich in erster Linie auf einige wenige Probleme konzentrieren. Es sollte Business Layer-Aufrufe umschließen, Ihre Domain in eine gemeinsame Sprache übersetzen, die Ihre Clients verstehen können, und das Kommunikationsmedium zwischen Server und anforderndem Client verwalten.

Dies ist ein ernstzunehmender Gegensatz zu anderen Ressourcen , die über die Serviceschicht sprechen:

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 auch die Eingabe überprüfen, um sicherzustellen, dass nichts Böses oder Nichterfüllendes angefordert wird. Diese Logik gehört in Ihre Serviceschicht.

"Lösung"?

Gemäß den Richtlinien in diese Antwort habe ich den folgenden Ansatz entwickelt, der eine Service-Schicht verwendet:

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;
    }
}

Schlussfolgerung

Insgesamt hat sich hier nicht viel geändert: Der Code vom Controller wurde in die Service-Schicht verschoben (was gut ist, daher hat dieser Ansatz einen Vorteil). 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 nach Möglichkeit umgesetzt werden müssen. Ich habe jedoch keine endgültige Erklärung für die Serviceschicht gefunden und wie sie zu betrachten ist.

  • Ist es ein Mittel, einfach Logik aus der Steuerung zu extrahieren und sie stattdessen in einen Dienst zu integrieren?

  • Soll es einen Vertrag zwischen dem Controller und der Domain bilden?

  • Sollte es eine Schicht zwischen der Domäne und der Serviceschicht geben?

Und zu guter Letzt: Folgen Sie dem ursprünglichen Kommentar

Geschäftslogik sollte wirklich in einem Dienst sein. Nicht in einem Modell.

  • Ist das richtig?

    • Wie würde ich meine Geschäftslogik in einen Service anstelle des Modells einführen?
408
Jeroen Vannevel

Um zu definieren, was die Zuständigkeiten eines - Dienstes sind, müssen Sie zuerst definieren, was ein Dienst ist.

Service ist kein kanonischer oder generischer Softwarebegriff. Tatsächlich ähnelt das Suffix Service auf einem Klassennamen dem stark bösartigen Manager : Es sagt Ihnen fast nichts darüber aus, was das Objekt tatsächlich tut =).

In Wirklichkeit sollte ein Dienst sehr architekturspezifisch sein:

  1. In einer traditionellen Schichtarchitektur ist service wörtlich gleichbedeutend mit Geschäftslogikschicht . Es ist die Ebene zwischen Benutzeroberfläche und Daten. Daher gehen alle Geschäftsregeln in Dienste. 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.

  2. In einer verteilten Architektur im RPC-Stil (SOAP, UDDI, BPEL usw.) ist service 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. Verschiedene Best Practices-Anleitungen erklären, dass ein Service operation tatsächlich eine Operation auf Unternehmensebene und keine CRUD sein sollte, und ich stimme eher zu.

    Da das Routing alles durch einen tatsächlichen Remote-Dienst die Leistung ernsthaft beeinträchtigen kann, ist es normalerweise am besten nicht , wenn diese Dienste das tatsächlich implementieren Geschäftslogik selbst; Stattdessen sollten sie einen "internen" Satz von Geschäftsobjekten umschließen. Ein einzelner Dienst kann ein oder mehrere Geschäftsobjekte umfassen.

  3. In einer MVP/MVC/MVVM/MV * -Architektur sind services überhaupt nicht vorhanden. In diesem Fall bezieht sich der Begriff auf jedes generische Objekt, das in einen Controller oder ein Ansichtsmodell eingefügt werden kann. Die Geschäftslogik befindet sich 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 dieses, aber es wird als Anti-Pattern ( Anemic Domain Model ) angesehen, da das Modell selbst nichts tut, sondern nur eine Reihe von Eigenschaften für die Benutzeroberfläche.

    Einige Leute denken fälschlicherweise, dass eine 100-Zeilen-Controller-Methode, die alles in einen Dienst umwandelt, irgendwie zu einer besseren Architektur führt. Das tut es wirklich nicht. Alles, was es tut, ist eine weitere, wahrscheinlich unnötige Indirektionsebene hinzuzufügen. Praktisch Sprechen, der Controller erledigt die Arbeit immer noch, er tut dies nur über ein schlecht benanntes "Helfer" -Objekt. Ich empfehle Jimmy Bogards Präsentation Wicked Domain Models als ein klares Beispiel dafür, wie man ein anämisches Domain-Modell in ein nützliches verwandelt. Es beinhaltet eine sorgfältige Prüfung der Modelle, die Sie verfügbar machen, und welche Operationen in einem business Kontext tatsächlich gültig sind.

    Wenn Ihre Datenbank beispielsweise Bestellungen 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 ist Geschichte und (b) Es soll durch die Reihenfolge bestimmt werden ( in ) sowie durch einige andere zeitkritische Daten/Regeln. Das Erstellen eines Dienstes zum Verwalten von Bestellungen löst dieses Problem nicht unbedingt, da Benutzercode noch das tatsächliche Bestellobjekt abrufen und den Betrag darauf ändern kann. Stattdessen sollte die Reihenfolge selbst dafür verantwortlich sein, dass sie nur auf sichere und konsistente Weise geändert werden kann.

  4. In DDD sind Dienste speziell für die Situation gedacht wenn Sie eine Operation haben, die nicht ordnungsgemäß zu einem aggregierten Stamm gehört . Hier muss man vorsichtig sein, denn oft kann die Notwendigkeit eines Dienstes bedeuten, dass Sie nicht die richtigen Wurzeln verwendet haben. Vorausgesetzt, Sie haben dies getan, wird ein Dienst verwendet, um Vorgänge über mehrere Wurzeln hinweg zu koordinieren oder manchmal um Probleme zu behandeln, 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 Transaktionsskripte verwenden darf. Wenn Sie an großen Anwendungen arbeiten, werden Sie wahrscheinlich irgendwann auf Instanzen stoßen, in denen es viel einfacher ist, mit einer T-SQL- oder PL/SQL-Prozedur etwas zu erreichen, als sich mit dem Domänenmodell zu beschäftigen. Dies ist in Ordnung und gehört zu einem Dienst.

    Dies ist eine radikale Abkehr von der Definition von Diensten mit geschichteter Architektur. Eine Serviceschicht kapselt Domänenobjekte. Ein DDD-Dienst kapselt alles, was nicht in den Domänenobjekten ist, und macht keinen Sinn.

  5. In einer serviceorientierten Architektur wird ein service als technische Autorität für eine Geschäftsfähigkeit angesehen. Das bedeutet, dass es der exklusive Eigentümer einer bestimmten Teilmenge der Geschäftsdaten ist und nichts anderes diese Daten berühren darf - nicht einmal nur read es.

    Services sind notwendigerweise ein End-to-End-Angebot in einer SOA. Das heißt, ein Service ist weniger ein spezifischer - Komponente als ein ganzer - Stapel und Ihre gesamte Anwendung (oder Ihr gesamtes Unternehmen) ) ist eine Reihe dieser Dienste, die nebeneinander ohne Schnittpunkt ausgeführt werden, außer auf der Messaging- und der UI-Ebene. Jeder Service verfügt über eigene Daten, Geschäftsregeln und eine eigene Benutzeroberfläche. Sie müssen nicht miteinander orchestrieren, weil sie geschäftsorientiert sein sollen - und wie das Unternehmen selbst hat jeder Dienst seine eigenen Verantwortlichkeiten und arbeitet mehr oder weniger unabhängig von den anderen.

    Nach der Definition SOA] ist also jede Geschäftslogik irgendwo im Service enthalten, aber auch die gesamte system . Dienste in einer SOA können Komponenten haben, und sie können endpoints , aber es ist ziemlich gefährlich, einen Code als service zu bezeichnen, da dies im Widerspruch zu dem steht, was das ursprüngliche "S" bedeuten soll.

    Da SOA im Allgemeinen sehr an Messaging interessiert ist, sind die Vorgänge, die Sie möglicherweise zuvor in einem - Dienst gepackt haben, im Allgemeinen in ) gekapselt + Handler , aber die Vielzahl ist unterschiedlich. Jeder Handler behandelt one Nachrichtentyp, one Operation Eine strikte Interpretation des Single Responsibility Principle , sorgt jedoch für eine hervorragende Wartbarkeit, da jede mögliche Operation in einer eigenen Klasse ist. Sie brauchen also nicht wirklich need Zentralisierte Geschäftslogik, da Befehle eher Geschäftsvorgänge als technische darstellen.

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 schließlich 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 entworfen wurde, hängt von Ihren Architekturzielen ab.

Es gibt keine richtige oder falsche Antwort, nur was für Ihre Situation gilt.

381
Aaronaught

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 sein sollte und nicht das Modell, 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 Entität. Davon spricht Martin Fowler.

So wie ich das sehe, spricht Martin Fowler von Dingen in einer idealen Welt. In der realen Welt von Hibernate und JPA (in Java Land)) sind die DTOs eine superleichte Abstraktion. Ich würde gerne meine Geschäftslogik in meine Entität integrieren. Das 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. Um meine Meinung zusammenzufassen: Martin Fowler empfiehlt den richtigen Weg, aber die ORMs verhindern, dass Sie dies tun.

Ich denke, Bob Martin hat einen realistischeren Vorschlag und er gibt ihn in diesem Video, das nicht kostenlos ist . Er spricht davon, Ihre DTOs frei von Logik zu halten. Sie halten die Daten einfach fest 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 den DTOs selbst ist nicht OO. Aber sobald Sie diese Ebene verlassen, werden Sie so OO wie Martin Fowler befürwortet).

Der Vorteil dieser Trennung besteht darin, dass die Persistenzschicht abstrahiert wird. Sie könnten von JPA zu JDBC wechseln (oder umgekehrt), und keine der Geschäftslogiken müsste sich ändern. Es hängt nur von den DTOs ab, es ist egal, wie diese DTOs werden gefüllt.

Um leicht Themen zu ändern, müssen Sie berücksichtigen, dass SQL-Datenbanken nicht objektorientiert sind. ORMs haben jedoch normalerweise eine Entität - die ein Objekt ist - pro Tabelle. Sie haben also von Anfang an bereits eine Schlacht verloren. Nach meiner Erfahrung können Sie die Entität niemals objektorientiert so darstellen, wie Sie es möchten.

Was " einen Dienst" betrifft, wäre Bob Martin gegen eine Klasse namens FooBarService. Das ist nicht objektorientiert. Was macht ein Service? Alles im Zusammenhang mit FooBars. Es kann auch mit FooBarUtils bezeichnet werden. 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.

41
Daniel Kaplan

Ich arbeite gerade am Greenfield-Projekt 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:

  • Datenschicht. Abfrage und Aktualisierung der Datenbank. Die Schicht wird durch injizierbare Repositories belichtet.
  • Domänenschicht. Hier lebt die Geschäftslogik. Diese Schicht verwendet injizierbare Repositorys und ist für den Großteil der Geschäftslogik verantwortlich. Dies ist der Kern der Anwendung, die wir gründlich testen werden.
  • Serviceschicht. Diese Schicht kommuniziert mit der Domänenschicht und bedient die Clientanforderungen. In unserem Fall ist die Serviceschicht recht einfach: Sie leitet Anforderungen an die Domänenschicht weiter, kümmert sich um die Sicherheit und einige andere Querschnittsthemen. Dies unterscheidet sich nicht wesentlich von einem Controller in einer MVC-Anwendung - Controller sind klein und einfach.
  • Client-Schicht. Spricht über SOAP mit der Serviceschicht.

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 entschieden haben, dass Sie Ihre Serviceschicht ersetzen oder sogar entfernen möchten, müssen Sie die gesamte Geschäftslogik an einen anderen Ort verschieben. Eine solche Anforderung ist selten, kann aber vorkommen.

Nach alledem denke ich, dass dies ziemlich nahe an dem liegt, was Martin Fowler damit gemeint hat

Diese Dienste leben über dem Domänenmodell und verwenden das Domänenmodell für Daten.

Das folgende Diagramm veranschaulicht dies ziemlich gut:

http://martinfowler.com/eaaCatalog/serviceLayer.html

http://martinfowler.com/eaaCatalog/ServiceLayerSketch.gif

26
CodeART

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 führt im Wesentlichen zu einer Unterbrechung des objektorientierten Designs.

Lassen Sie uns das grundlegendste objektorientierte Konzept überprüfen: 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 es ist das, worauf sich Martin Fowler bezieht, wenn er über ein anämisches Domänenmodell spricht.

Wenn Sie eine Kontoschicht haben, die das Konto schließt, anstatt das Kontoobjekt selbst schließen zu lassen, haben Sie kein echtes Kontoobjekt. Ihr Konto "Objekt" ist lediglich eine Datenstruktur. Am Ende haben Sie, wie Martin Fowler vorschlägt, ein paar Taschen mit Getter und Setter.

Dies ist eines der Dinge, die wirklich vom Anwendungsfall abhängen. Der übergeordnete Punkt einer Serviceschicht besteht darin, die Geschäftslogik zusammen zu konsolidieren. Dies bedeutet, dass mehrere Controller denselben UserService.MakeHimPay () aufrufen können, ohne sich tatsächlich darum zu kümmern, wie die Zahlung erfolgt. Was im Dienst vor sich geht, kann so einfach sein wie das Ändern einer Objekteigenschaft, oder es kann eine komplexe Logik ausführen, die sich mit anderen Diensten befasst (dh das Aufrufen von Diensten von Drittanbietern, das Aufrufen der Validierungslogik oder auch nur das 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, eine Methode für das Domänenobjekt einige Berechnungen für sich selbst durchführen zu lassen. In Ihrem letzten Beispiel ist der Dienst eine redundante Schicht über dem Repository/Domänenobjekt. Es bietet einen guten Puffer gegen Änderungen der Anforderungen, ist aber nicht unbedingt erforderlich. Wenn Sie der Meinung sind, dass Sie einen Dienst benötigen, versuchen Sie, die einfache Logik "Eigenschaft X für Objekt Y ändern" anstelle des Domänenobjekts auszuführen. Die Logik der Domänenklassen fällt eher in den Bereich "Diesen Wert aus Feldern berechnen", als dass alle Felder über Getter/Setter verfügbar gemacht werden.

9
firelore

Der einfachste Weg, um zu veranschaulichen, warum Programmierer es scheuen, Domänenlogik in die Domänenobjekte einzufügen, besteht darin, dass sie normalerweise mit der Situation konfrontiert sind: "Wo lege ich die Validierungslogik ab?" Nehmen Sie zum Beispiel dieses Domain-Objekt:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

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 validiert werden muss vorher er schreibt die Änderung tatsächlich am Domänenobjekt fest, da er den Benutzer entweder vorher oder beim Klicken auf die Schaltfläche Speichern darüber informieren muss sie können das nicht 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 überhaupt mit der Transaktion begonnen haben.

Aus diesem Grund verschieben Benutzer die Validierungslogik in eine Art Dienst, z. B. MyEntityValidator. Dann können sowohl die Entität als auch die aufrufende Logik einen Verweis auf den Validierungsdienst erhalten und ihn wiederverwenden.

Wenn Sie nicht dies tun und die Validierungslogik dennoch wiederverwenden möchten, fügen Sie sie am Ende in statische Methoden der Entitätsklasse ein:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

Dies würde Ihr Domain-Modell weniger "anämisch" machen und die Validierungslogik neben der Eigenschaft beibehalten, was großartig ist, aber ich denke, niemand mag die statischen Methoden wirklich.

8
Scott Whitlock

Wie würden Sie Ihre Geschäftslogik in der Service-Schicht 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 muss einen Zahlungsdatensatz erstellen, die Schulden dieses Benutzers erhöhen und all dies in Ihren Repositories 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.

4
Mr Cochese

Die tl; dr-Version:
Meine Erfahrungen und Meinungen besagen, dass alle Objekte mit Geschäftslogik Teil des Domänenmodells sein sollten. Das Datenmodell sollte wahrscheinlich überhaupt keine Logik haben. Die Dienste sollten wahrscheinlich beide miteinander verbinden und sich mit Querschnittsthemen (Datenbanken, Protokollierung usw.) befassen. Die akzeptierte Antwort ist jedoch die praktischste.

Die längere Version, auf die andere schon angedeutet haben, 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 Zweideutigkeit im Wort "Dienst" geben.

In der Praxis sollten Sie keinen Dienst haben, der Änderungen an Domänenobjekten vornimmt. Der Grund dafür ist, dass Ihr Dienst wahrscheinlich für jede Eigenschaft Ihres Objekts eine Methode hat, um den Wert dieser Eigenschaft zu ändern. Dies ist ein Problem, da dann, wenn Sie eine Schnittstelle für Ihr Objekt haben (oder auch wenn nicht), der Dienst nicht mehr dem Open-Closed-Prinzip folgt. Wenn Sie Ihrem Modell stattdessen mehr Daten hinzufügen (unabhängig von Domäne oder Daten), müssen Sie Ihrem Service letztendlich weitere Funktionen hinzufügen. Es gibt bestimmte Möglichkeiten, dies zu umgehen, aber dies ist der häufigste Grund, warum "Enterprise" -Anwendungen fehlschlagen, insbesondere wenn diese Organisationen der Meinung sind, dass "Enterprise" "eine Schnittstelle für jedes Objekt im System haben" bedeutet. Können Sie sich vorstellen, einer Schnittstelle neue Methoden hinzuzufügen, dann zwei oder drei verschiedenen Implementierungen (die In-App-Implementierung, die Mock-Implementierung und die Debug-Implementierung, die In-Memory-Implementierung?), Nur für eine einzelne Eigenschaft in Ihrem Modell? Klingt für mich nach einer schrecklichen Idee.

Es gibt hier ein längeres Problem, auf das ich nicht eingehen werde, aber das Wesentliche ist folgendes: Die objektorientierte Hardcore-Programmierung besagt, dass niemand außerhalb des relevanten Objekts in der Lage sein sollte, den Wert einer Eigenschaft innerhalb des Objekts zu ändern, und auch nicht " Siehe "Der Wert der Eigenschaft innerhalb des Objekts. Dies kann durch schreibgeschützte Daten verringert werden. Es kann immer noch zu Problemen kommen, z. B. wenn viele Benutzer die Daten auch als schreibgeschützt verwenden und Sie den Typ dieser Daten ändern müssen. Es ist möglich, dass sich alle Verbraucher ändern müssen, um dies zu berücksichtigen. Aus diesem Grund wird empfohlen, keine öffentlichen oder sogar geschützten Eigenschaften/Daten zu verwenden, wenn Sie APIs für jedermann verwenden. es ist genau der Grund OOP wurde letztendlich erfunden.

Ich denke, die Mehrheit der Antworten hier, abgesehen von der als akzeptiert gekennzeichneten, trübt das Problem. Die als akzeptiert gekennzeichnete ist gut, aber ich hatte immer noch das Bedürfnis zu antworten und zuzustimmen, dass Kugel 4 im Allgemeinen der richtige Weg ist.

In DDD sind Dienste speziell für die Situation gedacht, in der Sie eine Operation haben, die nicht ordnungsgemäß zu einem aggregierten Stamm gehört. Hier muss man vorsichtig sein, denn oft kann die Notwendigkeit eines Dienstes bedeuten, dass Sie nicht die richtigen Wurzeln verwendet haben. Vorausgesetzt, Sie haben dies getan, wird ein Service verwendet, um Vorgänge über mehrere Wurzeln hinweg zu koordinieren oder manchmal um Bedenken zu behandeln, die das Domänenmodell überhaupt nicht betreffen ...

2
WolfgangSenff

Das Konzept der Serviceschicht kann aus der DDD-Perspektive betrachtet werden. Aaronaught hat es in seiner Antwort erwähnt, ich gehe nur ein bisschen darauf ein.

Ein gängiger Ansatz besteht darin, einen Controller zu haben, der für einen Clienttyp spezifisch ist. Angenommen, es könnte sich um einen Webbrowser handeln, es könnte sich um eine andere Anwendung handeln, es könnte sich um einen Funktionstest handeln. Die Anforderungs- und Antwortformate können variieren. Daher verwende ich den Anwendungsdienst als Werkzeug zur Verwendung einer hexagonalen Architektur . Ich füge dort Infrastrukturklassen ein, die für eine konkrete Anfrage spezifisch sind. So könnte beispielsweise mein Controller aussehen, der Webbrowser-Anforderungen bearbeitet:

class WebBroserController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new PayPalClient(),
                new OrderRepository()
            )
        ;

        $response = new HtmlView($responseData);
    }
}

Wenn ich einen Funktionstest schreibe, möchte ich einen gefälschten Zahlungsclient verwenden und würde wahrscheinlich keine HTML-Antwort benötigen. Mein Controller könnte also so aussehen:

class FunctionalTestController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new FakePayPalClient(),
                new OrderRepository(),
                $data
            )
        ;

        return new JsonData($responseData);
    }
}

Der Anwendungsdienst 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 Ihre Fragen aus dieser Perspektive:

Ist es ein Mittel, einfach Logik aus der Steuerung zu extrahieren und sie stattdessen in einen Dienst zu integrieren?

Nein.

Soll es einen Vertrag zwischen dem Controller und der Domain bilden?

Nun, man kann es so nennen.

Sollte es eine Schicht zwischen der Domäne und der Serviceschicht geben?

Nee.


Es gibt jedoch einen radikal anderen Ansatz, der die Nutzung jeglicher Art von Diensten völlig verweigert. Zum Beispiel behauptet David West in seinem Buch Object Thinking , dass jedes Objekt über alle notwendigen Ressourcen verfügen sollte, um seine Arbeit zu erledigen. Dieser Ansatz führt beispielsweise zu Verwerfen eines ORM .

1
Zapadlo

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 bereitgestellt haben, ist wirklich einfach. Sobald Sie jedoch anfangen, an entkoppelte Systeme oder Dienste zu denken und darüber hinaus Transaktionsverhalten hinzuzufügen, möchten Sie wirklich, dass dies als Teil der Dienstschicht 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 Serviceschicht und die Geschäftsschicht 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 weiter dekorieren und Geräusche hinzufügen.

Was Sie idealerweise tun können, sind Modelldienste, die Geschäftslogik umfassen, um an Domänenmodellen zu arbeiten, um den Status beizubehalten . Sie sollten versuchen, die Dienste so weit wie möglich zu entkoppeln.

1
sunny

In MVC wird das Modell als Geschäftslogik definiert. Die Behauptung, es sollte sich an einem anderen Ort befinden, ist falsch, es sei denn, er verwendet MVC nicht. Ich betrachte Service-Layer als ähnlich wie ein Modulsystem. Es ermöglicht Ihnen, eine Reihe verwandter Funktionen in einem Nice-Paket zu bündeln. Die Interna dieser Service-Schicht hätten ein Modell, das die gleiche Arbeit wie Sie erledigt.

Das Modell besteht aus Anwendungsdaten, Geschäftsregeln, Logik und Funktionen. http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

0
stonemetal