JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

Java中的上下文验证 java怎么实现上下移动效果

wys521 2024-11-11 16:13:14 精选教程 25 ℃ 0 评论

在这里,我们将考虑执行验证的不同方法,什么是上下文验证以及为什么它胜过所有其他方法。


绑定到数据模型的上下文无关的验证

当前大多数框架都迫使我们及其用户将验证放入数据模型中。至少对于我们大多数人来说,默认模式是将验证规则简单地绑定到数据模型中的特定字段。这种方法有什么问题?

考虑一个示例,其中客人注册了新的食物交付订单。该服务背后的公司实际上是在做饭,称为SuperFood。这就是整个用户故事的样子:

SuperFood的后端服务在将内容放入数据库之前必须确保多个约束。其中一项是确保传递电子邮件或电话号码。

现在,假设另一个用户在SuperFood中注册了一个订单,但是这次通过某种聚合服务,将其称为AggreA。尽管要执行的约束条件有所不同,但该命令与在SuperFood网站上注册的命令没有太大不同。例如,传递电话号码对于该聚合器是必不可少的,而电子邮件是可选的。

现在,第三个用户在SuperFood中注册了一个订单,然后她通过其他聚合服务AggreB进行了订购。对于那个,不需要传递电话号码,但是电子邮件是必须的。

因此,我们有以下情况。我有一个订单的单个数据模型,但是至少有三个带有不同约束集的上下文。我可以采用传统方式:引入一个与数据库行相对应的实体,并通过注释或配置文件或我习惯的任何方式施加这些约束。数据模型验证支持以下方法:

Java


1

@ValidContactInfo

2

public class User

3

{

4

private String phoneNumber;

5

6

private String email;

7

8

// ...

9

}

自定义注解ValidContactInfo最终将我们带到自定义验证器,例如ContactInfoValidator。它最清晰的实现反映了产品经理的心智模型,它类似于以下内容(使用伪代码):

Java


1

If order is being registered through site, then either email or phone number must be present.

2

If order is being registered through AggreA, phone is required.

3

If order is being registered through AggreB, email is required.

主要目的是找出在某种程度上它究竟是什么范围内操作的具体方案。

数据模型方式意味着我们应该在验证器中执行此操作,并从实体数据对象中获取字段。这样一来,我相信它是最不受欢迎的一种,因为我们无法使用域模型的强大功能,而必须将验证逻辑驻留在服务类中。简化后,大致如下所示:

Java


1

public class ContactInfoValidator

2

{

3

public boolean isValid(Order order)

4

{

5

if (order.getSource.equals(new Site())) {

6

return order.getPhone() != null || order.getEmail() != null;

7

} else if (order.getSource.equals(new AggreA())) {

8

return order.getPhone() != null;

9

} else if (order.getSource.equals(new AggreB())) {

10

return order.getEmail() != null;

11

}

12

13

throw new Exception("Unknown source given");

14

}

15

}

现在,想象一下,如果请求变得越来越复杂,您的验证代码就会变成一团糟。

可以说是更好的:域对象中与上下文无关的验证

通常,上一个示例中显示的验证逻辑会失控。在这种情况下,将其放在负责业务逻辑的域对象中可能会更有利。此外,传统上,它是Web开发人员倾向于首先进行测试的域代码。它可能看起来像以下(记的命名:我改名Order,以OrderFromRequest强调它和域秩序之间的差异):

Java


1

public class DomainOrder

2

{

3

public DomainOrder(OrderFromRequest orderFromRequest, HttpTransport httpTransport, Repository repository)

4

{

5

// set private fields

6

}

7

8

public boolean register()

9

{

10

if (this.isRegisteredThroughSite() && this.isValidForRegistrationThroughSite()) {

11

// business logic 1

12

} else if (this.isRegisteredThroughAggreA() && this.isValidForRegistrationThroughAggreA()) {

13

// business logic 2

14

} else if (this.isRegisteredThroughAggreB() && this.isValidForRegistrationThroughAggreB()) {

15

// business logic 3

16

}

17

}

18

19

private boolean isRegisteredThroughSite()

20

{

21

return orderFromRequest.getSource.equals(new Site());

22

}

23

24

private boolean isValidForRegistrationThroughSite()

25

{

26

return orderFromRequest.getPhone() != null || orderFromRequest.getEmail() != null;

27

}

28

}

但是出现了收集错误并将其映射到UI的问题。据我所知,没有干净的解决方案。

特定于具体用户故事的上下文验证

对我来说,验证有一个明确的目的:告诉客户他们的要求到底出了什么问题。但是究竟应该去验证什么呢?这取决于您对域模型的看法。我认为,域模型中的对象表示上下文无关的“事物”,可以由特定方案以任何可能的方式对其进行编排。它们没有任何特定于上下文的约束。他们只检查通用规则,那些规则必须是真实的,否则,那东西根本不可能是那样。当您根本无法创建处于无效状态的对象时,这反映了一种始终有效的方法。

例如,有快递ID之类的东西。它只能包含UUID值。而且,我绝对希望确保确实如此。通常如下所示:

Java


1

public class CourierId

2

{

3

private String uuid;

4

5

public CourierId(String uuid)

6

{

7

if (/*not uuid*/) {

8

throw new Exception("uuid is invalid");

9

}

10

11

this.uuid = uuid;

12

}

13

}

引入自己的UUID接口以及一些实现会更好:

Java


1

public class FromString implements CourierId

2

{

3

private UUID uuid;

4

5

public FromString(UUID uuid)

6

{

7

this.uuid = uuid;

8

}

9

10

public String value()

11

{

12

return this.uuid.value();

13

}

14

}

通常,领域模型不变式非常基本和简单。所有其他更复杂的上下文特定检查都属于特定控制器(或应用程序服务或用户故事)。这就是Validol派上用场的地方。您可以先检查与格式相关的基本验证,然后进行复杂的验证。

上下文验证示例

考虑以下JSON请求:

JSON


1

{

2

"delivery":{

3

"where":{

4

"building":1,

5

"street":"Red Square"

6

}

7

}

8

}

验证可能如下所示:

Java


1

new FastFail<>(

2

new WellFormedJson(

3

new Unnamed<>(Either.right(new Present<>(this.jsonRequestString)))

4

),

5

requestJsonObject ->

6

new UnnamedBlocOfNameds<>(

7

List.of(

8

new FastFail<>(

9

new IsJsonObject(

10

new Required(

11

new IndexedValue("delivery", requestJsonObject)

12

)

13

),

14

deliveryJsonObject ->

15

new NamedBlocOfNameds<>(

16

"delivery",

17

List.of(

18

new FastFail<>(

19

new IndexedValue("where", deliveryJsonObject),

20

whereJsonElement ->

21

new AddressWithEligibleCourierDelivery<>(

22

new ExistingAddress<>(

23

new NamedBlocOfNameds<>(

24

"where",

25

List.of(

26

new AsString(

27

new Required(

28

new IndexedValue("street", whereJsonElement)

29

)

30

),

31

new AsInteger(

32

new Required(

33

new IndexedValue("building", whereJsonElement)

34

)

35

)

36

),

37

Where.class

38

),

39

this.httpTransport

40

),

41

this.dbConnection

42

)

43

)

44

),

45

CourierDelivery.class

46

)

47

)

48

),

49

OrderRegistrationRequestData.class

50

)

51

)

52

.result();

我承认,对于那些第一次看到该代码并且完全不熟悉域的人来说,这可能看起来很恐怖。不要害怕,事情并没有那么复杂。让我们逐行考虑发生了什么。

Lines 1-4:检查输入请求数据是否表示格式正确的JSON。否则,请快速失败并返回相应的错误。

Line 5:如果采用格式正确的JSON,则会调用闭包,并传递JSON数据。

Line 6:JSON结构已验证。高层结构是命名实体的未命名块。它非常类似于Map。

Line 7:暗含带有单个命名块的列表。

Line 11:叫做delivery。

Line 10:这是必需的。

Line 9:必须表示一个JSON对象。

Line 14:如果满足所有先前的条件,则调用闭包。否则,整个过程将快速失败并返回适当的错误。

Line 15:命名的块delivery由其他命名的实体组成。

Line 19:即where阻止。但这不是必需的。

Line 20:如果存在,则调用闭包。

Line 23:命名块where由其他命名实体组成。

Line 28:即street…

Line 27:…必填;

Line 26:并以字符串表示。

Line 33:和building,这是……

Line 32:也是必需的;

Line 31:,并且应表示为整数。

Line 37:如果先前的所有检查均成功,Where则创建该类的对象。老实说,这不是一个成熟的对象。它只是一个数据结构,具有对字段的方便,类型提示和IDE自动完成的访问。

Line 22:如果基础检查通过,则确保地址存在。注意第二个参数httpTransport。它用于请求一些第三方服务来检查地址是否存在。

Line 21:Aaaand,最后,我们要确保在该区域启用快递。为此,我们需要数据库访问权限,因此需要dbConnection论证。

Line 45:如果一切正常,CourierDelivery则创建一个对象。它只有一个参数,一个Where类。

Line 49:最后,OrderRegistrationRequestData创建并返回对象。

我有意将所有验证代码放在一个类中。如果数据结构确实很复杂,建议您为每个块创建一个类。在此处查看示例。

结论

就是这样。通过这样一个简单的请求,这种方法可能(实际上确实)看起来像是过分杀伤,尽管在更复杂的请求中却显得尤为突出。

最后,开发这么多年我也总结了一套学习Java的资料与面试题,如果你在技术上面想提升自己的话,可以关注我,私信发送领取资料或者在评论区留下自己的联系方式,有时间记得帮我点下转发让跟多的人看到哦。


本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表