2010-02-10 15 views
8

Consideremos el siguiente ejemplo simplificado:DDD - transición de estado Entidad

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

El Estado de clase se utiliza directamente en el billete objeto de dominio. Más adelante en el ciclo de vida del ticket, podrían establecerse otros estados.

El ticket se conserva en una tabla de entradas, así como también en TicketState. Entonces, dentro del DB, el ticket tendrá una clave externa para la tabla de estado del ticket.

Cuando establezco el estado apropiado dentro de mi entidad, ¿cómo cargo la instancia de estado desde la base de datos? ¿Tengo que inyectar un repositorio en la entidad? ¿Necesito usar un marco como castillo para este caso? ¿O hay mejores soluciones, tal vez pasar el estado desde el exterior?

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

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

¿Existe alguna práctica recomendada? Hasta ahora no he usado ningún marco de inyección de dependencias o nada y seguí cualquier cosas persistencia de mi dominio ..

Otra approch:

public class Ticket 
{ 
    //... 

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

Respuesta

4

el billete no tendría una referencia a un repositorio. Tendría una relación uno-a-uno con TicketState, y el TicketRepository simplemente haría el JOIN y asignaría los valores en el Ticket.

Cuando creo objetos de modelo, generalmente no les hago saber si son persistentes o no, por lo que no se les inyecta un repositorio. El repositorio maneja todas las operaciones CRUD.

Algunas personas se oponen a esto, diciendo que conduce a un anemic domain model; tal vez eres uno de ellos. Si ese es el caso, inyecte el repositorio en su objeto Ticket, pero simplemente solicite que haga el JOIN y devuelva un Ticket con su estado poblado. Cuando insertas o actualizas, tienes que modificar dos tablas como una sola unidad de trabajo, así que asegúrate de tener las transacciones activadas.

La razón por la que me gusta tener operaciones CRUD fuera del objeto de modelo de dominio es que generalmente no es el único objeto de dominio que participa en un caso de uso o transacción. Por ejemplo, tal vez su simple caso de uso de "comprar boleto" tendrá un objeto de Boleto, pero también podría tener que haber otros objetos que se ocupan de las reservas y el asiento y el libro mayor e inventario de equipaje y todo tipo de cosas. Realmente querrá persistir varios objetos modelo como una sola unidad de trabajo. Solo el nivel de servicio puede saber cuándo un objeto modelo está actuando por sí mismo y si forma parte de un plan mayor y más grande.

Actualización:

Otra razón por la que no me gusta la idea de inyectar un objeto de modelo con un DAO para que pueda controlar las tareas de persistencia es el destrozo de las capas y la dependencia cíclica que introduce. Si mantiene el modelo limpio de cualquier referencia a las clases de persistencia, puede usarlas sin tener que invocar la otra capa. Es una dependencia de una sola dirección; la persistencia conoce el modelo, pero el modelo no sabe acerca de la persistencia.

Inyecta la persistencia en el modelo y dependen cíclicamente el uno del otro. Nunca puedes usar o probar ninguno sin el otro. Sin estratificación, sin separación de preocupaciones.

+0

+1 para la persistencia ignorant objects. El hecho de que el modelo de dominio haya sido contaminado con objetos de repositorio no significa que no sea anémico, de hecho, podría ocultar lugares donde los objetos de dominio no están soportando su peso. –

+0

Gracias, pero tal vez mi pregunta no fue lo suficientemente clara. Estaba preguntando cómo configurar la entidad de estado apropiada cuando, por ejemplo, el ticket se instaura o su estado cambia. ¿Puedes publicar un ejemplo sobre cómo resolverías el problema anterior? – Chris

+0

El estado de instanciación es bastante fácil: debe ser NUEVO. Algo tiene que estar orquestando eventos para cambiar su estado. Normalmente lo llamo un servicio porque implementa un caso de uso particular. Poseerá una instancia del repositorio, que usará para instanciar un nuevo Ticket o leer uno existente, cambiar su estado para satisfacer el caso de uso, persistir el nuevo estado como una sola unidad de trabajo y finalizar el uso. caso. – duffymo

1

Esta respuesta es una continuación de duffymo's.

En una vista DDD del mundo, su TicketState es una entidad que forma parte del agregado de Ticket (donde un ticket es la raíz agregada).

En consecuencia, su TicketRepository se ocupa tanto de Tickets como de TicketStates.

Cuando recupera un Ticket de la capa de persistencia, permite que su TicketRepository recupere el estado del DB y lo configure correctamente en el ticket.

Si está renovando un boleto, entonces (creo) no necesita tocar la base de datos todavía. Cuando el ticket finalmente persiste, toma el Nuevo estado del ticket y lo persiste correctamente.

Las clases de su dominio no deberían necesitar saber nada sobre el modelo de base de datos que se ocupa del estado, solo deberían conocer la vista de estado del modelo de dominio. Su repositorio es entonces responsable de este mapeo.

+0

¿Pero qué pasa si instancia un nuevo ticket, que aún no se ha conservado y quiero mostrar su nombre de estado (almacenado en el DB) en la interfaz de usuario? ¿De dónde obtengo esa información? – Chris

+0

@Chris - mira mi comentario a continuación. – duffymo

0

Para mí, un par clave-valor simple que representa el estado en la base de datos (o cualquier medio de persistencia) no necesita ser modelado como tal en el dominio. En el dominio, convertiría a TicketState en una enumeración y responsabilizaría al ITicketRepository de saber cómo asignar eso a los requisitos del esquema de la base de datos.

Dentro del repositorio de tickets, puede tener un caché de ID de estado de ticket en TicketState, que se carga de forma diferida en una variable estática (solo un enfoque) de la base de datos. El repositorio de tickets asignaría el valor de Ticket.State a los ID de ese caché para inserciones/actualizaciones.

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

Tal como lo muestra el modelo, estoy de acuerdo con esto; sin embargo, si la transición del estado necesita evolucionar para poder expresar la lógica del negocio, entonces tener una entidad del Estado puede tener mucho sentido. –

+0

¿Pero qué pasa si instancia un nuevo ticket, que aún no se ha conservado y quiero mostrar su nombre de estado (almacenado en el DB) en la interfaz de usuario? ¿De dónde obtengo esa información? – Chris

+0

Realízala fácilmente después de crear una instancia y antes de mostrarla en la interfaz de usuario, por supuesto. ¿Por qué mostrarías el estado de un objeto persistente antes de que se guardara? No muestra el resultado de ninguna operación a un usuario hasta que esté completo, y no está completo hasta que persiste. – duffymo