las*_*hou 19 domain-driven-design
尽管已经研究Domain Driven Design了很长时间,但我仍然有一些基本要素.
似乎每一次我尝试设计了丰富的时候domain layer,我还需要大量的Domain Services或厚Application Layer的,我从"GetTotalAmount"等结束了,在他们没有真正的逻辑一堆近贫血域的实体,除了.关键问题是实体不了解外部因素,并且向实体注入任何内容都是不好的做法.
让我举几个例子:
1.用户注册服务.用户持久存储在数据库中,生成并保存文件(用户帐户需要),并发送确认电子邮件.
确认电子邮件的示例已在其他主题中进行了大量讨论,但没有得出真正的结论.有人建议把逻辑的application service是得到一个EmailService和FileService注入从infrastructure layer.但那时我会在域外拥有业务逻辑,对吧?其他人建议创建一个domain service得到infrastructure services注入 - 但在这种情况下,我需要有(和)infrastructure services内部的接口看起来不太好(因为不能引用).其他人建议实施Udi Dahan的域事件,然后让EmailService和FileService订阅这些事件.但这似乎是一个非常松散的实现 - 如果服务失败会发生什么?请告诉我您认为正确的解决方案.domain layerIEmailServiceIFileServicedomain layerinfrastructure layer
2.从数字音乐商店购买歌曲.购物车已清空.购买持续存在.付款服务被呼叫.发送电子邮件确认.
好吧,这可能与第一个例子有关.这里的问题是,谁负责编排这笔交易?当然,我可以通过注入服务将所有内容放入MVC控制器中.但如果我想要真正的DDD,所有业务逻辑都应该在域中.但是哪个实体应该采用"购买"方法?Song.Purchase()?Order.Purchase()?OrderProcessor.Purchase()(域名服务)?ShoppingCartService.Purchase()(申请服务?)
在这种情况下,我认为在域实体中使用真实的业务逻辑非常困难.如果向实体注入任何东西不是好的做法,那么除了检查自己(及其聚合的)状态之外,他们怎么做其他事情呢?
我希望这些例子足够清楚,以显示我正在处理的问题.
Mag*_*eus 11
Dimitry的回答指出了一些值得寻找的好东西.通常/轻松地发现自己处于自己的场景中,数据从db到GUI通过不同的层进行挖掘.
我受到了Jimmy Nilsson的简单建议"价值对象,价值对象和更多价值对象"的启发.通常人们倾向于将注意力集中在名词上并将其建模为实体.当然,您经常无法找到DDD行为.动词更容易与行为相关联.一个好处是让这些动词作为Value对象出现在您的域中.
在尝试开发域时我使用的一些指导(必须说构建一个富域需要时间,通常是几次重构迭代......):
不要忘记通过验证可以使您的域名变得丰富.只有您的域名知道如何进行购买,以及需要什么.
当您的实体从一个州转换到另一个州(工作流程验证)时,您的域也应负责验证.
我将举一些例子:这是我在博客上写的一篇关于你关于贫血领域问题的文章http://magnusbackeus.wordpress.com/2011/05/31/preventing-anemic-domain-model-where-is -我的模型,行为/
我还可以真正推荐Jimmy Bogard关于实体验证的博客文章,并使用Validator模式和扩展方法.它使您可以自由地验证基础结构,而不会使您的域变脏:http: //lostechies.com/jimmybogard/2007/10/24/entity-validation-with-visitors-and-extension-methods/
我使用Udi的Domain Events非常成功.如果您认为您的服务可能失败,您也可以使它们异步.您还将它包装在事务中(使用NServiceBus框架).
在你的第一个例子中(现在只是集思广益,让我们的思想更多地考虑价值对象).
MusicService.AddSubscriber(User newUser)应用程序服务从具有新用户的演示者/控制器/ WCF接收呼叫.该服务已经获得IUserRepository并IMusicServiceRepository注入ctor.musicService.SignUp(MusicServiceSubscriber newSubsriber)方法采用Value对象MusicServiceSubscriber.此Value对象必须在ctor中使用User和其他必需对象(值对象是不可变的).在这里你也可以放置逻辑/行为,如handle subscriptionId等.NewSubscriberAddedToMusicService.它被逮住由EventHandler HandleNewSubscriberAddedToMusicServiceEvent它得到了IFileService和IEmailService注入它的构造函数.此处理程序的实现位于应用程序服务层中,但事件由Domain和MusicService.SignUp.这意味着域名处于控制之中.Eventhandler创建文件并发送电子邮件.您可以通过eventhandler持久保存用户或将MusicService.AddSubscriber(...)方法设置为此.两者都会做到这一点,IUserRepository但这是一个品味问题,也许它将如何反映实际领域.
最后......我希望你能掌握上述内容......无论如何.最重要的是开始向实体添加"动词"方法并进行协作.您还可以在域中拥有不持久的对象,它们仅用于在多个域实体之间进行调解,并且可以托管算法等.
用户注册服务.用户持久存储在数据库中,生成并保存文件(用户帐户需要),并发送确认电子邮件.
您可以在此处应用依赖性倒置原则.定义这样的域界面:
void ICanSendConfirmationEmail(EmailAddress address, ...)
Run Code Online (Sandbox Code Playgroud)
要么
void ICanNotifyUserOfSuccessfulRegistration(EmailAddress address, ...)
Run Code Online (Sandbox Code Playgroud)
接口可以由其他域类使用.使用真正的SMTP类在基础架构层中实现此接口.在应用程序启动时注入此实现.这样,您在域代码中声明了业务意图,并且您的域逻辑没有直接引用SMTP基础结构.这里的关键是界面的名称,它应该基于无所不在的语言.
从数字音乐商店购买歌曲.购物车已清空.购买持续存在.付款服务被呼叫.发送电子邮件确认.好吧,这可能与第一个例子有关.这里的问题是,谁负责编排这笔交易?
使用OOP最佳实践来分配职责(GRASP和SOLID).单元测试和重构将为您提供设计反馈.业务流程本身可以是瘦应用层的一部分.来自DDD分层架构:
应用层:定义软件应该执行的作业,并指示表达域对象解决问题.该层负责的任务对业务有意义,或者与其他系统的应用层交互所必需.
该层保持薄.它不包含业务规则或知识,但只有协调任务和委托才能在下一层中协作域对象的协作.它没有反映业务情况的状态,但它可以具有反映用户或程序的任务进度的状态.