最近,我们为您提供了新的HTTP框架HttpMate。 在引言文章中 ,我们将请求和响应映射到域对象称为“最复杂的技术细节”,以及如何通过另一个伴侣MapMate帮助我们。

实际上,在将请求属性映射到您的域对象时,MapMate减轻了HttpMate的负担。 它负责将响应转换为适当的格式(JSON,XML,YAML等),从本质上执行反序列化和序列化,但还要执行更多的工作。

在本文中,我们将重点介绍MapMate如何帮助我们以可控和可预测的方式处理(反序列化)请求/响应对象。

自定义基元

让我们回顾一下上一篇文章中的示例; 我们有一个简单的UseCase发送电子邮件。 为此,我们需要一个Email对象,该对象应具有:

  • 发件人
  • 接收者
  • 学科
  • 身体

所有这些字段都可以表示为字符串,甚至可以表示为字节数组。 选择用来表示数据的通用类型越多,以后解释它的可能性就越大。 想象一下以下方法:

public Object sendEmail(final Object sender, final Object receiver, final Object subject, final Object body) {...
}

这给我们留下了很多未解决的问题:

  • 是发件人instanceOf字符串还是字节[]?
  • 编码是什么?
  • 拉链被压缩了吗?

清单继续。 尽管在某些情况下这可能是适当的,但我敢打赌,您会更满意:

public String sendEmail(final String sender, final String receiver, final String subject, final String body) {...
}

后者留出了较少的解释空间:例如,我们不再需要假设编码或完全质疑参数的类型。

但是,它仍然是模棱两可的,发件人字段是否带有用户名或她的电子邮件地址? 同样的歧义是编写单元测试时产生无限不确定性的原因……在某种程度上,使用随机字符串生成器测试了一种方法,人们知道该方法只能接受电子邮件地址。

对于人员和编译器,以下方法签名在歧义方面做得更好:

public Receipt sendEmail(final EmailAddress sender, final EmailAddress receiver, final Subject subject, final Body body) {...
}

我们可以以相同的方式相信字符串是字符串,整数是整数,现在我们可以相信EmailAddress是电子邮件地址,主题实际上是主题–它们成为了send email方法的自定义原语。

发件人和接收者不是面面俱到的“字符串”,它们与“主题”和“正文”有很大不同。 它们是电子邮件地址,我们可以通过使用一些理智的正则表达式来验证其值来表示它们。 (提防ReDoS )

使用工厂方法作为创建“始终有效”对象的方法的合理性已得到广泛讨论和验证。 考虑到这一点,我们将为示例用例创建一个EmailAddress类,然后将其用作Sender和Receiver字段的自定义原始类型。

public final class EmailAddress {private final String value;private EmailAddress(final String value) {this.value = value;}public static EmailAddress fromStringValue(final String value) {final String validated = EmailAddressValidator.ensureEmailAddress(value, "emailAddress");return new EmailAddress(validated);}
}

由于–唯一的实例变量是私有的且是最终变量,因此只能使用私有的构造函数进行赋值,该私有构造函数只能在验证输入之前使用公共工厂方法从类外部调用,该方法会验证输入,然后将其传递给构造函数–我们可以请确保每当我们收到EmailAddress实例时,它都是有效的。

如果您现在对EmailAddressValidator实现感到好奇,请确保签出此示例项目的源代码 。

现在,我们的域对象不仅可以使用默认原语(例如String,Double,Integer等),还可以使用自定义原语(例如EmailAddress和Body,Subject等)。通常,尽管我们需要能够将域对象存储在数据库中或将其传达给其他服务或UI。 尽管没有其他方知道名为EmailAddress的自定义基元。 因此,我们需要它的“表示形式”,即HTTP,持久性和人性化的东西–字符串。

public final class EmailAddress {private final String value;public static EmailAddress fromStringValue(final String value) {final String validated = EmailAddressValidator.ensureEmailAddress(value, "emailAddress");return new EmailAddress(validated);}public String stringValue() {return this.value;}
}

我们添加的方法“ stringValue”是自定义基元的字符串表示形式。 现在,我们可以发送EmailAddress的“ stringValue”,然后根据接收到的值对其进行重构。 本质上,“ fromString”和“ stringValue”方法分别是EmailAddress的“反序列化”和“序列化”机制。

按照这种方法,我们还可以为电子邮件的正文和主题创建自定义基元:

public final class Body {private final String value;public static Body fromStringValue(final String value) {final String emailAddress = LengthValidator.ensureLength(value, 1, 1000, "body");return new Body(emailAddress);}public String stringValue() {return this.value;}
}public final class Subject {private final String value;public static Subject fromStringValue(final String value) {final String validated = LengthValidator.ensureLength(value, 1, 256, "subject");return new Subject(validated);}public String stringValue() {return this.value;}
}

数据传输对象

有了我们的自定义基元,我们现在可以创建适当的数据传输对象–电子邮件,这是非常简单的任务,因为它基本上是一个不变的结构:

public final class Email {public final EmailAddress sender;public final EmailAddress receiver;public final Subject subject;public final Body body;
}

相同的“始终有效”的方法也适用于数据传输对象,除了在这里,由于我们利用了自定义基元,因此时间更短。

DTO的工厂方法可以像验证强制字段的存在一样简单,也可以像应用跨字段验证一样复杂。

public final class Email {public final EmailAddress sender;public final EmailAddress receiver;public final Subject subject;public final Body body;public static Email restore(final EmailAddress sender,final EmailAddress receiver,final Subject subject,final Body body) {RequiredParameterValidator.ensureNotNull(sender, "sender");RequiredParameterValidator.ensureNotNull(receiver, "receiver");RequiredParameterValidator.ensureNotNull(body, "body");return new Email(sender, receiver, subject, body);
}

不幸的是,现代(反)序列化和验证框架无法与这种DTO一起使用。

这是一个JSON示例,如果您使用默认配置将电子邮件DTO馈送到这样的框架,则可能会获得最佳效果:

{"sender": {"value": "sender@example.com"},"receiver": {"value": "receiver@example.com"},"subject": {"value": "subject"},"body": {"value": "body"}
}

虽然人们期望的是:

{"sender": "sender@example.com","receiver": "receiver@example.com","subject": "subject","body": "body"
}

尽管可以使用大量样板代码来缓解此问题,但是验证是另一种野兽,当您想从服务器“立即报告所有验证错误”时,验证就变得致命。 为什么不立即告诉用户发送方和接收方均无效,而不是发送寻求许可A38的请求发送给用户。 实际上,这就是我们在尝试编写现代微服务时,同时又遵循Clean Code的最佳实践的感觉,Domain Driven Design,Domain Driven Security的“始终有效”方法……

这就是MapMate需要解决的问题。

MapMate

与HttpMate一样,我们确保提供一个易于构建的构建器,同时保留细粒度定制的可能性。 这是使我们的电子邮件示例序列化,反序列化和验证我们的自定义基元和DTO的绝对最低配置。

public static MapMate mapMate() {return MapMate.aMapMate("com.envimate.examples.email_use_case").usingJsonMarshallers(new Gson()::toJson, new Gson()::fromJson).build();
}

这部分将使以下JSON成为有效请求:

{"sender": "sender@example.com","receiver": "receiver@example.com","subject": "Hello world!","body": "Hello from Sender to Receiver!"
}

您必须指定要扫描(递归)的程序包和一对(非)编组器。 可以是任何可以从Map生成字符串的内容,反之亦然。 这是使用ObjectMapper的示例:

final ObjectMapper objectMapper = new ObjectMapper();
return MapMate.aMapMate("com.envimate.examples.email_use_case").usingJsonMarshallers(value -> {try {return objectMapper.writeValueAsString(value);} catch (JsonProcessingException e) {throw new UnsupportedOperationException("Could not parse value " + value, e);}}, new Unmarshaller() {@OverridepublicT unmarshal(final String input, final Classtype) {try {return objectMapper.readValue(input, type);} catch (final IOException e) {throw new UnsupportedOperationException("Could not parse value " + input + " to type " + type, e);}}}).withExceptionIndicatingValidationError(CustomTypeValidationException.class).build();

承诺的验证异常聚合又如何呢?
在我们的示例中,如果自定义原语或DTO无效,则所有验证都返回CustomTypeValidationException的实例。

添加以下行,以指示MapMate将您的Exception类识别为验证错误的指示。

public static MapMate mapMate() {return MapMate.aMapMate("com.envimate.examples.email_use_case").usingJsonMarshallers(new Gson()::toJson, new Gson()::fromJson).withExceptionIndicatingValidationError(CustomTypeValidationException.class).build();
}

现在,如果我们尝试以下请求:

{"sender": "not-a-valid-sender-value","receiver": "not-a-valid-receiver-value","subject": "Hello world!","body": "Hello from Sender to Receiver!"
}

我们将收到以下答复:

HTTP/1.1 400 Bad Request
Date: Tue, 04 Jun 2019 18:30:51 GMT
Transfer-encoding: chunked{"message":"receiver: Invalid email address: 'not-a-valid-receiver-value',sender: Invalid email address: 'not-a-valid-sender-value'"}

最后的话

此处提供的MapMate构建器可简化初始使用。 但是,所有描述的默认值都是可配置的,此外,您还可以从“自定义基元”和“ DTO”中排除包和类,可以配置哪些异常被视为“验证错误”以及如何对其进行处理,还可以为“自定义”指定其他方法名称原始序列化,或者提供您的lambda来同时进行这两个序列的反序列化。

有关MapMate的更多示例和详细信息,请查看MapMate存储库 。

让我们知道您的想法以及您接下来想在MapMate中看到的功能!

翻译自: https://www.javacodegeeks.com/2019/08/deserialization-and-validation-of-custom-primitives-and-dtos.html

定制基元和DTO的(反)序列化和验证相关推荐

  1. 纹理和基元_自定义基元和DTO的(反)序列化和验证

    纹理和基元 最近,我们为您提供了新的HTTP框架HttpMate. 在介绍性文章中 ,我们将请求和响应映射到域对象称为"最复杂的技术细节",以及如何通过另一个伴侣MapMate帮助 ...

  2. 点阵、基元和晶体结构之间的关系

    点阵.基元和晶体结构的关系可以表示为:晶体结构=基元+晶体点阵 基元:构成晶体的原子.分子.离子.原子团.分子团等,是组成晶体的最小结构单元.下图为一维周期性结构的结构基元(原图) 晶体的周期性结构使 ...

  3. 什么是序列化? 如何实现(反)序列化 序列化的应用

    1. 什么是序列化与反序列化,什么情况需要序列化 1.1 序列化 序列化是什么 序列化的目的 什么情况需要序列化 1.2 反序列化 反序列化是什么 反序列化的目的 2. Java中的序列化与反序列化 ...

  4. [Java基础]对象(反)序列化流

    对象序列化流: 代码如下: package ObjectOutputStreamPack;import java.io.Serializable;public class Student implem ...

  5. 纹理和基元_Java的精妙之处,包括基元和变量参数数组

    纹理和基元 在我最近的博客文章Arrays.hashCode()与 DZone联合版本的评论中提出了一个有趣的问题. Objects.hash() ". 该评论的作者建立了一些示例,这些示例 ...

  6. Java的精妙之处,包括基元和变量参数数组

    在我最近的博客文章Arrays.hashCode()与 DZone联合版本的评论中提出了一个有趣的问题. Objects.hash() ". 该评论的作者建立了一些示例,这些示例与我的博客文 ...

  7. 解决 SpringBoot 在 JDK8 中 LocalDateTime (反)序列化问题

    问题复现 Java 8 date/time type `java.time.LocalDateTime` not supported by default:add Module "com.f ...

  8. 非基元类型数据结构_Java数据类型–基元和二进制文字

    非基元类型数据结构 Java is a strongly typed language, that means all the variables must first be declared bef ...

  9. Java(正,反)序列化

    序列化:把java对象转换为字节序列的过程 反序列化:把字节序列恢复为java对象的过程 实现序列化的步骤(存入数据): 首先引用输出流 ObjectOutputStream oos = new Ob ...

最新文章

  1. Redis之七种武器
  2. Codeforces 861D - Polycarp's phone book 字典树/hash
  3. Java 8 - Optional实战
  4. ipaddr库计算区间IP及CIDR的IP段
  5. 【源码学习之spark core 1.6.1 各种部署模式所使用的的TaskSceduler及SchedulerBackend】...
  6. Angular CLI 全局 ng.cmd 文件内容分析
  7. ES6学习笔记(六)数组的扩展
  8. Spring Boot —— YAML配置文件
  9. java实现功能6_Java 6
  10. 华为设备VRRP+MSTP冗余负载均衡实现
  11. C#敏感词过滤算法实现
  12. 洛谷 P3496 [POI2010]GIL-Guilds 题解
  13. digispark开发板attiny85烧写digispark bootloader
  14. 8. Celery 4.3.0 Periodic Tasks 周期性任务调度
  15. [SilkyBible] XviD系列-9
  16. 无锡:车联网先导区“排头兵”,编织的自动驾驶产业雄心!
  17. SQlServer的日期相减(间隔)dateadd ,datediff 函数
  18. 面试项目经理,这12个问题一定会被问到(建议收藏)
  19. Java中如何实现一个函数返回多个值
  20. vue3支持多根节点,eslint插件报错只能有一个根节点

热门文章

  1. js中toString()和String()区别详解
  2. 告诉你,Spring Boot 真是个牛逼货
  3. 面试经历—广州YY(欢聚时代)
  4. 57张PPT彻底搞清楚区块链技术。。
  5. Java趣味分享:try/finally
  6. 算法六之直接插入排序
  7. html session 登录页面跳转页面跳转页面,session失效后跳转到登陆页面
  8. pycharm 的version controller
  9. html让时间只展示年月日_如何用html写代码,使得在网页上显示当前的时间和日期...
  10. python短视频自动制作_Python 带你一键生成朋友圈超火的九宫格短视频