“不要在Design中使用Abstract Base类; 但在建模/分析中“

虽然我在OOAD有一些经验,但我是SOA的新手。

SOA设计的一个指导原则是“仅使用抽象类进行建模。 从设计中省略它们。 抽象的使用可以有助于建模(分析阶段)。

在分析阶段,我想出了一个BankAccount基类。 从中派生的专业类是“FixedAccount”和“SavingsAccount”。 我需要创建一个服务,返回用户的所有帐户(帐户列表)。 应满足要求的服务结构应该是什么?

注意:如果您可以使用WCF提供代码演示,那将会很棒。

听起来您正在尝试使用SOA来远程访问对象模型。 您最好查看希望服务公开的交互和function,并避免暴露服务实现的inheritance详细信息。

因此,在这种情况下,您需要一个用户帐户列表,您的界面看起来就像

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

如果你决定继续沿着inheritance路由,你可以使用KnownType属性 ,但要注意这会在通过网络发送的消息中添加一些类型信息,这可能会限制你在某些情况下的互操作性。

更新

当我回答时,我的时间有点受限,所以我会试着详细说明为什么我更喜欢这种风格。

我不建议通过DTO在单独的图层中暴露你的OOAD,这通常会导致一个膨胀的界面,你传递大量未使用的数据,并将其直接映射到你的域模型的副本中删除所有逻辑,我只是​​没有看到值。 我通常围绕它公开的操作设计我的服务层,并使用DTO来定义服务交互。

使用基于公开操作而不是域模型的DTO有助于保持服务封装并减少与域模型的耦合。 通过不暴露我的域模型,我不必为了序列化而在字段可见性或inheritance上做出任何妥协。

例如,如果我将Transfer方法从一个帐户暴露给另一个帐户,那么服务接口将如下所示:

 [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) } } 

现在从这个界面可以清楚地看到conusmer调用此操作所需的数据是什么。 如果我实现这个

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

和AccountDto是帐户中的字段的副本,作为消费者,我应填充哪些字段? 他们都是? 如果添加新属性以支持新操作,则所有操作的所有用户现在都可以看到此属性。 WCF允许我将此属性标记为非强制性,这样我就不会破坏所有其他客户端,但如果新操作必须使用,则客户端只会在调用操作时找到它。

更糟糕的是,作为服务实施者,如果他们为我提供了当前余额会发生什么? 我应该相信吗?

这里的一般规则是询问谁拥有数据,客户或服务? 如果客户端拥有它,那么它可以将它传递给服务,并且在进行一些基本检查之后,服务可以使用它。 如果服务拥有它,则客户端应仅传递足够的服务信息以检索它所需的内容。 这允许服务维护其拥有的数据的一致性。

在此示例中,服务拥有帐户信息,并且用于定位帐户的密钥是帐号。 虽然服务可以validation金额(是正数,支持的货币等),但这是由客户拥有的,因此我们期望填充DTO上的所有字段。

总而言之,我已经看到它完成了所有3种方式,但围绕特定操作设计DTO是迄今为止最成功的服务和​​消费者实现。 它允许操作独立发展,并且非常明确地表示服务的期望以及将返回给客户端的内容。

我认为这里的最佳做法是不在数据合同中使用inheritance:

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

哪种方法适用于大多数情况,但不适用于获取混合帐户列表。
如果这非常重要,请考虑:

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

这基本上是@ jageall的答案。

我会对其他人在这里说过的内容非常感兴趣,但可能需要添加以下内容:

  • 大多数SOA系统使用Web服务进行通信。 Web Services通过WSDL公开其接口。 WSDL对inheritance没有任何理解。
  • DTO中的所有行为在穿过电线时都会丢失
  • 所有私有/受保护的字段在通过电线时都会丢失

想象一下这种情况(案例很愚蠢但是说明性):

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

现在,您已在客户端上使用“添加服务引用”“添加Web引用” (而不是重新使用程序集)来访问保存帐户。

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

将要发生的事情是对CreationDate的更改将不会回复到CreationDateUniversal因为没有实现跨越线路,只有在服务器序列化时 CreationDateUniversal的值。