定制基元和DTO的(反)序列化和验证

最近,我们为您提供了新的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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/341566.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UBUNtu·E: 无法获得锁 /var/lib/apt/lists/lock - open (11: 资源暂时不可用) E: 无法对目录 /var/lib/apt/lists/ 加锁 问题解决方法

E: 无法获得锁 /var/lib/apt/lists/lock - open (11: 资源暂时不可用) E: 无法对目录 /var/lib/apt/lists/ 加锁 问题解决方法 参考链接1 参考链接2

电话光端机的电话业务不通问题,该怎么去检查?

我司的设备,电话光端机都是达到电信级别,通话声音是特别清晰的。如果电话光端机的电话杂音声过大可能是电话光端机的光口有误码引起的。 一般应该是光端机有误码引起的,产生误码的原因主要是光纤部分,因为一般设备一般发货前&…

java8 camel_Meet Fabric8:基于Camel和ActiveMQ的开源集成平台

java8 camel面料8 Fabric8是来自Red Hat的JBoss Fuse产品的Apache 2.0许可上游社区。 这是一个基于Apache ActiveMQ , Camel , CXF , Karaf , HawtIO等的集成平台。 它提供了自动化的配置和部署管理,以帮助使部署变得…

解决apt-get install E: 无法定位软件包问题

更换清华源后出现的问题 参考链接 参考链接2

电话光端机的电话接口类型有哪些?

电话光端机常用的的电话接口类型有:环路中继接口(FXO)、模拟用户线接口(FXS)、热线电话接口(公务电话)、磁石电话接口。接下来,我们就跟随飞畅科技的小编来详细了解下电话光端机的电…

如何使用可外部化的接口在Java中自定义序列化

在上一篇文章“用示例介绍的有关Java序列化的一切”中 ,我解释了如何使用以下方法序列化/反序列化一个对象 Serializable接口,还说明了如何使用writeObject和readObject方法自定义序列化过程。 Java序列化过程的缺点 但是,这些定制还不够&a…

Ubuntu将python2.7默认更改为python3.X版本

1 删除原有链接 sudo rm /usr/bin/python2 建立软连接 sudo ln -s /usr/bin/python3.6 /usr/bin/python//3.6 3.5 2.7 可以根据需求更改 参考链接 建议使用方法二,方法一没使用成功

电话光端机使用什么光纤网络比较好?

电话光端机是一种将传统电话信号转换为光信号并通过光纤传输的设备,要使用电话光端机,必须首先具有光纤网络。那么电话光端机使用哪种纤维更好呢?接下来就让我们跟随飞畅科技的小编一起来看看吧! 如果电话光端机使用的光纤网络在…

NS3出现错误cc1plus: all warnings being treated as errors

参考链接 在运行./waf之前首先进行configure设置: CXXFLAGS"-Wno-error" ./waf configure #注意等号前后没有空格//进行该配置之后再进行编译 即可忽略警告

电话光端机原理及作用分析

常用的光端机分为多种类型,例如电话光端机、网络光端机、音频光端机、视频光端机、串口光端机、PDH光端机等。通常,电话光端机是最常用的,并且它们也被广泛使用。今天,就由光端机专业厂家飞畅科技来为大家普及下电话光端机的原理及…

ns-3文件编译出错总结

1、某个依赖项没有配好,应该卸载设备再重新安装,否则会造成干扰,导致编译出错 2、某文件资料下载不全,导致安装一直与教程不符。这种情况应该重下载文件 3、Git-hub下载代码慢,需要有一定的耐心

关于Jakarta EE与MicroProfile的创新和关系的提案

在JCrete非会议上,我们中的一些人正在就Jakarta EE的愿景,尤其是与MicroProfile的关系进行头脑风暴。 我想开始讨论,以使所有人都在同一页面上,尤其是Jakarta EE和MicroProfile之间的关系以及Jakarta的创新应如何。 我相信我们中的…

电话光端机作用,电话光端机功能特点介绍

电话光端机顾名思义其实也是一种光端机,但是他不仅仅只能传输电话的光端机。它的用户接口类型多样(包括语音、数据、图象),均以小型模块化部件方式装配到母板上,各种用户模块可以混合装配,方便扩容及维护。…

光端机的作用是什么? 简述光端机的作用

简单说光端机是光信号传输的终端设备,光端机一般都是成对使用,分为光发射机和光接收机。市面上常见的光端机主要有:电话光端机、PDH光端机、高清视频光端机、音频光端机、以太网光端机、数据光端机等等。那么,各类光端机的作用是什…

Ubunt中卸载protobuf与安装3.6.0版本步骤

1.卸载Ubuntu自带的protobuf; # sudo apt-get remove libprotobuf-dev # which protoc// 运行完“which protoc”会显示一个protoc的路径,如果没有显示则下面这条命令不必执行 # rm /usr/local/bin/protoc// 具体路径以“which protoc”显示的为准 到…

db2分页sql_停止尝试使用内部DB框架模拟SQL OFFSET分页!

db2分页sql我敢肯定,到目前为止,您已经以多种方式弄错了。 而且您可能很快将无法正确处理。 那么,当您可以实施业务逻辑时,为什么还要在SQL调整上浪费您的宝贵时间呢? 让我解释… 直到最近的SQL:2008标准 …

关于单纤与双纤光端机的区别介绍

单纤和双纤的光端机最大区别就是它们主板上的光模块区别,其他的地方基本上都是一样的。下面,飞畅科技的小编来为大家详细介绍下单纤与双纤光端机的区别,一起来看看吧! 单纤光端机:接收与发送的数据在一根光纤上传输。…

ImportError: cannot import name ‘constants‘

运行ns3gym案例一直报这个错,然后重新配置了两遍环境,然而并没有什么用。通过Google Baidu搜索,建议执行以下代码 pip install --upgrade pyzmq 执行后出现以下错误 然后搜索执行代码 sudo pip3 install --ignore-installed pyzmq 安装成功…

如何在不同的浏览器中设置Selenium网格以并行执行

到目前为止,Selenium是最常用的Web自动化测试工具。 如此受欢迎的原因之一是Selenium的自动跨浏览器测试功能。 硒自动化测试可以帮助您在所有主要浏览器,所有主要操作系统甚至移动设备浏览器上进行测试。 您可以在所有功能测试中获得广泛的浏览器覆盖&a…