领域模型和"业务逻辑"混乱

时间:2015-04-10 09:35:14

标签: mvvm architecture domain-driven-design business-logic domain-model

每当我阅读有关MVVM或DDD等现代设计模式的文章时,我都无法将该示例转换为我正在处理的域名。

所有这些模式得出的结论是,域模型应该存在于它们自己的小泡沫中,没有任何引用,不应该暴露给视图进行绑定,应该是POCO / POJO并包含“业务逻辑”。

我一直在问自己的问题是:域模型应该做什么?

答案显然是“处理业务逻辑”,但当我想到可能是什么时,我无法找到真实世界的示例。

例如:总是出现的一个典型示例是财务应用程序,您可以在其中拥有BankAccount实体,该实体可以具有TransferMoneyTo(otherAccount)功能。 这在理论上听起来不错,但在现实世界中,这个应用程序不会管理世界上所有的银行账户,而只管理一家银行的账户。 因此,真实世界的应用程序必须以某种方式联系另一家银行的服务器以启动此交易。这种“某种程度上”显然是服务BankAccount不允许引用。这意味着这对于孤立域模型来说不是一个很好的例子。

到目前为止,我所读过的所有例子都是关于这样的地方,这个例子只在哪里工作,因为它忽略了重要的细节或琐碎。我的意思是,“商业逻辑”只是简单的验证(例如必填字段)。

这一切导致anemic domain model(除了验证),这应该是一件坏事。

我的问题是:除了验证之外,术语“业务逻辑”背后隐藏着什么,这证明需要单独的域模型?

注意:我知道这取决于您正在处理的域名,但我认为至少有一些DDD实际上有用的示例将会受到赞赏。

3 个答案:

答案 0 :(得分:2)

  

术语"业务逻辑"

背后隐藏着什么

许多域模型反映业务流程,因此包含状态机,您可以根据某些规则将事物从已知的有效状态转换到另一个状态。几乎每个企业都能获得这种流程。其他域可能涉及更复杂的内部算法和数据转换。

这些几乎不属于简单的" 只是验证"类别,除非您将铁路公司的座位预订系统或政府的税务计算流程视为" 验证"。

  

这"以某种方式"显然是BankAccount不是的服务   允许引用

关于与外界沟通的域名,它并不是真正的责任。一般来说,你所拥有的是一个发出事件的域名"这发生了!"应用程序上下文处理它并启动与外部系统的适当通信。

协调对内部和外部子系统的调用,使数据流入,流出和流经应用程序不是域逻辑,它是技术应用程序级别的关注点。控制反转,以某种形式(事件,DI等),通常是保持域不知道这一点的关键。

答案 1 :(得分:0)

我有一些用php编写的足球比赛管理软件(不完全是oop的顶峰),

我的商业逻辑包括一个排名计算器,根据游戏结果计算出团队的位置。打破平局的策略可能有点牵扯。其他业务逻辑包括游戏安排,裁判分配和志愿者协调。

这个逻辑位于我认为是域层的地方。我的实体本身(游戏等)往往是贫血的。除了保存数据之外,他们没什么可做的。但这对我没问题。这是真正的工作所在的服务类。

我使用或有界上下文和聚合根概念。我的游戏实体包括团队,官员和一个领域。当我在游戏环境中时,游戏就是王者,并负责它的孩子。在现场管理环境中,该领域是老板,而游戏就是这样的。

我实现了持久性独立。我可以对我的域实体和值对象进行建模以反映域要求,而不必担心它们最终会出现什么表.ORM层负责映射并允许我在多个表中存储一个域实体,反之亦然。

关键是,从DDD中选择可以帮助您特定应用的东西。不要担心其余的事情。

答案 2 :(得分:0)

  

这种“某种方式”显然是BankAccount不是的服务   允许引用。这意味着这不会是一个   隔离域模型的一个很好的例子。

虽然BankAccount本身没有对此服务的引用,但它仍然可以与此类服务进行交互。

更简单的例子,我们来计算兴趣。天真的方法可能是:

public BankAccount 
{
    public decimal Balance { get; set; }
    public decimal Interest { get; set; }
    private public List<Transaction> transactions = new List<Transaction>();
    public List<Transaction> Transactions { get { return transactions; } } 

    public decimal CalculateInterest() 
    {
        return Balance * Interest;
    }
}

// inside a service
BankAccount account = ...;
var interest = account.CalculateInterest();
account.Balance += interest;
account.AddTransaction(new Transaction() { Description = "Monthly Interest", Amount = interest });

哪个不好,因为现在你的责任不一。计算利息不是BankAccount类的重点,它现在涉及多个职责,如计算,这可能会改变或取决于几个因素。

public BankAccount 
{
    // private setters, so no one outside BankAccount can update it directly
    public decimal Balance { get; private set; }
    public AccountType AccountType { get; private set; } // assume business and private account
    private public List<Transaction> transactions = new List<Transaction>();
    // return as "AsEnumerable" so user can't later cast it back to list and
    // directly add Transactions, skipping the AddTransaction method
    public IEnumerable<Transaction> Transactions { get { return transactions.AsEnumerable(); } } 

    public void CalculateInterest(IInterestCalculator calc) 
    {
        decimal interest = calc.CalculateInterest(this);
        this.AddTransaction(new Transaction() { Description = "Monthly Interest", Amount = interest });
    }

    public void AddTransaction(Transaction transaction) 
    {
        var newBalance = this.Balance + transaction.Balance;

        if(this.transaction.Amount < 0 && newBalance < this.Limit) 
        {
            // new balance would exceed the accounts limit
            throw new NotEnoughFundsException();
        }

        this.transactions.Add(transaction);
        this.Balance = newAmount;
    }
}

public interface IInterestCalculator 
{
    decimal CalculateInterest(Bankaccount);
}

public class DefaultAccountInterestCalculator : IInterestCalculator
{
    public decimal CalculateInterest(BankAccount account) 
    {
        // for sake of simplicity, inlined
        return account.Balance * 0.02;
    }
}
public class PremiumAccountInterestCalculator : IInterestCalculator 
{
    private const decimal Threshold = 10000m;
    public decimal CalculateInterest(BankAccount account) 
    {
        // Premium customers receive increased interest, above a certain threshold. 3% for the balance above the threshold of 10000 USD
        if(account.Balance > Threshold) 
        {
            return (decimal)((Threshold * 0.02) + (account.Balance-Threshold) * 0.03);
        } 
        else 
        {
            return (decimal)(account.Balance * 0.02);
        }
    }
}

使用

进行服务
BankAccount account = ...;
IInterestCalculator calculator = (account.AccountType == AccountType.Premium)?new PremiumAccountInterestCalculator():DefaultAccountInterestCalculator();

BankAccount account.CalculateInterest(calculator);

现在你的BankAccount班只有一个责任,维持它的状态和所需的业务逻辑(即检查是否有足够的余额,只允许通过方法操纵银行账户而不是直接改变{{ 1}}或操纵Balance

计算由计算器类完成,计算器类传递给List<Transaction>的{​​{1}}方法。该服务包含所需的逻辑,既不适合计算器也不适用于BankAccount类。

简而言之:业务逻辑(在富域模型中)是类所需的逻辑,以保持其状态并尽可能地封装它。在第二类中,不可能直接改变平衡。需要BankAccount或者需要CalculateInterest(用于计算利息)。

这保证(假设它是并发安全AddTransactionCalculateInterest将始终处于一致状态(即永远不会丢失以添加事务或更新余额)。