背景
Web 后台开发中,对于一个实体的操作会衍生出多个类似的对象进行操作(避免直接使用实体),由此出现相关名词
- 持久化对象,即实体 PO(Persistent Object)
- 传输对象 DTO(Data Transfer Object)
- 业务对象 BO(Business Object)
- 展示对象 VO(View Object)
- 等等…… 这些对象大多数直接从实体里面裁剪几个字段,比如,在一次创建订单请求中以订单实体(OrderEntity)为例,经历如下流程:
1. 接收请求体 CreateOrderRequest
2. 根据 OrderQuery 构造查询对象查询订单
3. 构造 OrderEntity 进行持久化操作
4. 构造 OrderBO 进行下游消费
5. 返回响应体 CreateOrderResponse
可见,从 OrderEntity 衍生出 4 个对象,仅仅是对订单的实体的部分裁剪,但是要编写很多重复的代码(复制也行)。当然,如果是新增字段的话可以使用继承解决。
在 Kotlin Web 后台开发中,data class 的语法特性带来很多优势,但还是避免不了创建类似的重复对象。
所以 Konverter
诞生于此,解决实体对象裁剪问题。还有另一个功能那就是自动生成两个实体间的转换方法。
注意:目前只支持 Kotlin,并且生成的转换方法是通过扩展函数实现
是什么
通过 KAPT(Kotlin Annotation Processing Tool 注解处理以及 Kotlin Poet 代码生成,实现自动生成对实体的相关裁剪的对象。 主要有两个注解:
@Konvertable
生成裁剪的实体以及对应的转换方法@Konvert
单独针对某个类生成转换方法
废话不多说来看怎么使用。
怎么用
1. 引入依赖
// for build.gradle.kts
repositories {
maven("https://jitpack.io")
}
dependencies {
kapt("com.github.lexcao:konverter:master-SNAPSHOT")
implementation("com.github.lexcao:konverter-annotation:master-SNAPSHOT")
}
// for build.gradle
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
kapt 'com.github.lexcao:konverter:master-SNAPSHOT'
implementation 'com.github.lexcao:konverter-annotation:master-SNAPSHOT'
}
2. 在需要转换的类上加上注解
@Konvertable(
To(name = "LoginDTO", pick = ["username", "password"]),
To(name = "UserListDTO", omit = ["password"])
)
@Konvert(to = UserVO::class)
data class UserEntity(
val id: Long,
@Konvert.Field("name")
val username: String,
val password: String,
@Konvert.By(GenderEnumConverter::class)
val gender: Int
)
3. 生成的代码如下:
// @Konvertable
/**
* Auto generated code by @Konvertable
*/
data class LoginDTO(
val username: String,
val password: String
)
/**
* Auto generated code by @Konvertable
*/
data class UserListDTO(
val id: Long,
val username: String,
val gender: Int
)
/**
* Auto generated code by @Konvert
*/
fun UserEntity.toLoginDTO(username: String = [email protected], password: String =
[email protected]): LoginDTO = LoginDTO(username=username,password=password)
/**
* Auto generated code by @Konvert
*/
fun LoginDTO.toUserEntity(
id: Long = 0L,
username: String = [email protected],
password: String = [email protected],
gender: Int = 0
): UserEntity = UserEntity(id=id,username=username,password=password,gender=gender)
/**
* Auto generated code by @Konvert
*/
fun UserEntity.toUserListDTO(
id: Long = [email protected],
username: String = [email protected],
gender: Int = [email protected]
): UserListDTO = UserListDTO(id=id,username=username,gender=gender)
/**
* Auto generated code by @Konvert
*/
fun UserListDTO.toUserEntity(
id: Long = [email protected],
username: String = [email protected],
password: String = "",
gender: Int = [email protected]
): UserEntity = UserEntity(id=id,username=username,password=password,gender=gender)
/**
* Auto generated code by @Konvert
*/
fun UserEntity.toRegisterDTO(
username: String = [email protected],
password: String = [email protected],
gender: Int = [email protected]
): RegisterDTO = RegisterDTO(username=username,password=password,gender=gender)
/**
* Auto generated code by @Konvert
*/
fun RegisterDTO.toUserEntity(
id: Long = 0L,
username: String = [email protected],
password: String = [email protected],
gender: Int = [email protected]
): UserEntity = UserEntity(id=id,username=username,password=password,gender=gender)
// @Konvert
// 转换为如下对象
data class UserVO(
val id: String,
val name: String,
val gender: GenderEnum
)
enum class GenderEnum {
MALE, FEMALE;
}
object GenderEnumConverter : Konvert.KonvertBy<Int, GenderEnum> {
override fun Int.forward(): GenderEnum {
return GenderEnum.values()[this]
}
override fun GenderEnum.backward(): Int {
return this.ordinal
}
}
// 生成的代码
**
* Auto generated code by @Konvert
*/
fun UserEntity.toUserVO(
id: String = [email protected](),
name: String = [email protected],
gender: GenderEnum = with(GenderEnumConverter) { [email protected]() }
): UserVO = UserVO(id=id,name=name,gender=gender)
/**
* Auto generated code by @Konvert
*/
fun UserVO.toUserEntity(
id: Long = [email protected](),
username: String = [email protected],
password: String = "",
gender: Int = with(GenderEnumConverter) { [email protected]() }
): UserEntity = UserEntity(id=id,username=username,password=password,gender=gender)
相关 API 说明
转换规则
- 如果转换至 String 类型,原类型不匹配时会调用
toString()
- 如果转换至基础数据类型,转换字段缺失时会使用默认类型
- 如果转换至允许为 null 类型,原类型不匹配时会使用默认值 null
- 如果转换至引用类型(String 除外)或者找不到映射需要在转换方法显式赋值
下一步
- 代码重构,新增测试用例
- 支持引用类型默认值
- 支持嵌套对象
- 支持 Java
- 已知 BUG 修复
- 支持映射失败字段获取原类型的构造函数参数默认值,或者成员变量默认值(目前 Kotlin KAPT 已支持获取成员变量默认值,暂不支持获取函数参数默认值)
已知 BUG
- 相同 name 在 @Konvertable 冲突
- @Konvertable 参数合法性校验以及友好报错
- KonvertBy 目前使用 Class 报错或者使用 Companion 报错
最后
Konverter 源码在 GitHub
相关的样例代码 GitHub