DDD访问外部信息的方法

时间:2012-06-28 09:19:52

标签: c# .net design-patterns domain-driven-design solid-principles

我有一个现有的银行应用程序类,如下所示。银行帐户可以是SavingsBankAccount或FixedBankAccount。有一个名为IssueLumpSumInterest的操作。对于FixedBankAccount,仅当帐户的所有者没有其他帐户时才需要更新余额。

这要求FixedBankAccount对象了解帐户所有者的其他帐户。如何按照 SOLID / DDD / GRASP /信息专家模式执行此操作?

namespace ApplicationServiceForBank
{

public class BankAccountService
{
    RepositoryLayer.IRepository<RepositoryLayer.BankAccount> accountRepository;
    ApplicationServiceForBank.IBankAccountFactory bankFactory;

    public BankAccountService(RepositoryLayer.IRepository<RepositoryLayer.BankAccount> repo, IBankAccountFactory bankFact)
    {
        accountRepository = repo;
        bankFactory = bankFact;
    }

    public void IssueLumpSumInterest(int acccountID)
    {
        RepositoryLayer.BankAccount oneOfRepositroyAccounts = accountRepository.FindByID(p => p.BankAccountID == acccountID);

        int ownerID = (int) oneOfRepositroyAccounts.AccountOwnerID;
        IEnumerable<RepositoryLayer.BankAccount> accountsForUser = accountRepository.FindAll(p => p.BankUser.UserID == ownerID);

        DomainObjectsForBank.IBankAccount domainBankAccountObj = bankFactory.CreateAccount(oneOfRepositroyAccounts);

        if (domainBankAccountObj != null)
        {
            domainBankAccountObj.BankAccountID = oneOfRepositroyAccounts.BankAccountID;
            domainBankAccountObj.AddInterest();

            this.accountRepository.UpdateChangesByAttach(oneOfRepositroyAccounts);
            //oneOfRepositroyAccounts.Balance = domainBankAccountObj.Balance;
            this.accountRepository.SubmitChanges();
        }
    }
}

public interface IBankAccountFactory
{
    DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount);
}

public class MySimpleBankAccountFactory : IBankAccountFactory
{
    public DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount)
    {
        DomainObjectsForBank.IBankAccount acc = null;

        if (String.Equals(repositroyAccount.AccountType, "Fixed"))
        {
            acc = new DomainObjectsForBank.FixedBankAccount();
        }

        if (String.Equals(repositroyAccount.AccountType, "Savings"))
        {
            //acc = new DomainObjectsForBank.SavingsBankAccount();
        }

        return acc;
    }
}

}

namespace DomainObjectsForBank
{

public interface IBankAccount
{
    int BankAccountID { get; set; }
    double Balance { get; set; }
    string AccountStatus { get; set; }
    void FreezeAccount();
    void AddInterest();
}

public class FixedBankAccount : IBankAccount
{
    public int BankAccountID { get; set; }
    public string AccountStatus { get; set; }
    public double Balance { get; set; }

    public void FreezeAccount()
    {
        AccountStatus = "Frozen";
    }

    public void AddInterest()
    {
        //TO DO: Balance need to be updated only if the person has no other accounts.
        Balance = Balance + (Balance * 0.1);
    }
}

}

阅读

  1. Issue in using Composition for “is – a “ relationship

  2. 实现业务逻辑(LINQ to SQL) http://msdn.microsoft.com/en-us/library/bb882671.aspx

  3. 构建LINQ to SQL应用程序

  4. 使用LINQ to SQL探索N层体系结构 http://randolphcabral.wordpress.com/2008/05/08/exploring-n-tier-architecture-with-linq-to-sql-part-3-of-n/

  5. Confusion between DTOs (linq2sql) and Class objects!

  6. Domain Driven Design (Linq to SQL) - How do you delete parts of an aggregate?

4 个答案:

答案 0 :(得分:2)

我注意到的第一件事是银行账户工厂的使用不当。存储库应该使用工厂来创建基于从数据存储中检索的数据的实例。因此,对accountRepository.FindByID的调用将返回FixedBankAccount或SavingsBankAccount对象,具体取决于从数据存储返回的AccountType。

如果兴趣仅适用于FixedBankAccount实例,则可以执行类型检查以确保使用正确的帐户类型。

public void IssueLumpSumInterest(int accountId)
{
    var account = _accountRepository.FindById(accountId) as FixedBankAccount;

    if (account == null)
    {
        throw new InvalidOperationException("Cannot add interest to Savings account.");
    }

    var ownerId = account.OwnerId;

    if (_accountRepository.Any(a => (a.BankUser.UserId == ownerId) && (a.AccountId != accountId)))
    {
        throw new InvalidOperationException("Cannot add interest when user own multiple accounts.");
    }

    account.AddInterest();

    // Persist the changes
}

注意:FindById应该只接受ID参数而不是lambda / Func。您已通过名称“FindById”指示将如何执行搜索。 “accountId”值与BankAccountId属性进行比较的事实是隐藏在方法中的实现细节。如果您想要使用lambda的通用方法,请将方法命名为“FindBy”。

如果所有实现都不支持该行为,我也不会将AddInterest放在IBankAccount接口上。考虑一个单独的IInterestEarningBankAccount接口,它公开AddInterest方法。我还考虑在上面的代码中使用该接口而不是FixedBankAccount,以便在将来添加支持此行为的其他帐户类型时,使代码更易于维护和扩展。

答案 1 :(得分:2)

从阅读您的要求,我就是这样做的:

//Application Service - consumed by UI
public class AccountService : IAccountService
{
    private readonly IAccountRepository _accountRepository;
    private readonly ICustomerRepository _customerRepository;

    public ApplicationService(IAccountRepository accountRepository, ICustomerRepository customerRepository)
    {
        _accountRepository = accountRepository;
        _customerRepository = customerRepository;
    }

    public void IssueLumpSumInterestToAccount(Guid accountId)
    {
        using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
        {
            Account account = _accountRepository.GetById(accountId);
            Customer customer = _customerRepository.GetById(account.CustomerId);

            account.IssueLumpSumOfInterest(customer);

            _accountRepository.Save(account);
        }
    }
}

public class Customer
{
    private List<Guid> _accountIds;

    public IEnumerable<Guid> AccountIds
    {
        get { return _accountIds.AsReadOnly();}
    }
}

public abstract class Account
{
    public abstract void IssueLumpSumOfInterest(Customer customer);
}

public class FixedAccount : Account
{
    public override void  IssueLumpSumOfInterest(Customer customer)
    {
        if (customer.AccountIds.Any(id => id != this._accountId))
            throw new Exception("Lump Sum cannot be issued to fixed accounts where the customer has other accounts");

        //Code to issue interest here
    }
}   

public class SavingsAccount : Account
{
    public override void  IssueLumpSumOfInterest(Customer customer)
    {
        //Code to issue interest here
    }
}
  1. 帐户聚合上的 IssueLumpSumOfInterest 方法要求客户聚合帮助决定是否应该发放利息。
  2. 客户汇总包含帐户ID列表 - 帐户汇总列表。
  3. 基类“帐户”具有多态方法 - FixedAccount检查客户没有任何其他帐户 - SavingsAccount不会执行此检查。

答案 2 :(得分:1)

2分钟扫描回答..

  • 不确定为什么需要BankAccount的2个表示 RepositoryLayer.BankAccount DomainObjectsForBank.IBankAccount 。隐藏连接的持久层...只处理服务中的域对象。
  • 不要传递/返回Nulls - 我认为这是一个很好的建议。
  • finder方法看起来像LINQ方法,它从集合列表中选择项目。您的方法看起来像是想要获得第一个匹配并退出..在这种情况下,您的参数可以是简单的原语(Ids)和lambdas。

一般的想法似乎是正确的。该服务封装了此事务的逻辑 - 而不是域对象。如果此更改,则只需更新一个位置。

public void IssueLumpSumInterest(int acccountID)
{
    var customerId = accountRepository.GetAccount(accountId).CustomerId;

    var accounts = accountRepository.GetAccountsForCustomer(customerId);
    if ((accounts.First() is FixedAccount) && accounts.Count() == 1)
    {
       // update interest    
    }
}

答案 3 :(得分:0)

让我感到奇怪的事情:

  • 您的IBankAccount有方法FreezeAccount,但我认为所有帐户的行为都非常相似?也许有一个BankAccount类可以实现一些接口吗?
  • AccountStatus应该是一个枚举?如果帐户是“Forzen”会怎么样?