2010-02-10 2 views
8

betrachten Sie das folgende vereinfachte Beispiel:DDD - Entity Zustandsübergang

public class Ticket 
{ 
    public int Id; 
    public TicketState State; 

    public Ticket() 
    { 
     // from where do I get the "New" state entity here? with its id and name 
     State = State.New; 
    } 

    public void Finished() 
    { 
     // from where do I get the "Finished" state entity here? with its id and name   
     State = State.Finished; 
    } 
} 

public class TicketState 
{ 
    public int Id; 
    public string Name; 
} 

Die Klasse Zustand direkt innerhalb des Domain-Objekt-Ticket verwendet wird. Später im Lebenszyklus des Tickets könnten andere Zustände eingestellt sein.

Das Ticket wird in einer Ticket-Tabelle sowie in TicketState gespeichert. Innerhalb der DB hat das Ticket also einen Fremdschlüssel für die Ticketstatus-Tabelle.

Wenn ich den entsprechenden Status innerhalb meiner Entity festlege, wie lade ich die State-Instanz aus der DB? Muss ich ein Repository in die Entität einfügen? Muss ich für solche Fälle ein Framework wie Castle verwenden? Oder gibt es bessere Lösungen, vielleicht den Staat von außen?

public class Ticket 
{ 
    //... 
    public ITicketStateRepository stateRep; //<-- inject 

    public Ticket() 
    { 
     State = stateRep.GetById(NEW_STATE_ID); 
    } 
    //... 
} 

Gibt es Best Practices? Bisher habe ich keinen Dependency Injection-Framework oder irgendetwas verwenden und hielt keine Persistenz Dinge aus meiner Domain ..

Another approch:

public class Ticket 
{ 
    //... 

    public Ticket(NewTicketState newTicketState) 
    { 
     State = newTicketState; 
    } 
    public void Finished(FinishedTicketState finishedTicketState) 
    { 
     State = finishedTicketState; 
    } 
    //... 
} 

Antwort

4

Das Ticket nicht einen Verweis auf ein Repository haben würde. Es würde eine Eins-zu-Eins-Beziehung mit TicketState haben, und das TicketRepository würde einfach den JOIN durchführen und die Werte dem Ticket zuordnen.

Wenn ich Modellobjekte erstelle, mache ich ihnen normalerweise nicht bewusst, ob sie persistent sind oder nicht, so dass ihnen kein Repository hinzugefügt wird. Das Repository behandelt alle CRUD-Vorgänge.

Einige Leute widersprechen dem und sagen, dass es zu einem anemic domain model führt; vielleicht bist du einer von ihnen. Wenn dies der Fall ist, injizieren Sie das Repository in Ihr Ticket-Objekt, aber fordern Sie es einfach auf, den JOIN-Befehl auszuführen und ein Ticket zurückzugeben, dessen Status aufgefüllt ist. Wenn Sie zwei Tabellen als eine Arbeitseinheit einfügen oder aktualisieren, müssen Sie sicherstellen, dass Transaktionen aktiviert sind.

Der Grund, warum ich CRUD-Ops außerhalb des Domänenmodellobjekts haben möchte, ist, dass es normalerweise nicht das einzige Domänenobjekt ist, das an einem Anwendungsfall oder einer Transaktion beteiligt ist. Zum Beispiel könnte Ihr einfacher "Kauf Ticket" Anwendungsfall ein Ticket-Objekt haben, aber es muss auch einige andere Objekte geben, die sich mit Reservierungen und Sitz- und Hauptbuch und Inventar von Gepäck und allen möglichen anderen Dingen beschäftigen. Sie möchten mehrere Modellobjekte wirklich als einzelne Arbeitseinheit beibehalten. Nur die Serviceschicht kann wissen, wann ein Modellobjekt eigenständig agiert und wann es Teil eines größeren, größeren Plans ist.

Update:

Ein weiterer Grund, warum Ich mag nicht die Idee, ein Modellobjekt mit einem DAO der Injektion, so dass es Persistenz Aufgaben umgehen kann, ist das Wegwerfen von Schichten und die zyklische Abhängigkeit es einführt. Wenn Sie das Modell von Referenzen auf Persistenzklassen freihalten, können Sie sie verwenden, ohne die andere Ebene aufzurufen. Es ist eine einseitige Abhängigkeit; Persistenz kennt Modell, aber Modell weiß nichts über Persistenz.

Injizieren Sie die Persistenz in Modell und sie sind zyklisch voneinander abhängig. Sie können keines der beiden ohne das andere verwenden oder testen. Keine Schichtung, keine Trennung von Belangen.

+0

+1 für persistente ignorante Objekte. Nur weil das Domänenmodell mit Repository-Objekten verschmutzt wurde, heißt das nicht, dass es nicht anämisch ist - es könnte sogar Orte verbergen, an denen Domain-Objekte nicht ihr Gewicht tragen. –

+0

Danke, aber vielleicht war meine Frage nicht klar genug. Ich fragte, wie man die entsprechende Statuseinheit einstellt, wenn z.B. Das Ticket wird instanziiert oder sein Status wird geändert. Können Sie ein Beispiel veröffentlichen, wie Sie das obige Problem lösen könnten? – Chris

+0

Der Instantiierungszustand ist einfach genug: Es sollte NEU sein. Etwas muss Events orchestrieren, um seinen Zustand zu ändern. Ich nenne es normalerweise einen Dienst, weil es einen bestimmten Anwendungsfall implementiert. Es besitzt eine Instanz des Repositorys, mit der es entweder ein neues Ticket instanziiert oder ein bestehendes liest, seinen Status ändert, um den Anwendungsfall zu erfüllen, den neuen Status als einzelne Arbeitseinheit beizubehalten und die Verwendung zu beenden Fall. – duffymo

1

Diese Antwort folgt hoffentlich auf Duffymos.

In einer DDD-Ansicht der Welt ist Ihr TicketState eine Entität, die Teil des Ticket-Aggregats ist (wobei ein Ticket das aggregierte Stammverzeichnis ist).

Anschließend behandelt Ihr TicketRepository sowohl Tickets als auch TicketStates.

Wenn Sie ein Ticket aus der Persistenzschicht abrufen, erlauben Sie Ihrem TicketRepository, den Status aus der DB abzurufen und auf dem Ticket korrekt zu setzen.

Wenn Sie ein Ticket neu erstellen (ich denke), müssen Sie die Datenbank noch nicht berühren. Wenn das Ticket schließlich beibehalten wird, nehmen Sie den Status "Neu" des Tickets und führen es korrekt fort.

Ihre Domänenklassen sollten nichts über das Datenbankmodell wissen müssen, das sich um den Status kümmert, sie sollten nur über die Zustandsansicht des Domänenmodells informiert sein. Ihr Repository ist dann für dieses Mapping verantwortlich.

+0

Was passiert aber, wenn ich ein neues Ticket instanziiere, das noch nicht persistent ist und seinen Statusnamen (in der Datenbank gespeichert) auf der Benutzeroberfläche anzeigen möchte? Woher bekomme ich diese Informationen? – Chris

+0

@Chris - siehe meinen Kommentar unten. – duffymo

0

Für mich muss ein einfaches Schlüssel/Wert-Paar, das den Status in der Datenbank (oder einem anderen Persistenzmedium) darstellt, nicht als solches in der Domäne modelliert werden. In der Domäne würde ich TicketState zu einer Enumeration machen und es dem ITicketRepository überlassen, zu wissen, wie man dies den Anforderungen des Datenbankschemas zuordnen kann.

Innerhalb des Ticketrepositorys können Sie einen Ticketstatus-ID-Puffer für TicketState speichern, der in eine statische Variable (nur eine Methode) aus der Datenbank geladen wird. Das Ticket-Repository würde den Ticket.State-Wert IDs aus diesem Cache für Einfügungen/Aktualisierungen zuordnen.

namespace Domain { 
    public class Ticket { 
    public Ticket() { State = TicketStates.New; } 
    public void Finish() { State = TicketStates.Finished; } 
    public TicketStates State {get;set;} 
    } 

    public enum TicketState { New, Finished } 
} 

namespace Repositories { 
    public class SqlTicketRepository : ITicketRepository { 
    public void Save(Ticket ticket) { 
     using (var tx = new TransactionScope()) { // or whatever unit of work mechanism 
     int newStateId = TicketStateIds[ticket.State]; 
     // update Ticket table with newStateId 
     } 
    } 
    } 

    private Dictionary<TicketState, int> _ticketStateIds; 
    protected Dictionary<TicketState, int> TicketStateIds{ 
    get { 
     if (_ticketStateIds== null) 
     InitializeTicketStateIds(); 
     return _ticketStateIds; 
    } 
    } 

    private void InitializeTicketStateIds() { 
    // execute SQL to get all key-values pairs from TicketStateValues table 
    // use hard-coded mapping from strings to enum to populate _ticketStateIds; 
    } 
} 
+0

Da das Modell steht, stimme ich damit überein - jedoch, wenn sich der Statusübergang so entwickeln muss, dass er Geschäftslogik ausdrücken kann, dann kann das Vorhandensein einer staatlichen Entität sehr sinnvoll sein. –

+0

Was aber, wenn ich ein neues Ticket instanziiere, das noch nicht persistent ist und seinen Statusnamen (in der Datenbank gespeichert) auf der Benutzeroberfläche anzeigen möchte? Woher bekomme ich diese Informationen? – Chris

+0

Einfach pervers, nachdem Sie es instanziiert haben und bevor Sie natürlich auf der Benutzeroberfläche angezeigt werden. Warum sollten Sie den Status eines persistenten Objekts anzeigen, bevor es gespeichert wurde? Sie zeigen einem Benutzer erst dann das Ergebnis einer Operation an, wenn es abgeschlossen ist, und es ist erst abgeschlossen, wenn es dauerhaft ist. – duffymo