“No use la clase Base abstracta en Diseño; pero en Modelado / Análisis “

Soy novato en SOA aunque tengo cierta experiencia en OOAD.

Una de las pautas para el diseño de SOA es “Usar clases abstractas solo para modelado”. Omitirlos desde el diseño “. El uso de la abstracción puede ser útil en el modelado (fase de análisis).

Durante la fase de análisis, he llegado a una clase base de Cuenta bancaria. Las clases especializadas derivadas de él son “Cuenta Fija” y “Cuenta de Ahorro”. Necesito crear un servicio que devolverá todas las cuentas (lista de cuentas) para un usuario. ¿Cuál debería ser la estructura del servicio (s) para cumplir con el requisito?

Nota: Sería genial si puede proporcionar una demostración de código usando WCF .

Parece que está intentando usar SOA para acceder de forma remota a su modelo de objetos. Sería mejor que observara las interacciones y las capacidades que desea que su servicio exponga y evite exponer los detalles de herencia de la implementación de sus servicios.

Entonces, en este caso donde necesita una lista de cuentas de usuario, su interfaz se parecería a

[ServiceContract] interface ISomeService { [OperationContract] Collection ListAccountsForUser( User user /*This information could be out of band in a claim*/); } [DataContract] class AccountSummary { [DataMember] public string AccountNumber {get;set;} [DataMember] public string AccountType {get;set;} //Other account summary information } 

Si decide ir por la ruta de herencia, puede usar el atributo KnownType , pero tenga en cuenta que esto agregará cierta información de tipo en el mensaje que se envía a través del cable, lo que puede limitar su interoperabilidad en algunos casos.

Actualización :

Estaba un poco limitado por el tiempo anterior cuando respondí, así que trataré de explicar por qué prefiero este estilo.

No aconsejaría exponer su OOAD a través de DTOs en una capa separada, esto generalmente lleva a una interfaz inflada en la que pasa una gran cantidad de datos que no se usan y, religiosamente, los mapea y traza como una copia de su modelo de dominio. con toda la lógica borrada, y simplemente no veo el valor. Normalmente diseño mi capa de servicio alrededor de las operaciones que expone y utilizo los DTO para la definición de las interacciones del servicio.

El uso de DTO basados ​​en operaciones expuestas y no en el modelo de dominio ayuda a mantener el encapsulamiento del servicio y reduce el acoplamiento al modelo de dominio. Al no exponer mi modelo de dominio, no tengo que comprometer la visibilidad del campo o la herencia en aras de la serialización.

por ejemplo, si expongo un método de transferencia de una cuenta a otra, la interfaz de servicio se vería así:

 [ServiceContract] interface ISomeService { [OperationContract] TransferResult Transfer(TransferRequest request); } [DataContract] class TransferRequest { [DataMember] public string FromAccountNumber {get;set;} [DataMember] public string ToAccountNumber {get;set;} [DataMember] public Money Amount {get;set;} } class SomeService : ISomeService { TransferResult Transfer(TransferRequest request) { //Check parameters...omitted for clarity var from = repository.Load(request.FromAccountNumber); //Assert that the caller is authorised to request transfer on this account var to = repository.Load(request.ToAccountNumber); from.Transfer(to, request.Amount); //Build an appropriate response (or fault) } } 

ahora desde esta interfaz, es muy claro para el conusmer qué son los datos requeridos para llamar a esta operación. Si implementé esto como

 [ServiceContract] interface ISomeService { [OperationContract] TransferResult Transfer(AccountDto from, AccountDto to, MoneyDto dto); } 

y AccountDto es una copia de los campos en la cuenta, como consumidor, ¿qué campos debo rellenar? ¿Todos ellos? Si se agrega una nueva propiedad para admitir una nueva operación, todos los usuarios de todas las operaciones ahora pueden ver esta propiedad. WCF me permite marcar esta propiedad como no obligatoria para que no rompa todos mis otros clientes, pero si es obligatoria para la nueva operación, el cliente solo se enterará cuando llame a la operación.

Peor aún, como implementador del servicio, ¿qué sucede si me han proporcionado un saldo actual? Debería confiar en eso?

La regla general aquí es preguntar quién posee los datos, el cliente o el servicio. Si el cliente lo posee, puede pasarlo al servicio y después de hacer algunas comprobaciones básicas, el servicio puede usarlo. Si el servicio lo posee, el cliente solo debe pasar suficiente información para que el servicio recupere lo que necesita. Esto permite que el servicio mantenga la consistencia de los datos que posee.

En este ejemplo, el servicio posee la información de la cuenta y la clave para localizarlo es un número de cuenta. Si bien el servicio puede validar la cantidad (es positiva, moneda compatible, etc.), es propiedad del cliente y, por lo tanto, esperamos que todos los campos en el DTO se llenen.

En resumen, lo he visto hacerlo de las 3 formas, pero el diseño de DTO en operaciones específicas ha sido, con mucho, el más exitoso tanto de las implementaciones de servicios como de los consumidores. Permite que las operaciones evolucionen de manera independiente y es muy explícita sobre lo que espera el servicio y lo que se devolverá al cliente.

Creo que la mejor práctica aquí es no utilizar la herencia en sus contratos de datos:

 [DataContract] class SavingsAccount { public string AccountNr { ... } .... } [DataContract] class FixedAccount { public string AccountNr { ... } .... } 

Lo cual funciona bien para la mayoría de los escenarios pero no para obtener una lista de cuentas mixtas.
Si eso es realmente esencial, considere:

 [DataContract] class AccountDTO { public string AccountType { ... } public string AccountNr { ... } .... } 

que es esencialmente la respuesta de @ jageall.

Me gustaría mucho más con lo que otros han dicho aquí, pero probablemente necesite agregar estos:

  • La mayoría de los sistemas SOA usan los servicios web para la comunicación. Los servicios web exponen su interfaz a través de WSDL. WSDL no tiene ninguna comprensión de la herencia.
  • Todo el comportamiento en sus DTO se perderá cuando crucen el cable
  • Todos los campos privados / protegidos se perderán cuando crucen el cable

Imagine este escenario (el caso es tonto pero ilustrativo):

 public abstract class BankAccount { private DateTime _creationDate = DateTime.Now; public DateTime CreationDate { get { return _creationDate; } set { _creationDate = value; } } public virtual string CreationDateUniversal { get { return _creationDate.ToUniversalTime().ToString(); } } } public class SavingAccount : BankAccount { public override string CreationDateUniversal { get { return base.CreationDateUniversal + " UTC"; } } } 

Y ahora ha utilizado “Agregar referencia de servicio” o “Agregar referencia web” en su cliente (y no reutilizar los ensamblajes) para acceder a la cuenta de ahorro.

 SavingAccount account = serviceProxy.GetSavingAccountById(id); account.CreationDate = DateTime.Now; var creationDateUniversal = account.CreationDateUniversal; // out of sync!! 

Lo que sucederá es que los cambios en CreationDate no serán reciprocados en CreationDateUniversal ya que no hay una implementación cruzada, solo el valor de CreationDateUniversal en el momento de la serialización en el servidor .