Estoy tratando de descubrir cómo mantener las invariantes constantes para algunos consumidores (clientes comerciales) del proyecto que tienen sus propios requisitos en la misma versión de la raíz agregada. Tomemos al Cliente como ejemplo y hagamos una pregunta hipotética para cumplir con la siguiente lógica tonta:
public class Customer { public Id { get; private set;} public string Name { get; private set;} public void SetName(string name){ //client1 -> requires the name not to be null //client2 -> requires the name can start with "J" //client3 -> some other business logic this.Name = name; } }Por ahora, lo que tengo en mente es tener la lógica de estrategia de verificación invariable/validación personalizada de la siguiente manera:
public void SetName(string name, INameCheckStrategy strategy){ if(!strategy.IsSatisfiedBy(name)) throw new BusinessException("name does not meet the invariant check!"); this.Name = name; }donde
public class Client1NameCheckStrategy : INameCheckStrategy { public bool IsSatisfiedBy(string name){ return name != null; } }¿Alguna idea de cómo manejar tal problema?
Podría adoptar una especie de enfoque DDDD ("Dynamic Domain Driven Design", para acuñar una frase) y cosificar las reglas en su dominio en agregados propios y asociarlos con los clientes. Si va a modelar el cambio de reglas, eso podría hacer las cosas interesantes (¿cómo maneja un cambio que hace que los agregados se consideren válidos por una regla anterior?).
Si las entidades del modelo de dominio siguen siendo las mismas con respecto a los datos y los datos tienen el mismo significado para todos los clientes comerciales, entonces usar algo como el patrón de estrategia (como sugirió) parece ser una buena opción. Solo asegúrese de no permitir que nada, como la infraestructura de configuración, se filtre en su modelo de dominio y sea estricto al inyectar la información requerida y la lógica específica del cliente.
Si hay varios lugares en su agregado donde se aplica esta lógica específica, también podría considerar dejar que el repositorio (o una fábrica) inyecte la estrategia al consultar los agregados de la colección del repositorio.
Otra opción sería ir con implementaciones específicas de objetos de valor que ya deberían contener las invariantes comerciales para sus datos de todos modos. En su ejemplo fabricado, podría haber diferentes tipos de objetos de valor CustomerName (por ejemplo, CustomerNameClientX). Dependiendo de quién sea el cliente actual, puede asegurarse de que se crea el objeto de valor de nombre de cliente correspondiente, que ya se ha validado durante la creación y se pasa al agregado.
Sé que ha preguntado sobre C# e incluso mencionó DDD, pero si me piden que lo ayude, le daría un ejemplo de JS.
function setName(name) { this.name = name; }No hay comprobaciones de tipo, y no son necesarias, porque limitarse ANTES de necesitar más seguridad es un mal hábito.
Mire, acabo de usar esta propiedad sin verificar su tipo, y todos mis casos comerciales están cubiertos:
function insertToMongoByName(db, collectionName, record) { console.log(`Saving record by name: ${record.name}`); record._id = record.name; db.collection(collectionName).insertOne(record); } Mongodb necesita que _id esté presente, por lo que he satisfecho este invariante.
console.log puede funcionar literalmente con cualquier cosa, como muchas otras bibliotecas en JS (también vea la función insertOne que he usado).
Y aquí hay otro uso del nombre:
_ = require("lodash"); function getEmailWithDisplayName(customer) { if(customer.fullEmail) { return customer.fullEmail; } if(!customer.email) { return undefined; } if(_.isString(customer.name)) { return `${customer.name} <${customer.email}>`; } else if(_.isObject(customer.name) && (customer.name.firstName || customer.name.lastName)) { const name = [customer.name.honorific, customer.name.firstName, customer.name.lastName] .filter(s => _.isString(s)).join(" "); return `${name} <${customer.email}>`; } return customer.email; } Esta vez tuve que hacer algunas comprobaciones de tipos alrededor de name , pero es muy específico para el objetivo que estaba tratando de lograr.
Después de esto, mi pregunta a su arquitectura es: ¿realmente desea arreglar su clase de Customer para admitir todos los posibles requisitos futuros? ¿Todos los casos de uso futuros estarán felices de proporcionar una "estrategia" para los controles, en lugar de seguir adelante con lo que tienen en mente?