域模型和 "Business Logic" 混淆
Domain Model and "Business Logic" confusion
每当我阅读有关现代设计模式(如 MVVM 或 DDD)的文章时,我都无法将示例转换为我通常工作的领域。
所有这些模式得出的结论是领域模型应该存在于它们自己的小气泡中,不引用任何东西,不应该暴露给绑定视图,应该是 POCOs/POJOs 并且包含 "business logic".
我经常问自己的问题是:领域模型应该做什么?
答案显然是 "Handle business logic",但当我思考这可能是什么时,我很难找到 真实世界 的例子。
例如:一个经常出现的典型例子是财务应用程序,其中您可以有一个 BankAccount
实体,它可以有一个 TransferMoneyTo(otherAccount)
函数。
这在理论上听起来不错,但在现实世界中,这个应用程序不会管理世界上所有的银行账户,而只会管理一家银行的账户。
因此,真实世界的应用程序必须以某种方式 联系其他银行的服务器以启动此交易。此 "somehow" 显然是一个 服务 ,BankAccount
不允许对其进行引用。这意味着这对于隔离域模型来说不是一个很好的例子。
到目前为止,我读过的所有示例都是因为它忽略了重要的细节或琐碎的地方,例如这样的地方,该示例只起作用的地方。我的意思是,"business logic" 只是简单的验证(例如必填字段)。
所有这些都会导致 anemic domain model(除了验证之外),这应该是一件坏事。
我的问题是:除了验证之外,"business logic" 一词背后还有什么可以证明需要单独的领域模型?
注意:我知道这取决于您正在处理的领域,但我认为至少有一些 DDD 实际有用的例子会受到赞赏。
What hides behind the term "business logic"
许多领域模型反映了业务流程,因此包含状态机,您可以在其中根据某些规则将事物从已知的有效状态转换到另一个有效状态。您几乎在每个企业中都会遇到这种流程。其他领域可能涉及更复杂的内部算法和数据转换。
这些很难归入简单化的“只是验证”的范畴,除非你将铁路公司的座位预订系统或政府的税收计算过程视为“验证” ".
This "somehow" is obviously a service to which the BankAccount is not
allowed to have a reference to
关于与外界通信的域,这不是他们的责任。通常,您所拥有的是域发出一个事件 "this happened !" 并且应用上下文处理它并启动与外部系统的适当通信。
编排对内部和外部子系统的调用,以便数据流入、流出和流经应用程序不是域逻辑,而是技术应用程序级别的问题。以一种或另一种形式(事件、DI 等)的控制反转通常是使域不了解这一点的关键。
我有一些用 php 编写的足球锦标赛管理软件(不完全是 oop 的巅峰之作),
我的业务逻辑包括一个排名计算器,它根据球队的比赛结果计算出球队的排名。打破平局的策略可能有点复杂。其他业务逻辑包括比赛安排、裁判分配和志愿者协调。
这个逻辑位于我认为是领域层的地方。我的实体本身(游戏等)往往是贫血的。除了保存数据外,他们无事可做。但这对我来说很好。这是服务 类 真正发挥作用的地方。
我使用或限界上下文和聚合根概念。我的游戏实体包括团队、官员和场地。当我处于游戏环境中时,游戏为王并负责 children。在现场管理环境中,现场是老板,比赛只是顺风顺水。
我实现了持久独立。我可以对我的域实体和值 objects 进行建模以反映域需求,而不必担心它们最终会出现在哪些表中。ORM 层负责映射并允许我跨多个表存储一个域实体,反之亦然相反。
重点是,从 DDD 中挑选对您的特定应用程序有帮助的东西。其他的不用担心。
This "somehow" is obviously a service to which the BankAccount
is not
allowed to have a reference to. This means that this would not be a
very good example for an isolated domain model.
虽然 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 class 的重点,它现在涉及多项职责,例如计算,这可能会改变或取决于多种因素。
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
class 只有一个责任,维护它的状态和为此所需的业务逻辑(即检查是否有足够的余额,只允许通过方法操纵银行账户而不是直接更改 Balance
或操纵 List<Transaction>
.
计算由计算器classes完成,传递给BankAccount
的CalculateInterest
方法。该服务包含既不适合计算器也不适合 BankAccount class 的所需逻辑。
简而言之:业务逻辑(在丰富的域模型中)是 class 维护其状态并尽可能封装它所需的所有逻辑。在第二个 class 中,无法直接更改余额。需要 AddTransaction
或 CalculateInterest
(用于利息计算)。
这保证(假设它是 并发安全)Balance
和 Transactions
将始终处于一致状态(即永远不会错过添加任何一个事务或更新余额)。
每当我阅读有关现代设计模式(如 MVVM 或 DDD)的文章时,我都无法将示例转换为我通常工作的领域。
所有这些模式得出的结论是领域模型应该存在于它们自己的小气泡中,不引用任何东西,不应该暴露给绑定视图,应该是 POCOs/POJOs 并且包含 "business logic".
我经常问自己的问题是:领域模型应该做什么?
答案显然是 "Handle business logic",但当我思考这可能是什么时,我很难找到 真实世界 的例子。
例如:一个经常出现的典型例子是财务应用程序,其中您可以有一个 BankAccount
实体,它可以有一个 TransferMoneyTo(otherAccount)
函数。
这在理论上听起来不错,但在现实世界中,这个应用程序不会管理世界上所有的银行账户,而只会管理一家银行的账户。
因此,真实世界的应用程序必须以某种方式 联系其他银行的服务器以启动此交易。此 "somehow" 显然是一个 服务 ,BankAccount
不允许对其进行引用。这意味着这对于隔离域模型来说不是一个很好的例子。
到目前为止,我读过的所有示例都是因为它忽略了重要的细节或琐碎的地方,例如这样的地方,该示例只起作用的地方。我的意思是,"business logic" 只是简单的验证(例如必填字段)。
所有这些都会导致 anemic domain model(除了验证之外),这应该是一件坏事。
我的问题是:除了验证之外,"business logic" 一词背后还有什么可以证明需要单独的领域模型?
注意:我知道这取决于您正在处理的领域,但我认为至少有一些 DDD 实际有用的例子会受到赞赏。
What hides behind the term "business logic"
许多领域模型反映了业务流程,因此包含状态机,您可以在其中根据某些规则将事物从已知的有效状态转换到另一个有效状态。您几乎在每个企业中都会遇到这种流程。其他领域可能涉及更复杂的内部算法和数据转换。
这些很难归入简单化的“只是验证”的范畴,除非你将铁路公司的座位预订系统或政府的税收计算过程视为“验证” ".
This "somehow" is obviously a service to which the BankAccount is not allowed to have a reference to
关于与外界通信的域,这不是他们的责任。通常,您所拥有的是域发出一个事件 "this happened !" 并且应用上下文处理它并启动与外部系统的适当通信。
编排对内部和外部子系统的调用,以便数据流入、流出和流经应用程序不是域逻辑,而是技术应用程序级别的问题。以一种或另一种形式(事件、DI 等)的控制反转通常是使域不了解这一点的关键。
我有一些用 php 编写的足球锦标赛管理软件(不完全是 oop 的巅峰之作),
我的业务逻辑包括一个排名计算器,它根据球队的比赛结果计算出球队的排名。打破平局的策略可能有点复杂。其他业务逻辑包括比赛安排、裁判分配和志愿者协调。
这个逻辑位于我认为是领域层的地方。我的实体本身(游戏等)往往是贫血的。除了保存数据外,他们无事可做。但这对我来说很好。这是服务 类 真正发挥作用的地方。
我使用或限界上下文和聚合根概念。我的游戏实体包括团队、官员和场地。当我处于游戏环境中时,游戏为王并负责 children。在现场管理环境中,现场是老板,比赛只是顺风顺水。
我实现了持久独立。我可以对我的域实体和值 objects 进行建模以反映域需求,而不必担心它们最终会出现在哪些表中。ORM 层负责映射并允许我跨多个表存储一个域实体,反之亦然相反。
重点是,从 DDD 中挑选对您的特定应用程序有帮助的东西。其他的不用担心。
This "somehow" is obviously a service to which the
BankAccount
is not allowed to have a reference to. This means that this would not be a very good example for an isolated domain model.
虽然 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 class 的重点,它现在涉及多项职责,例如计算,这可能会改变或取决于多种因素。
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
class 只有一个责任,维护它的状态和为此所需的业务逻辑(即检查是否有足够的余额,只允许通过方法操纵银行账户而不是直接更改 Balance
或操纵 List<Transaction>
.
计算由计算器classes完成,传递给BankAccount
的CalculateInterest
方法。该服务包含既不适合计算器也不适合 BankAccount class 的所需逻辑。
简而言之:业务逻辑(在丰富的域模型中)是 class 维护其状态并尽可能封装它所需的所有逻辑。在第二个 class 中,无法直接更改余额。需要 AddTransaction
或 CalculateInterest
(用于利息计算)。
这保证(假设它是 并发安全)Balance
和 Transactions
将始终处于一致状态(即永远不会错过添加任何一个事务或更新余额)。