“不要在设计中使用抽象基类;但在建模/分析中”
本文关键字:建模 基类 抽象 | 更新日期: 2023-09-27 18:25:54
我是SOA的新手,尽管我在OOAD方面有一些经验。
SOA 设计的准则之一是"仅使用抽象类进行建模。从设计中省略它们"。使用抽象有助于建模(分析阶段(。
在分析阶段,我提出了一个 BankAccount 基类。从中衍生出的专门类别是"固定账户"和"储蓄账户"。我需要创建一个服务,该服务将返回用户的所有帐户(帐户列表(。满足要求的服务结构应该是什么?
注意:如果可以使用 WCF 提供代码演示,那就太好了。
听起来您正在尝试使用 SOA 远程访问您的对象模型。 最好查看希望服务公开的交互和功能,并避免公开服务实现的继承详细信息。
因此,在这种情况下,您需要一个用户帐户列表,您的界面将如下所示
[ServiceContract]
interface ISomeService
{
[OperationContract]
Collection<AccountSummary> 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
}
如果您决定沿继承路线走下去,则可以使用 KnownType 属性,但请注意,这会将一些类型信息添加到通过线路发送的消息中,这在某些情况下可能会限制互操作性。
更新:
当我回答时,我的时间有点有限,所以我会尝试详细说明为什么我更喜欢这种风格。
我不建议在单独的层中通过 DTO 公开您的 OOAD,这通常会导致一个臃肿的界面,您可以在其中传递大量未使用的数据,并虔诚地将其映射到本质上是您的域模型的副本,删除了所有逻辑,我只是看不到价值。我通常围绕它公开的操作设计我的服务层,并使用 DTO 来定义服务交互。
使用基于公开操作而不是域模型的 DTO 有助于保持服务封装并减少与域模型的耦合。 通过不公开我的域模型,我不必为了序列化而对字段可见性或继承做出任何妥协。
例如,如果我将转移方法从一个帐户公开到另一个帐户,则服务接口将如下所示:
[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<Account>(request.FromAccountNumber);
//Assert that the caller is authorised to request transfer on this account
var to = repository.Load<Account>(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 允许我将此属性标记为非必需属性,这样我就不会中断所有其他客户端,但如果它是新操作的必需属性,则客户端只会在调用该操作时发现。
更糟糕的是,作为服务实施者,如果他们为我提供了当前余额会发生什么? 我应该相信它吗?
这里的一般规则是询问谁拥有数据,客户端或服务? 如果客户端拥有它,那么它可以将其传递给服务,并且在执行一些基本检查后,服务可以使用它。 如果服务拥有它,则客户端应仅传递足够的信息,以便服务检索所需的内容。 这允许服务维护其拥有的数据的一致性。
在此示例中,服务拥有帐户信息,用于查找该信息的密钥是帐号。 虽然服务可以验证金额(为正数、支持的货币等(,但这归客户所有,因此我们希望填充 DTO 上的所有字段。
总之,我已经看到它完成了所有 3 种方式,但围绕特定操作设计 DTO 是迄今为止从服务和消费者实现来看最成功的。 它允许操作独立发展,并且非常明确地说明服务的期望以及将返回给客户端的内容。
我几乎同意其他人在这里所说的,但可能需要添加这些:
- 大多数 SOA 系统使用 Web 服务进行通信。Web 服务通过 WSDL 公开其接口。WSDL 对继承没有任何了解。
- 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
值。