你看到了什么是使用回调域逻辑的利弊? (我说的是在Rails和/或Ruby项目的情况下。)
展开了讨论,我想从提这句话的回调Mongoid页 :
使用回调域逻辑是一个糟糕的设计实践,并能导致一些难以调试的意外错误时链条中止执行,回调。 这是我们的建议只使用它们横切关注点,像排队的后台作业。
我很想听到这种说法背后的论据或防御。 难道是打算只适用于蒙戈支持的应用程序? 或有意跨数据库技术应用?
这似乎是on Rails的指南的Ruby ActiveRecord的验证和回调可能不同意,当它涉及到关系数据库至少。 就拿这个例子:
class Order < ActiveRecord::Base
before_save :normalize_card_number, :if => :paid_with_card?
end
在我看来,这是一个简单的回调实现域逻辑的一个很好的例子。 看来,快速和有效的。 如果我是拿Mongoid建议,其中将这个逻辑走呢?
Answer 1:
我真的很喜欢使用的回调小班。 我觉得它使一类非常可读的,例如像
before_save :ensure_values_are_calculated_correctly
before_save :down_case_titles
before_save :update_cache
它是不清楚发生了什么。
我甚至觉得这个检验的; 我可以测试该方法本身的工作,我可以单独测试每个回调。
我坚信,在一个类中的回调应该只能用于属于类方面。 如果你想触发保存,例如事件发送邮件,如果对象是处于某种状态或日志,我会用一个观察者 。 这种尊重单一职责原则。
回调
回调的优点:
- 一切都在一个地方,所以,可以很容易
- 非常可读的代码
回调的缺点:
- 因为一切是一个地方,很容易打破了单一职责原则
- 可以使重类
- 如果一个回调失败会发生什么? 它仍然沿用链? 提示:请确保您的回调永远不会失败,或以其他方式设置模型的状态为无效。
观察员
观察员的优势
- 很干净的代码,你可以做一些观察家对同一个班级,每做一件事不同
- 观察者的执行并不耦合
观察员的缺点
- 起初,它可能是怪异的行为是如何触发(看在观察者!)
结论
因此,在短期:
- 使用简单,模型相关的东西回调(计算值,默认值,验证)
- 使用更多跨领域的行为观察员(如发送邮件,传播状态,...)
和往常一样:所有意见必须与一粒盐服用。 但以我的经验观察员规模非常好(和还鲜为人知)。
希望这可以帮助。
Answer 2:
编辑:我已经联合我就在这里有些人建议的答案。
摘要
基于一些阅读和思考,我来的是什么,我相信一些(暂定)语句:
声明“使用回调域逻辑是一个糟糕的设计实践”是假的,因为写的。 它夸大了点。 回调可以是域逻辑好地方,适当地使用。 这个问题不应该是如果域模型的逻辑应该在回调中去,这是什么样的领域逻辑有道理进去。
声明“使用域逻辑回调...可以导致一些难以调试时链条中止执行,回调意外的错误”是真实的。
是的,回调可能会导致影响其他对象的连锁反应。 要,这是不可测的程度,这是一个问题。
是的,你应该能够测试你的业务逻辑,而无需对象保存到数据库中。
如果一个对象的回调为你的感情太臃肿,有替代设计考虑,包括(a)观察员或(b)辅助类。 这些可以干净地处理多对象操作。
建议“只使用[回调]为横切关注点,像排队的后台作业”是有趣的,但夸大了。 (我审查交叉问题 ,看看我也许忽视的东西。)
我也想分享一些我的反应,以博客文章我读过,谈这个问题:
反应“的ActiveRecord的回调毁了我的生活”
马蒂亚斯Meyer的2010年后, ActiveRecord的的回调毁了我的生活 ,提供了一个视角。 他写:
每当我开始添加验证和回调Rails应用程序的模型[...]这只是感觉不对。 这感觉就像我加入的代码,不应该存在,才使得一切变得更复杂了,而变成显性隐性成代码。
我觉得这是最后的要求“变成显性隐性变成码”是,好了,不公平的期望。 我们正在谈论的Rails在这里,对不对? 这么多的附加价值是Rails的做事“神奇”,例如,而无需显式地做开发。 难道不觉得奇怪享受Rails的成果,但批评隐码?
即只被依赖于对象的持久状态运行代码。
我同意这听起来很令人讨厌。
即正在努力测试,因为你需要一个对象保存到你的业务逻辑的测试部分代码。
是的,这使得测试缓慢和艰难。
因此,总的来说,我觉得马蒂亚斯增加了一些有趣的推波助澜,但我不觉得这一切引人注目。
反应“疯狂,邪教和真棒:我的方式写Rails应用”
在詹姆斯Golick的2010年后, 疯狂的,异端,和真棒:我的方式写Rails应用 ,他写道:
此外,所有的业务逻辑连接到你的持久对象可以有奇怪的副作用。 在我们的应用程序,创建的东西时,一个after_create回调产生的日志,这是用来生产活动供稿中的条目。 如果我想建立一个没有记录的对象是什么 - 比如,在控制台? 我不能。 保存和记录永远和永恒结婚。
后来,他得到它的根源:
该解决方案实际上是非常简单的。 这个问题的简单解释是,我们违反了单一职责原则。 所以,我们要使用标准的面向对象技术来分离我们的模型逻辑的关注。
我真的很感激,他告诉你什么时候适用,当它没有缓和他的建议:
事实是,在一个简单的应用,肥胖持续性对象可能永远不会受到伤害。 这是当事情变得有点比CRUD操作更为复杂的是,这些事情开始堆积起来,成为痛点。
Answer 3:
这个问题就在这里( 忽略RSpec的验证失败 )是一个很好的理由,为什么不把逻辑在你的回调:可测性。
您的代码可以开发随着时间的推移很多依赖的倾向,在那里你开始增加unless Rails.test?
到你的方法。
我建议只保留在你的格式化逻辑before_validation
回调,动人的东西,碰多班列到一个服务对象。
所以你的情况,我会在normalize_card_number移动到before_validation,然后您就可以验证卡号是标准化。
但是,如果你需要去关闭和地方创造PaymentProfile,我会做,在另一个服务工作流程对象:
class CreatesCustomer
def create(new_customer_object)
return new_customer_object unless new_customer_object.valid?
ActiveRecord::Base.transaction do
new_customer_object.save!
PaymentProfile.create!(new_customer_object)
end
new_customer_object
end
end
然后,您可以轻松地测试某些条件,例如,如果它不是,有效的,如果保存不会发生,或者如果支付网关会抛出异常。
Answer 4:
在我看来,利用回调最好的情况是,当射击它的方法无关,与什么在回调本身执行。 举例来说,一个好的before_save :do_something
不应该执行与保存代码。 它更像是一个观察者应该如何工作。
人们往往使用回调只将干燥他们的代码。 这不是不好,但可能会导致复杂和难以维护的代码,因为读取save
方法不告诉你它,如果你不通知回调被调用。 我认为这是明确的代码(尤其是在Ruby和Rails,哪来那么多奇迹发生)的重要。
与保存一切都应该是在save
方法。 如果,例如,回调是要确保用户进行身份验证,它没有任何关系降耗 ,那么它是一个很好的回调情形。
Answer 5:
Avdi格林在他的书中一些很好的例子, 对象上的Rails 。
你会发现这里和这里为什么他不选择回调选项,你怎么可以简单地通过重写相应的ActiveRecord的方法摆脱这一点。
在你的情况,你最终会喜欢的东西:
class Order < ActiveRecord::Base
def save(*)
normalize_card_number if paid_with_card?
super
end
private
def normalize_card_number
#do something and assign self.card_number = "XXX"
end
end
[您的评论后,UPDATE“这仍是回调”]
当我们说回调域逻辑的,我明白ActiveRecord
回调,请纠正我,如果你想从Mongoid报价引荐到别的东西,如果有一个“回调设计”的地方我没有发现它。
我认为ActiveRecord
回调是,在大多数(全部?)部分无非就是语法糖,你可以在我前面的例子中去掉了。
首先,我同意这个回调方法隐藏其背后的逻辑:人谁不熟悉ActiveRecord
,他将不得不学习它理解代码,与上面的版本,这是很容易理解和测试。
这可能是最糟糕的与ActiveRecord
回调了他们的“共同使用”或“脱钩感觉”他们可以生产。 回调版本可能似乎一开始不错,但你会添加更多的回调,这将是更难理解你的代码(在这种顺序是它们装载,其中一个可能会停止执行流程等)并进行测试(您的域逻辑耦接ActiveRecord
持久性逻辑)。
当我读到下面我举的例子,我觉得不好的代码,它的气味。 我相信你可能不同意这个代码,如果你在做TDD / BDD结束了,如果你忘了ActiveRecord
,我想你也只会写的card_number=
方法。 我希望这个例子是不够好,不能直接选择回调选项,第一个想到的设计。
关于从MongoId报价我不知道为什么他们的建议不使用回调域逻辑,但用它来排队的后台作业。 我觉得排队后台作业可能是领域逻辑的一部分,有时可能会比一个回调别的什么更好的设计(比方说,一个观察员)。
最后,是关于ActiveRecord的是如何使用的一些批评/从一个角度面向对象的程序设计点铁实现的,这个答案包含关于它的良好的信息,你会发现更容易。 您可能还需要检查DataMapper的设计模式 / Ruby实现的项目可能是ActiveRecord的更换(但好多少),并没有他的弱点。
Answer 6:
我不认为答案是所有的太复杂了。
如果你打算建立具有确定性的行为的系统,与数据相关的东西,如标准化处理回调都OK,与业务逻辑处理的回调,如发送确认电子邮件没有确定 。
OOP与应急行为作为最佳实践推广1 ,在我的经验的Rails似乎都同意。 许多人, 包括谁介绍了MVC的家伙 ,认为这会导致应用程序不必要的痛苦下,运行时的行为是确定性的,众所周知的时间提前。
如果您使用的OO自发行为的做法一致,则耦合行为,你的数据对象图中的活动记录模式不是什么大不了的。 如果(像我)你看/感到的理解,调试和修改等新兴系统的痛苦,你会想要做的一切,你可以使行为更具有确定性。
现在,如何做一个设计OO系统的松耦合性和确定性的正确的平衡? 如果你知道答案,写一本书,我就买它! DCI , 域驱动的设计 ,并且更一般的GOF模式是一个开端:-)
- http://www.artima.com/articles/dci_vision.html ,“在哪里我们有什么错?”。 不是一个主要来源,但我一般的理解和最狂野的假设的主观体验是一致的。
文章来源: Pros and cons of using callbacks for domain logic in Rails