现在以网页发布的软件非常普遍,叫BS模式。前后端分离也是大趋势,或者说逐渐普及开来,深受前后端程序员的喜爱,我还是习惯以程序员来泛称所有软件制作者。后端需要把数据传送给前端,往往是通过DTO的序列化来实现的,而不是直接产生json或xml格式的数据。这里不说为什么要用DTO,只说探讨一个问题,不同的api接口(方法)返回不同格式的数据,这些DTO要如何设计。
特别说明,以下说到的DTO,是指DTO类,而不是DTO实例。虽然O应该是指实例,但这里继续用这个词,不阻碍意思的表述。
方案一,需要什么造什么。
这个方案简单,每个接口需要的数据结构不同就造不同的DTO,相同就复用。一般情况下能复用的不多,于是会出现大量的DTO,这些DTO的命名和管理成为难题。若规定一个接口独立使用一个DTO,则写这些DTO的工作量不小。还有一种粗糙的办法,假设有100个DTO,把它们按相近程度划分为少数若干类DTO,假设10类吧,然后同一类的DTO属性合并成一个DTO,这样确实能有效减少DTO的数量,但在具体的应用时,会多出来许多很是奇怪的属性。不可取。
方案二,照着对象造DTO
这里的对象可以简单理解为数据表,一个数据表对应一个DTO,但是多对多的关系表不对应DTO。造出来的DTO基本上与数据表的数量一样多(含统计功能的查询除外),有效的控制了DTO的数量,命名也方便。一个缺点是每个表的全部字段不管是否用到都在DTO中,有些字段只在少数情况下使用的,也得到处出现。另一个缺点是在DTO之间的关系上几乎总是造成循环引用。比如子对象往往要引用父对象,而父对象也有子对象的集合。不同接口往往只需要表现其中一个方向,但做出来的两个DTO类之间就必然有这种相互引用的关系,在自动生成api文档的时候,这种循环引用会是个问题,至今没有找到好的解决办法。
方案三,一个对象对应N个DTO
这N个DTO我作了规划:
类型 | 基类 | 范围 |
---|---|---|
DTO1 | 只有ID和Name | |
DTO2 | DTO1 | 只有常用的属性 |
DTO3 | DTO2 | 有全部的属性,但不包含IsDeleted这类特殊的附加属性 |
DTO4 | DTO3 | 包含了父对象 |
DTO5 | DTO3 | 包含了子对象 |
DTO6 | DTO4 | 包含了父对象和子对象 |
DTO4,DTO5,DTO6引用其它的DTO,作为泛型在具体使用的时候传入,并约束为对应的DTO1本身或子类。
这样,就可以以有限的数量的DTO,来组合表达足够灵活的结构,同时不会有输出太多多余的属性的情况。
也能解决DTO之间的循环引用问题,从而能使用swagger等生成api文档。
以学生和班级为例:
- 需要按条件查到若干个学生和每个学生对应的班级名称,则是结构是:
List<学生3<班级1>> 学生列表;
- 需要得到若干个班级,以及每个班级的部分符合指定条件的学生
List<班级5<学生2>>
- 需要得到指定老师所教的全部班级的学生,则是
老师5<班级5<学生2>>
缺点也是有的,DTO有6个,虽然有继承,不用重复写,但总比只有1个DTO要多写点代码。另外,这些DTO如何区分命名比较好,也是一个有待进一步完善的问题。如上表,是以数字123456区分的,应该有更好的命名方案。
- 2019-03 经过项目的实际使用, 调整为以下方案, 更适应实际的应用的需要。
类型 | 基类 | 范围 |
---|---|---|
DTO1 | ID和Name(Code) | |
DTO2 | DTO1 | 只有常用的属性 |
DTO3 | DTO2 | 全部属性(不包含IsDeleted这类特殊的附加属性,下同) |
DTO4 | DTO2 | 常用属性+父对象 |
DTO5 | DTO2 | 常用属性+子对象 |
DTO6 | DTO4 | 常用属性+父对象+子对象 |
DTO7 | DTO3 | 全部属性+父对象 |
DTO8 | DTO3 | 全部属性+子对象 |
DTO9 | DTO7 | 全部属性+父对象+子对象 |