领域驱动设计学习笔记二-各层实现规范
门面层 —— User Interface.门面层,对外以各种协议提供服务,该层需要明确定义支持的服务协议、契约等
应用服务层 Application Layer - service、assembler组成, - 区别于domain层的domain service,是应用服务。它是组件的粘合剂,组合domain层的各个组件和 infrastructure层的持久化组件、消息组件等等,完成具体的业务逻辑,提供完整的业务服务。 - 通过DDD实现业务服务时,检验业务模型的质量的一个标准便是 —— service方法中不要有if/else。如果存在if/else,要么就是系统用例存在耦合,要么就是业务模型不够友好,导致部分业务逻辑泄漏到service了
领域层 Domain Layer、业务领域层,是我们最应当关心的一层,也是最多变的一层,需要保证这一层是高内聚的。确保所有的业务逻辑都留在这一层,而不会遗漏到其他层。按照ddd(domain driven design)理论,主要有如下概念构成: - entity - value object - domain service - domain event - factory - repository Interface
基础设施层 – infrastructure 基础设施层提供公共功能组件,供controller、service、domain层调用。比如: - 持久化相关组件 - httpclent - validation - exceptionHanler - message resource等。
DDD设计。可以保证各层之间开发互相不干扰 有人可以专注于facade层,有人专注于application层,有人专注于领域层,有人专注于基础设施层,有人专注于适配层。
如何确定系统边界?
- 将项目分割成系统内的和系统外的。
- 系统内的在以后的项目进展中我们必须为创建他们而投入大量的精力,系统外的我们不需要创建,但是需要我们考虑与他们的接口。
- 系统外部, 必须考虑到他们甚至以他们为中心来分析我们的系统内部该做些什么。
- 大致可以分为我们产品将要面对的使用者(人)
- 以及为外部别的系统提供的服务(其他的软件),数据存储,硬件设备,以及网络等等
通常我们将系统外部与系统内部交互的的事物统称为执行者,执行者是同系统交互的所有事物,执行者总是在我的系统之外,从来就不是我的系统的一部分,每一个执行者都对应一种特定的角色,每一个系统之外的实体对应多种执行者,就好比一个人在系统中他会有多种角色一样,又或者几个人可以用一个执行者来表示,因为他们对于系统来讲属于同一个的角色。 如何寻找系统的执行者?只要能回答一下几个问题,我想系统的执行者大体上也就找到差不多了。 谁使用这个系统? 谁安装系统? 谁启动系统? 谁维护系统? 谁关闭系统? 哪些其他的系统使用这个系统? 谁从这个系统获取信息? 谁为这个系统提供信息? 是否有事情自动在预计的时间发生?
1、找出系统有什么;系统外有什么;确定项目规模,定义要创建系统那些部分。 2、通过确定执行者和用例来确定系统边界。 3、确定执行者:谁使用这个系统,谁安装这个系统,谁启动这个系统,谁维护这个系统,谁关闭这个系统,那些系统使用这个系统,谁从这个系统获取信息,系统为谁提供信息,是否有事情在预计时间自动发生?…..提问的方式最好针对参与者的目标。因为用例建模的观点就是寻找特定参与者及其目标。 4、确定执行者使用的用例: 5、用例是一种系统执行的一系列活动,执行者执行它产生一种可估量(量化)的结果。什么样子才是可量化?一般指用例执行后的结果是具有持久性,稳定性的数据。 6、确定用例:执行者希望系统提供什么样功能?系统存储,创建,更新或删除什么信息?系统是否需要把自身的状态变化通知给执行者?系统必须知道哪些外部的事件?执行者怎样通知系统这些事件? 7、言简意骇的描述执行者和用例。 8、发现新需求问一些问题: 这些需求是必须的?是系统逻辑上必须完成的吗?是否会影响到风险分析?需求是否能被现有的执行者处理?是客户希望的系统能做的吗?会使产品在市场上变得与众不同吗? 9、系统边界确定后必须确定项目 范围:划分系统需求的优先级,确定预算。
异常应用
层与层之间信息的传递,除了主路径代码流程外,都需要抽象出响应的业务异常。每一层尽量对下一层业务异常和本层出现的系统异常包装成本层的业务异常行为, 杜绝出现业务分支不可控的状态,对于要求比较严谨的金融领域更加适合。而且可以包含更多的信息,对于监控、和上层调用根据信息作出判断或处理。 一方面,如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化 另一方面,防止导致更深层次的嵌套结构
异常回答了下面三个问题: - 什么出了错? - 在哪出的错? - 为什么出错? 异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出,如果你的异常没有回答以上全部问题,那么可能你没有很好地使用它们。 有三个原则可以帮助你在调试过程中最大限度地使用好异常,这三个原则是: - 具体明确 - 提早抛出 - 延迟捕获
具体明确的异常,某些情况下多个catch块带来的额外编码工作量可能是非必要的负担,但额外的代码的确帮助程序提供了对用户更友好的响应或者是为系统的正确性提供了保证。
提早抛出异常(又称"迅速失败"),异常得以清晰又准确。堆栈信息立即反映出什么出了错(提供了非法参数值),为什么出错(文件名不能为空值),以及哪里出的错(readPreferences()的前部分)。通过在检测到错误时立刻抛出异常来实现迅速失败,可以有效避免不必要的对象构造或资源占用,比如文件或网络连接。同样,打开这些资源所带来的清理操作也可以省却
延迟捕获: 菜鸟和高手都可能犯的一个错是,在程序有能力处理异常之前就捕获它。Java编译器通过要求检查出的异常必须被捕获或抛出而间接助长了这种行为。自然而然的做法就是立即将代码用try块包装起来,并使用catch捕获异常,以免编译器报错。问 题在于,捕获之后该拿异常怎么办?最不该做的就是什么都不做。
Error: 是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 checked: 跟代码实施素质无关的。如果不能行之有效的处理,还不如转换为RuntimeException抛出。 runtime:开发实施人员素质相对较高时,可以杜绝此类异常发生。 NPE、数组越界等。由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。