纹理和基元_自定义基元和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/331983.shtml

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

相关文章

vim 常用命令

文章目录普通命令模式下技巧汇总切换到插入模式切换到可视模式切换至底行命令模式复制内容复制命令的记忆技巧粘贴/恢复修改/改写删除/剪切字符大小写切换选择文本游标移动剪切文本/移动文本滚屏/翻页撤回查看文档状态查找/搜索字符串查看历史命令查看历史搜索记录多窗口操作保…

常见花材的固定的方法有哪些_旋流器常见的故障及处理方法有哪些?

旋流器除了用在磨矿循环中的分级作业外,还可以用于脱泥、脱水以及脱除浮选药剂等。此外,还可以用做重悬浮液选矿,其分选粒度可达0.1毫米左右。旋流器有许多优点,构造简单,没有运动部件,单位容积的处理能力大…

二叉堆(优先队列)

【0】README 0.1) 本文总结于 数据结构与算法分析,但源代码均为原创;旨在理清二叉堆(优先队列) 堆的其他操作及其应用, 以便让朋友些知道为什么要学习优先队列; 【1】二叉堆 1.0)…

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

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

python训练营朋友圈留言_用Python发一个高逼格的朋友圈【附代码】

今天二胖要给大家介绍一个Python库:PIL(Python Image Library)下面我们用一个实际的例子看看50行python代码可以做什么神奇的事情这是二胖发的一个朋友圈切图前是一张图切图后就是九张图啦成功霸屏除了可以处理规整的正方形图片还可以处理非规则的图片比如下面这张宽…

vim 编辑器的快捷键

文章目录命令终端界面滚屏命令终端页签切换缓存区切换/文件切换分割窗口/打开新窗口切换窗口移动/旋转/移出窗口关闭窗口调整窗口大小底行命令模式下的编辑快捷键vim 很多指令或者快捷键是大小写敏感。命令终端界面滚屏 快捷键说明Fn ←向上滚屏到开始处Fn →向下滚屏到末尾…

关于二叉堆(优先队列)的其他操作及其应用

【0】README 0.1)本文总结于 数据结构与算法分析;源代码均为原创, 旨在了解到我们学习了优先队列后,还能干些什么东西出来, 增加学习的interest; 0.2)以下列出了 关于二叉堆(优先队…

gradle junit5_JUnit 5和Selenium –使用Gradle,JUnit 5和Jupiter Selenium设置项目

gradle junit5Selenium是一组支持浏览器自动化的工具和库,主要用于Web应用程序测试。 Selenium的组件之一是Selenium WebDriver,它提供客户端库,JSON有线协议(与浏览器驱动程序进行通信的协议)和浏览器驱动程序。 Sele…

ubuntu 两块硬盘挂载不上_win10 轉 Ubuntu

目前用了win10兩三年。發現越來越慢,況且已習慣mac OS,所以想用自己的機子來裝個雙系統Linux,慢慢的將win的東西都轉到Ubuntu上。已清空一個磁盤300G,打算就是在這300G裡裝一個Ubuntu,不知道是否夠用(雖然很想裝在三星…

包+类导入+静态导入+类放入包中+包作用域

【0】README 0.1)本文转自 core java volume 1, 旨在理清 包和类导入的相关知识; 【1】 包 1.1) java 允许使用包将类组织起来,包可以方便组织代码,并将自己的代码与别人提供的代码库分开管理&#xff1b…

selenium自动化测试_使用Selenium自动化测试处理多个浏览器选项卡

selenium自动化测试使用Selenium进行自动化测试一直是将萌芽的自动化测试人员培养为专业人员的生命线。 Selenium是开源的,在全球范围内被广泛采用。 结果,您会得到社区的大力支持。 提供了与Selenium绑定的不同语言的多种框架。 因此,您已经…

qt和c#怎么选_请问目前做windows桌面应用程序,MFC、QT、C#哪个更好?

回答问题之前,先装个逼——没有主导过生命周期三年以上的桌面软件项目的,闭嘴。你连一个桌面软件项目的生命周期都没经历过,你凭什么做技术选型?凭信仰吗?装逼结束,正文开始。首先,非主流技术和…

java 白皮书的关键术语

【0】README 0.1) 本文转自 core java volume 1,仅供了解,所谓爱屋及乌嘛; 0.2) java的设计者编写了颇有影响力的白皮书,用来解释设计的初衷以及完成的情况,并发布了一个摘要;【1】…

当集合a为空集时a的取值范围_高中数学必修一第一章集合分节练习和章末测试题含答案[1] 2...

高中数学必修1 第一章 集合 分节练习和章末综合测试题含答案1 集合的含义与表示1、下列各组对象能否组成一个集合?(1)接近于0的数的全体; (2)2的近似值的全体; (3)平面上到点O 的距离等于1的点的全体; (4)正三角形的全体&#xff…

spring jpa 流式_从响应式Spring Data存储库流式传输实时更新

spring jpa 流式这篇文章详细介绍了从数据库到对该数据感兴趣的任何其他组件进行流更新的幼稚实现。 更准确地说,如何更改Spring Data R2DBC存储库以向相关订阅者发出事件。 对R2DBC和Spring的一点背景知识将对这篇文章有所帮助。 我以前的著作《 使用 Microsoft S…

弹窗页面交互_UI进阶知识-信息提交类弹窗该如何设计?

原文作者:风筝KK 信息提交类弹窗大家应该都比较熟悉,和其他弹窗的区别在于他有输入、选择等操作,比如我们常见的输入验证码、留言回复、充值转账、任务设置等。看上去设计都比较简单,但是当你验收时就会发现问题,为什么…

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

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

java 发展简史

【0】README 0.1) 本文转自 core java volume 1,仅供了解Java 的发展历史,它的前世今生,所谓知己知彼,百战不殆(just a joke) ; 【1】java 发展简史 1.1)java的历史要…

axios代理跨域 cli4_跨域本质及解决办法

1、什么是跨域?2、如何解决?跨域是前端所独有的,后端不存在跨域问题。是浏览器的一种安全保护手段,为了防止别人抓取、篡改你的网站数据信息。遵循同源策略、同协议(http)、同域名、同端口,少一…

如何使用eclemma插件_如何集成和使用EclEmma插件来获得良好的Junit覆盖率

如何使用eclemma插件你好朋友, 如果编写好的代码很重要,那么编写覆盖所有业务逻辑的优良Junit测试用例也同样重要。通过编写覆盖业务逻辑的Junit测试用例,我们实际上确保代码的每种方法都能正常工作按照预期进行,因此减少了在软…