2009-02-27 8 views
8

Actualmente estoy refabricando un código en un proyecto que está terminando, y terminé poniendo mucha lógica comercial en clases de servicio en lugar de en los objetos de dominio. En este punto, la mayoría de los objetos de dominio son solo contenedores de datos. Había decidido escribir la mayor parte de la lógica empresarial en objetos de servicio, y refactorizar todo después en formas mejores, más reutilizables y más legibles. De esa forma, podría decidir qué código debería colocarse en los objetos de dominio, y qué código debería dividirse en nuevos objetos propios, y qué código debería quedar en una clase de servicio. Así que tengo algo de código:En el diseño impulsado por dominio, ¿sería una violación de DDD poner llamadas a las reposiciones de otros objetos en un objeto de dominio?

public decimal CaculateBatchTotal(VendorApplicationBatch batch) 
{ 
    IList<VendorApplication> applications = AppRepo.GetByBatchId(batch.Id); 

    if (applications == null || applications.Count == 0) 
      throw new ArgumentException("There were no applications for this batch, that shouldn't be possible"); 
    decimal total = 0m; 
    foreach (VendorApplication app in applications) 
      total += app.Amount; 
    return total; 
} 

Este código parece que sería una buena adición a un objeto de dominio, porque es único parámetro de entrada es el objeto de dominio propio. Parece un candidato perfecto para algunas refactorizaciones. Pero el único problema es que este objeto llama al repositorio de otro objeto. Lo que me hace querer dejarlo en la clase de servicio.

Mis preguntas son por lo tanto:

  1. dónde pondría este código?
  2. ¿Romperías esta función?
  3. ¿Dónde lo pondría alguien que sigue un diseño estricto basado en el dominio?
  4. ¿Por qué?

Gracias por su tiempo.

Editar Nota: No se puede usar un ORM en este caso, por lo que no puedo usar una solución de carga diferida.

Editar Nota 2: No puedo modificar el constructor para que tome los parámetros, debido a cómo la capa de datos aspirante instancia los objetos de dominio utilizando la reflexión (no es mi idea).

Editar Nota3: No creo que un objeto por lotes deba ser capaz de sumar cualquier lista de aplicaciones, parece que solo debería ser capaz de totalizar las aplicaciones que están en ese lote en particular. De lo contrario, tiene más sentido para mí dejar la función en la clase de servicio.

+0

Variables locales de Pascal-cased. Yuck. – yfeldblum

+0

@Justice, siento tu dolor – mbillard

Respuesta

5

Ni siquiera debería tener acceso a los repositorios desde el objeto de dominio.

Lo que puede hacer es dejar que el servicio proporcione al objeto de dominio la información adecuada o tener un delegado en el objeto de dominio establecido por un servicio o en el constructor.

public DomainObject(delegate getApplicationsByBatchID) 
{ 
    ... 
} 
+0

Buena idea, me gusta. –

+0

De acuerdo, entonces para esta instancia haría el trabajo que requiere llamadas al repositorio en la clase de servicio, luego pasaré una colección o un solo objeto a la entidad si se necesita un cálculo –

+0

Sí, el objeto de dominio usaría su delegado para obtener cualquier dato necesita, el delegado se establecerá por lo que crea el objeto de dominio. El delegado puede ser un método de servicio o un método de repositorio directamente. – mbillard

2

¿Por qué no pasar en un IList <VendorApplication> como parámetro en lugar de un VendorApplicationBatch? El código de llamada para esto presumiblemente provendría de un servicio que tendría acceso a AppRepo. De esta forma, el acceso a su repositorio estará en su lugar, mientras que la función de su dominio puede permanecer felizmente ignorante de dónde provienen esos datos.

+0

Si pongo el código que suma un lote en el repositorio, pero no el getbybatchid, no habría garantías de que las aplicaciones aprobadas sean de ese lote. Creo que el lote solo debería poder sumar las aplicaciones que contiene. –

+0

totaliza un lote en el repositorio -> totaliza un lote en el objeto de dominio por lotes –

+0

No estoy seguro de entender. Lo que sugiero es que guardes tu código como está, excepto que en lugar de pasar un VendorApplicationBatch, pasas la lista de aplicaciones. GetByBatchID todavía está en el repositorio, el código que los totales todavía está en el dominio. –

5

No soy un experto en DDD, pero recuerdo un artículo del gran Jeremy Miller que respondió a esta pregunta por mí. Normalmente querrías lógica relacionada con tus objetos de dominio, dentro de esos objetos, pero tu clase de servicio ejecutaría los métodos que contienen esta lógica.Esto ayudó a empujar la lógica específica de dominio en las clases de entidad, y guardan mis clases de servicio menos voluminoso (como me encontré poniendo a mucha lógica dentro de las clases de servicio como usted ha mencionado)

Editar: Ejemplo

I utilizar la biblioteca de la empresa para la validación simple, por lo que en la clase de entidad voy a establecer un atributo de este modo:

[StringLengthValidator(1, 100)] 
public string Username { 
    get { return mUsername; } 
    set { mUsername = value; } 
} 

la entidad hereda de una clase base que tiene el método siguiente "IsValid" que se asegurará de cada objeto se encuentra con el Criterios de validación

 public bool IsValid() 
    { 
     mResults = new ValidationResults(); 
     Validate(mResults); 

     return mResults.IsValid(); 
    } 

    [SelfValidation()] 
    public virtual void Validate(ValidationResults results) 
    { 
     if (!object.ReferenceEquals(this.GetType(), typeof(BusinessBase<T>))) { 
      Validator validator = ValidationFactory.CreateValidator(this.GetType()); 
      results.AddAllResults(validator.Validate(this)); 
     } 
     //before we return the bool value, if we have any validation results map them into the 
     //broken rules property so the parent class can display them to the end user 
     if (!results.IsValid()) { 
      mBrokenRules = new List<BrokenRule>(); 
      foreach (Microsoft.Practices.EnterpriseLibrary.Validation.ValidationResult result in results) { 
       mRule = new BrokenRule(); 
       mRule.Message = result.Message; 
       mRule.PropertyName = result.Key.ToString(); 
       mBrokenRules.Add(mRule); 
      } 
     } 
    } 

siguiente que necesitamos para ejecutar este método "IsValid" en la clase de servicio Método de salvar, así:

public void SaveUser(User UserObject) 
{ 
    if (UserObject.IsValid()) { 
     mRepository.SaveUser(UserObject); 
    } 
} 

Un ejemplo más complejo podría ser una cuenta bancaria. La lógica de depósito vivirá dentro del objeto de cuenta, pero la clase de servicio llamará a este método.

1

Según tengo entendido (no hay suficiente información para saber si este es el diseño correcto) VendorApplicationBatch debe contener un IList con carga lenta dentro del objeto de dominio, y la lógica debe permanecer en el dominio.

por ejemplo (código de aire):

public class VendorApplicationBatch { 

    private IList<VendorApplication> Applications {get; set;}; 

    public decimal CaculateBatchTotal() 
    { 
     if (Applications == null || Applications.Count == 0) 
      throw new ArgumentException("There were no applications for this batch, that shouldn't be possible"); 

     decimal Total = 0m; 
     foreach (VendorApplication App in Applications) 
      Total += App.Amount; 
     return Total; 
    } 
} 

Esto se hace fácilmente con un ORM como NHibernate y yo creo que sería la mejor solución.

+0

Estoy de acuerdo con usted en que esta es la mejor solución. Desafortunadamente, no estaba cerca cuando se tomó la decisión sobre cómo diseñar la capa de datos (o la falta de ella), por lo que no puedo usar un ORM, e hice un sistema de depósito kick-ass en su lugar. –

+0

Entonces la respuesta es hacer que el servicio obtenga la lista de aplicación de proveedor y el objeto de lote debe hacer la lógica. Ejemplo: VendorApplicationBatch.CaculateBatchTotal (IList applications) – gcores

+0

No creo que sea correcto permitir un lote para el total de aplicaciones que pueden pertenecer a otro lote, debe haber alguna forma de garantizar que un lote solo puede sumar su propio proveedor aplicaciones. Por lo tanto, no puedo de buena fe, poner esta función en el lote. –

0

Me parece que su CalculateTotal es un servicio para las colecciones de VendorApplication de, y que la devolución de la colección de VendorApplication de un lote adapta de forma natural como una propiedad de la clase de lote. Entonces, algún otro servicio/controlador/lo que sea recuperaría la colección apropiada de VendorApplication de un lote y los pasaría al servicio VendorApplicationTotalCalculator (o algo similar). Pero eso puede romper algunas reglas del servicio raíz agregado DDD o algo por el estilo que desconozco (novato DDD).

Cuestiones relacionadas