2012-06-27 17 views
17

Después de ver la presentación NDC12 "Elaboración de modelos de dominio malicioso" de Jimmy Bogard (http://ndcoslo.oktaset.com/Agenda), estaba vagando cómo persistir ese tipo de modelo de dominio.
Esta es la clase de muestra de la presentación:Modelo de dominio enriquecido con comportamientos y ORM

public class Member 
{ 
    List<Offer> _offers; 

    public Member(string firstName, string lastName) 
    { 
     FirstName = firstName; 
     LastName = lastName; 
     _offers = new List<Offer>(); 
    } 

    public string FirstName { get; set; } 

    public string LastName { get; set; } 

    public IEnumerable<Offer> AssignedOffers { 
     get { return _offers; } 
    } 

    public int NumberOfOffers { get; private set; } 

    public Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc) 
    { 
     var value = valueCalc.CalculateValue(this, offerType); 
     var expiration = offerType.CalculateExpiration(); 
     var offer = new Offer(this, offerType, expiration, value); 
     _offers.Add(offer); 
     NumberOfOffers++; 
     return offer; 
    } 
} 

por lo que hay algunas reglas contenidas en este dominio del modelo:
- Miembro debe tener nombre y apellido
- Número de ofertas no se puede cambiar fuera
- El miembro es responsable de crear una nueva oferta, calculando su valor y asignación

Si intenta asignar esto a algún ORM como Entity Framework o NHibernate, no funcionará. Entonces, ¿cuál es el mejor enfoque para asignar este tipo de modelo a la base de datos con ORM?
Por ejemplo, ¿cómo puedo cargar AssignedOffers desde DB si no hay un setter?

Lo único que tiene sentido para mí es utilizar la arquitectura de comando/consulta: las consultas siempre se hacen con DTO como resultado, no como entidades de dominio, y los comandos se realizan en modelos de dominio. Además, el abastecimiento de eventos es perfecto para los comportamientos en el modelo de dominio. Pero este tipo de arquitectura CQS no es adecuada para todos los proyectos, especialmente en el brownfield. ¿O no?

Estoy al tanto de preguntas similares aquí, pero no pude encontrar ejemplos concretos y soluciones.

+0

Acabo de ver el mismo video, y me preguntaba lo mismo. ¿Qué piensas sobre pasar un poco en el constructor, y también tener una propiedad de solo lectura en la clase Member para devolver un clon de ese poco? De esta forma, puede obtener datos dentro y fuera del objeto de dominio para persistir o pasarlo. – stralsi

+0

¿Algo como instantánea de objetos? Funcionaría probablemente, pero también requeriría algunos pirateo para que funcione con la herramienta ORM. Personalmente, no veo ninguna manera fácil, y traería muchas abstracciones y generalizaciones que tendrían que luchar durante el desarrollo de la aplicación. La fuente del evento es la única manera de ir a IMO –

+0

De hecho, acabo de ver este video y estaba pensando lo mismo; ¿Eso significa que necesita un conjunto de objetos DTO/POCO para la capa de datos/persistencia que su ORM hidrata y luego utiliza un asignador como AutoMapper para asignar a un objeto de dominio? ¿Algo así sucede en el repositorio? Parece que un ORM como EF Code First espera un POCO con getters y setters. – Abe

Respuesta

1

For AssignedOffers: si observa el código, verá que AssignedOffers devuelve valor de un campo. NHibernate puede poblar ese campo de esta manera: Mapa (x => x.AssignedOffers) .Access.Field().

De acuerdo con el uso de CQS.

+0

Genial, gracias por la información de NH! –

+0

Pero, ¿y el constructor? –

+0

No veo un constructor privado/protegido o interno en su muestra. Entonces NHibernate usará el predeterminado que no tiene parámetros. Usaría este tipo de constructor que solo hizo para asegurar que el objeto se rellene en código, no por orm. – Luka

0

Al hacer DDD, primero ignora los problemas de persistencia. El ORM está bien conectado a un RDBMS, por lo que es una preocupación de persistencia.

Una estructura de persistencia de modelos ORM NO el dominio. Básicamente, el repositorio debe 'convertir' la raíz agregada recibida en una o varias entidades de persistencia. El contexto delimitado importa mucho ya que la raíz agregada cambia de acuerdo con lo que intentas lograr también.

Digamos que desea guardar al miembro en el contexto de una nueva oferta asignada. Entonces tendrás algo como esto (por supuesto, esto es sólo un escenario posible)

public interface IAssignOffer 
{ 
    int OwnerId {get;} 
    Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc); 
    IEnumerable<Offer> NewOffers {get; } 
} 

public class Member:IAssignOffer 
{ 
    /* implementation */ 
} 

public interface IDomainRepository 
{ 
    void Save(IAssignOffer member);  
} 

A continuación el repositorio será obtener sólo los datos necesarios con el fin de cambiar las entidades NH y eso es todo.

Acerca de EVent Sourcing, creo que tiene que ver si se ajusta a su dominio y no veo ningún problema con el uso de Event Sourcing solo para almacenar el dominio Aggregate Roots mientras que el resto (principalmente infraestructura) se puede almacenar en manera ordinaria (tablas relacionales). Creo que CQRS le da una gran flexibilidad en este asunto.

+0

gracias por responder, pero ¿cómo debería cargar el repositorio Miembros? –

+0

¿Cargar con qué propósito? :) – MikeSW

+0

Miembro expone IEnumerable , por lo que para su uso en la clase de servicio o algo por el estilo. –

11

Esto es realmente una muy buena pregunta y algo que he contemplado. Es potencialmente difícil crear objetos de dominio adecuados que estén completamente encapsulados (es decir, sin definidores de propiedad) y usar un ORM para construir los objetos de dominio directamente.

En mi experiencia hay 3 formas de resolver este problema:

  • Como ya se ha consignado por Luka, NHibernate admite la asignación de campos privados, en lugar de los emisores de propiedad.
  • Si se utiliza EF (que no creo que es compatible con los anteriores) se podría utilizar el memento pattern a restaurar el estado de sus objetos de dominio. p.ej. usas el marco de entidad para poblar los objetos 'memento' que tus entidades de dominio aceptan para establecer sus campos privados.
  • Como ha señalado, el uso de CQRS con aprovisionamiento de eventos elimina este problema. Este es mi método preferido para crear objetos de dominio perfectamente encapsulados, que también tienen todos los beneficios adicionales de la obtención de eventos.
2

Tema viejo. Pero hay un more recent post (finales de 2014) de Vaughn Vernon que aborda solo este escenario, con referencia particular a Entity Framework. Dado que de alguna manera tuve problemas para encontrar dicha información, tal vez pueda ser útil publicarla aquí también.

Básicamente los defensores de correos para el objeto Product dominio (agregado) para envolver el objeto de datos ProductState EF POCO por lo que se refiere a la "bolsa de datos" lado de las cosas. Por supuesto, el objeto de dominio aún agregaría todo su comportamiento de dominio enriquecido a través de métodos/accesadores específicos de dominio, pero recurriría a objetos de datos internos cuando tenga que obtener/establecer sus propiedades.

copia fragmento directamente de mensaje:

public class Product 
{ 
    public Product(
    TenantId tenantId, 
    ProductId productId, 
    ProductOwnerId productOwnerId, 
    string name, 
    string description) 
    { 
    State = new ProductState(); 
    State.ProductKey = tenantId.Id + ":" + productId.Id; 
    State.ProductOwnerId = productOwnerId; 
    State.Name = name; 
    State.Description = description; 
    State.BacklogItems = new List<ProductBacklogItem>(); 
    } 

    internal Product(ProductState state) 
    { 
    State = state; 
    } 

    //... 

    private readonly ProductState State; 
} 

public class ProductState 
{ 
    [Key] 
    public string ProductKey { get; set; } 

    public ProductOwnerId ProductOwnerId { get; set; } 

    public string Name { get; set; } 

    public string Description { get; set; } 

    public List<ProductBacklogItemState> BacklogItems { get; set; } 
    ... 
} 

Repositorio usaría constructor interna con el fin de crear una instancia (carga) una instancia de entidad de su versión persistido DB-.

El poco puedo añadir yo, es que probablemente Product objeto de dominio debe ser ensuciado con un descriptor de acceso más justo a los efectos de la persistencia a través de EF: en la misma era tan new Product(productState) permite que una entidad de dominio para ser cargado desde base de datos, la manera opuesta debe permitirse a través de algo como:

public class Product 
{ 
    // ... 
    internal ProductState State 
    { 
    get 
    { 
     // return this.State as is, if you trust the caller (repository), 
     // or deep clone it and return it 
    } 
    } 
} 

// inside repository.Add(Product product): 

dbContext.Add(product.State); 
Cuestiones relacionadas