[TOC]
成層階層ドメインモデルの応用&なぜそれが重要なのですか?
私たちは、ソフトウェアの設計と開発プロセス、いくつかの層に横方向の分割に使用するソフトウェアを開発しています。プレゼンテーション層(VIEW / UI)、ビジネスロジック層(SERVICE / BAL)、データアクセス層(DAO / DAL):このような一般的な3層アーキテクチャとして。図は次のとおりです。
なぜそれがアプリケーションシステムを階層化することですか?それは次のような問題を解決するために、主に次のとおりです。
-
最初はデカップリングされています。
すべてのソフトウェアの問題は、レイヤーを追加することによって解決することができます:コンピュータがことわざがあります。システムに大きい場合には、より多くのチームは、より高速な需要の変化、より多くの我々は、手続き間の依存性が少ないことを確認する必要があります。層状/インターフェイス指向プログラミングは、より多くの我々は、変更への対応にする傾向があります。
-
二つ目は、問題を単純化することです。
我々は、相互作用のプロセス全体まで、ディスクオフユーザデータからの操作を理解していないとき、私たちは別の方法を考える必要があります。どのような支援の層を考えて提供する必要があり、層の分裂の明確な定義によって、複雑な質問は、各レイヤの機能をどのように組み合わせるかなっ「を構築するためのビルディングブロックを。」
-
第三は、システムのメンテナンスを削減し、コストをアップグレードすることです:
ここインターフェイス指向プログラミングの利点を体現しています。私たちは、OracleやMySQLを使用して、基礎となるデータベースの両方を提供するために、同じインターフェイスは、上部構造が知覚できないであることを確認する必要があり、データアクセス層を抽象化。
-
第4の論理多重化/コードの再利用:
、明確に定義された責任の層を積層することにより、システムはもはやデータベースクエリのコード表を複数の場所に表示されません。クエリなので、データベースのテーブルには、専用のデータアクセス層のクラスが提供する統一していきます。
- 開発効率を向上させるための第五チーム:
開発チームの階層とのインタフェースで定義された多く、もし。唯一のインタフェース規格/仕様の開発を追跡する必要が各チームには、並行して開発することができます。記述するのがより適切ではあり:横方向に対応する階層ソフトウェアは、数回縦に切断ソフトウェアのモジュール化に対応し、数回をカット。
アプリケーションの階層化のために推奨される「アリ・ババのJava開発マニュアル」では、この次のとおりです。
- オープンインタフェース層:直接RPCインターフェースにさらされるのサービス実装方法、HTTP Webインターフェイスにカプセル化され、ゲートウェイセキュリティ制御/フロー制御。
- ターミナル表示層:テンプレート層の展示は、それぞれの端部をレンダリングして実行します。主電流は、速度レンダリング、JSレンダリング、JSPレンダリング、ディスプレイ及び他の携帯端末です。
- ウェブ層:主に、アクセス制御の転送のために、基本的なパラメータのすべての種類を確認し、ビジネスや簡単な治療は、もはや使用。
- サービス層:相対集団のビジネス・ロジック・サービス層。
- Manager层:通用业务处理层,它有如下特征:
- 对第三方平台封装的层,预处理返回结果及转化异常信息。
- 对Service层通用能力的下沉,如缓存方案/中间件通用处理。
- 与DAO层交互,对多个DAO的组合复用。
- DAO层:数据访问层,与底层MySQL、Oracle、HBase等进行数据交互。
- 外部接口或第三方平台:包括其他部门RPC开放接口,基础平台,其他公司的HTTP接口。
以上的层级只是在原来三层架构的基础上进行了细分,而这些细分的层级仅仅是为了满足业务的需要。千万不要为了分层而分层。
过多的层会增加系统的复杂度和开发难度。因为应用被细分为多个层次,每个层关注的点不同。所以在这基础上,抽象出不同的领域模型。也就是我们常见的DTO,DO等等。其本质的目的还是为了达到分层解耦的效果。
典型的领域模型都有哪些?
以上我们简单了解了分层的重要性,那么随着分层引入的典型领域模型都有哪些?我们还是来看看《阿里开发手册》提供的分层领域模型规约参考:
- DO(Data Object):此对象与数据库表结构一一对应,通过DAO层想上传输数据源对象。
- DTO(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
- BO(Business Object):业务对象,由Service层输出的封装业务逻辑的对象。
- AO(Application Object):应用对象,在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
- VO(View Object):显示层对象,通常是Web向模版渲染引擎层传输的对象。
- Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。
各个领域模型在分层上的传输关系大概是这样:
在给出的参考中并没有对模型对象进行非常明确的划分,特别是对BO、AO、DTO的界限不是非常明确。这也是因为系统处理的业务不同、复杂度不同导致的。所以在设计系统分层和建模的时候,需要综合考虑实际应用场景。
数据在上传下达的过程中就会出现转换的工作,可能有些小伙伴会觉得麻烦,为什么要弄出这么多O?转来转去的多累!
在这里我举个例子,比如你查询自己网上购物的订单,可能会在网页上看到这样的信息:
其中包含:订单编号,下单日期,店铺名称,用户信息,总金额,支付方式,订单状态还有一个订单商品明细的集合。
对终端显示层来说,这些信息是可以封装成一个VO对象的。因为显示层的关注点就是这些信息。为了方便显示层展示,我们可以将所有属性都弄成字符串类型。如下示例,可以看到,除了订单id外,都是String类型:
public class OrderVO {
/**
* 订单id
*/
Long orderId;
/**
* 下单日期
*/
String orderDate;
/**
* 总金额
*/
String totalMoney;
/**
* 支付方式
*/
String paymentType;
/**
* 订单状态
*/
String orderStatus;
/**
* 商铺名称
*/
String shopName;
/**
* 用户名称
*/
String userName;
/**
* 订单商品明细集合
*/
List<ProductVO> orderedProducts;
}
再来看看对于业务逻辑层来说,它关心的是什么呢?显然跟显示层关注的不一样,它更加关注的是内部的逻辑关系。如下示例:
public class OrderVO {
/**
* 订单id
*/
Long orderId;
/**
* 下单日期
*/
Date orderDate;
/**
* 总金额
*/
BigDecimal totalMoney;
/**
* 支付方式
*/
PaymentType paymentType;
/**
* 订单状态
*/
OrderStatus orderStatus;
/**
* 商铺信息
*/
ShopDTO shopInfo;
/**
* 用户信息
*/
UserDTO userInfo;
/**
* 订单商品明细集合
*/
List<ProductDTO> orderedProducts;
}
从如上代码可以看到,下单日期使用的Date类型,金额使用BigDecimal,支付方式和订单状态使用枚举值表示,商铺名称和用户名称变成了商铺信息/用户信息对象,明细集合中的商品也变成了DTO类型的对象。
在业务逻辑层面,更多的是关注由多种信息组合而成的关系。因为它在系统中起到信息传递的作用,所以它携带的信息也是最多的。
那我们再来看看数据持久层,上面也提到了,数据持久层与数据库是一一对应的关系,而上一层的订单信息其实可以拆解为多个持久层对象,其中包含:订单持久层对象(OrderDO),商铺持久层对象(ShopDO),用户持久层对象(UserDO)还有一堆的商品持久层对象(ProductDO)。相信通过描述大家也可以理解具体的拆分方法了。
回过头来想想,如果我们一路拿着最开始的OrderVO对象来操作,当我们想要将它持久化时,会遇到多少坑就可想而知了。所以分层/拆分的本质还是简化我们思考问题的方式,各层只关注自己感兴趣的内容。
模型转换需要注意的问题是啥?
可这样的拆分确实增加了许多工作量,不同模型之间转来转去的确实头疼。那就让我们来梳理一下,在模型转换时都需要注意哪些问题。在进行不同领域对象转换时,有些问题是需要我们考虑的。
例如,上面这两个不同的模型在转换时,我们就需要考虑一些问题:
- 原对象和目标对象相同属性的类型不一样,有的是Date,有的是BigDecimal,还有的是枚举
- 属性的名称也不一样
- 集合类属性中的泛型也不一样
- 能不能只复制一部分属性
- 能不能自定义转换逻辑
- 嵌套对象是深拷贝还是浅拷贝
这么多需要考虑的地方,咱们要怎么处理,才能优雅的进行模型转换呢?
常见的模型转换方法了解下!
这里我调研了大概有10种方法,有些使用起来比较复杂就没有下大力气去深入研究,如果有感兴趣的小伙伴,可以自行深入研究下。
做为测试和讲解的案例,咱们就以上面说到的OrderDTO转OrderVO为例,来说说下面的各种方法。源对象大体结构是这样的:
我们期待转换完的OrderVO对象是这样的:
先来看第一种方法:
也是最简单粗暴的方法,直接通过Set/Get方式来进行人肉赋值。代码我就不贴了,相信大家都会。
说一说它的优缺点:
优点:直观,简单,执行速度快
缺点:属性过多的时候,人容易崩溃,代码显得臃肿不好复用
第二种:FastJson:
利用序列化和反序列化,这里我们采用先使用FastJson的toJSONString的方法将原对象序列化为字符串,再使用parseObject方法将字符串反序列化为目标对象。
转换后的结果如下:
可以看到转换后的数据格式有几个问题:
- 日期不符合我们的要求
- 金额也有问题
- 最严重的是,当属性名不一样时,不会进行复制
这就是第二种使用JSON处理,好像也不能满足我们的要求
第三种,Apache工具包PropertyUtils工具类,代码如下:
转换代码看着很简单,但是转换过程会报错:
缺点:
- 属性类型不一样,报错
- 不能部分属性复制
- 得到的目标对象部分属性成功(这点很要命,部分成功,部分失败!)
第四种,Apache工具包BeanUtils工具类,代码如下:
转换后的结果是这样:
缺点:
- 日期不符合要求
- 属性名不一样时不复制
- 目标对象中的商品集合变成了DTO的对象,这是因为List的泛型被擦除了,而且是浅拷贝,所以造成这种现象。
第五种,Spring封装BeanUtils工具类,代码如下:
在忽略了部分属性后,转换结果就只剩下:
apache的BeanUtils
和spring的BeanUtils
中拷贝方法的原理都是先用jdk中 java.beans.Introspector
类的getBeanInfo()
方法获取对象的属性信息及属性get/set方法,接着使用反射(Method
的invoke(Object obj, Object... args)
)方法进行赋值。
前面五种都不能满足我们的需要,其实想想也挺简单。对象转换本来就很复杂,人工不介入很难做到完美转换。
第六种,cglib工具包BeanCopier:
cglib的BeanCopier
采用了不同的方法:它不是利用反射对属性进行赋值,而是直接使用ASM的MethodVisitor
直接编写各属性的get/set
方法生成class文件,然后进行执行。
使用方法如下,注释写的很清楚。我们通过自定义的转换器来处理Date转String的操作:
转换结果如下,对于我们自定义处理的属性可以完美支持,其他未处理的属性就不行了:
优缺点:
- 字节码技术,速度快
- 提供自己自定义转换逻辑的方式
- 转换逻辑自己写,比较复杂,繁琐
- 属性名称相同,类型不同,不会拷贝(原始类型和包装类型也被视为类型不同)
第七种,Dozer框架:
注意,这已经不是一个工具类了,而是框架。使用以上类库虽然可以不用手动编写get/set
方法,但是他们都不能对不同名称的对象属性进行映射。在定制化的属性映射方面做得比较好的就是Dozer了。
Dozer支持简单属性映射、复杂类型映射、双向映射、隐式映射以及递归映射。可使用xml或者注解进行映射的配置,支持自动类型转换,使用方便。
使用方式很简单,关键在于配置:
在配置文件中对特殊属性进行了特殊定义,转换结果符合我们的要求:
它的特点如下:
- 支持多种数据类型自动转换(双向的)
- これは、別の属性名との間の変換をサポートしています
- 3つのマッピングの設定(注釈モード、APIモード、XMLモード)をサポート
- これは、プロパティの一部を無視するようにコンフィギュレーションをサポートしています
- カスタムのサポートは、コンバータ属性
- ディープコピーネストされたオブジェクト
第八、MapStructフレーム:
JavaアノテーションJSR269プロセッサ、コンパイル時のインタフェースクラスに自動的に生成された注釈で構成マッピング関係に基づきます。ほとんどロンボク島の原則と同様に、同様なので、実行速度とセッター、ゲッターに。私は現在、後者は別の記事MapStructの使用が記載されているを書くための時間を持つことになり、より多くのMapStruct個人やBeanCopierを使用しています。
第九、オリカフレーム:
サポート登録コードフィールドがマップされて、直接生成されたバイトコード実行ファイルをロードした後、バイトコードビーンJavassistのライブラリによって生成されたマッピング。
第十、ModelMapperフレームワーク:
反射やメンバ変数の直接割り当ての原理に基づきます。同等のBeanUtils
高度なバージョン
いくつかの他のフレームワークには、綿密研究できなくなります。しかし、状況を見て、実際のシーンの使用の要件を満たすことができるはずです。これらの変換方法は、実質的に性能を、以下に説明:手動割り当て> CGLIB>>ドーザー>シリアライゼーションを反映しています。
実際のプロジェクトでは、総合的なモデル変換は、上記の方法を使用して。例えば以下のネストされたオブジェクトに関連するように下層を行うには、より少ない変更、それは々BeanUtilsに直接使用することができます。それはスピード、安定した優先システムがある場合は、素直にそれを達成入手、設定を使用します。