9

尽管我在 OOAD 方面有一些经验,但我是 SOA 的新手。

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

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

注意:如果您可以使用WCF提供代码演示,那就太好了。

4

2 回答 2

8

听起来您正在尝试使用 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 有助于保持服务封装并减少与域模型的耦合。通过不公开我的域模型,我不必为了序列化而对字段可见性或继承做出任何妥协。

例如,如果我将 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<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)
    }
}

现在从这个界面,消费者很清楚调用这个操作所需的数据是什么。如果我将其实现为

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

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

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

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

在此示例中,服务拥有帐户信息,找到它的关键是帐号。虽然服务可能会验证金额(为正数、支持的货币等),但它归客户所有,因此我们希望填充 DTO 上的所有字段。

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

于 2012-02-28T07:15:12.470 回答
1

我会很赞同其他人在这里所说的话,但可能需要添加以下内容:

  • 大多数 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 在服务器序列化时的值。

于 2012-02-28T10:31:16.310 回答