小谈网关项目中的设计模式
在日常的开发过程中,我们常常要处理不同来源的数据。数据可能来自不可靠的外部系统、不可靠的用户输入和甚至设计有误的数据库表,因此,对数据有效性进行验证是必要的工作。
开源工具ABAP Data Validator是一个使用ABAP开发的数据验证工具,它可以简化开发者在这方面的工作。本文将介绍它的用法和一些设计思路。
本文链接:https://www.cnblogs.com/hhelibeb/p/12206648.html
原创内容,转载请注明
目的
具体而言,ABAP Data Validator将通过以下的思路简化数据有效性验证方面的工作:
- 提供统一的检查接口,让开发者通过单次方法调用就可以实现对数据的检查。
- 将验证逻辑集中实现,避免相似的检查代码分散在系统各处造成的逻辑不一致,从而降低相关程序的维护成本。
- 避免检查过程中的潜在dump,减少开发者处理dump问题的精力花费。
为了实现以上目的,该工具实现了一些功能:
- 内置常见的验证逻辑。
- 可配置的检查规则。
- 可扩展的检查程序。
- 异常的统一处理。
支持的检查列表
ABAP Data Validator目前支持以下类型的检查(持续更新中):
- 日期.
- 时间.
- 时间戳.
- 邮件地址.
- INT4.
- 正则字符串.
- URL.
- JSON.
- HEX.
- IMEI.
- GUID.
- BASE64.
- HTML (实验性的).
此外,它也支持对内表字段的必填检查、根据数据元素的类型进行检查等功能,下文会详细介绍。
使用
ABAP Data Validator支持多种检查方式,下面会由简单到复杂进行逐一介绍。
对单一字段的直接检查
对于每种数据类型,ABAP Data Validator会有一个专门的检查类,可以用这些检查类的is_valid方法来检查变量的值是否有效,就像使用abap的内置函数那样。比如,要检查一个字符串是否是有效的邮箱地址,可以用如下代码实现,
IF zcl_adv_email_check=>is_valid( 'example@github.com' ). "do something ENDIF.
所有的检查类都实现了接口zif_adv_check,因此它们都拥有静态方法is_valid,如下图(为了方便阅读,图中没有包含全部的实现类),
对于类方法而言,is_valid是个别名,也可以不使用别名来调用接口方法:
IF zcl_adv_email_check=>zif_adv_check~is_valid( 'example@github.com' ). "do something ENDIF.
当然并不推荐这种方式,因为会让代码变长。
检查类的命名规则是ZCL_ADV_类型名_CHECK。
使用参考数据元素检查
为了便于使用,方法is_valid的定义十分简单,只有一个输入参数、一个返回值,不包含任何异常,这使得它无法胜任数量、金额等类型(一般而言是P类型,包含长度和小数位定义)字段的检查。为了解决这一问题,ABAP Data Validator提供了参考数据元素进行检查的用法。可以使用类zcl_adata_validator的方法validate_by_element使用该功能,示例如下,
DATA(result) = NEW zcl_adata_validator( )->->validate_by_element( data = data element = 'MENGE_D' "数量 ).
result是一个结构,包含两个字段valid和type,如果data的值无法转换为数字、或者转换后溢出的话,result-valid将被赋值为abap_false(即空);如果data是一个有效值,那么rresult-valid将被赋值为abap_true(即’X’)。
正常情况下,result-type总会被赋值为数据元素的类型,对P类型,则为固定值’PACKED‘(c_type_packed)。但如果输入的数据元素无效的话,type则会被赋值为固定值’Invalid Type‘(c_type_invalid)。
目前支持以下类型的数据元素:
- 日期
- 时间
- 时间戳
- INT4
- GUID
- HEX
- DEC(P类型)
为了保持检查方法的简单性,validate_by_element方法的定义同样不包含异常。任何异常都会在方法内部被处理。出现异常时,result-valid会被赋值为abap_false,type会被赋值为’Invalid Type‘。
内表检查
ABAP Data Validator可以根据指定的规则对内表进行检查,如下所示,将规则my_rules和内表uploaded_data传入类zcl_adata_validator的方法validate,可以得到检查结果内表results,
【5min+】传说中的孪生兄弟? Memory and Span
TRY. DATA(results) = NEW zcl_adata_validator( )->validate( rules = my_rules data = uploaded_data ). CATCH zcx_adv_exception INTO DATA(ex). DATA(msg) = ex->get_text( ). ENDTRY.
检查规则rules用于设定检查逻辑。
rules的类型定义如下,
TYPES: BEGIN OF ty_rule, fname TYPE name_komp, "字段名 required TYPE abap_bool, "必填 initial_or_empty TYPE abap_bool, "必须为空或初始值 user_type TYPE ty_spec_type,"检查类型(参考类zcl_adata_validator的常量列表) regex TYPE string, "自定义正则表达式 regex_msg TYPE string, "自定义正则表达式检查失败消息 ref_element TYPE rollname, "参考数据元素 END OF ty_rule. TYPES: ty_rules_t TYPE HASHED TABLE OF ty_rule WITH UNIQUE KEY fname.
简单的示例如下,
DATA: rules TYPE zcl_adata_validator=>ty_rules_t. *字段1为必填字段,类型是日期,字段2非必填,类型为邮件地址 rules = VALUE #( ( fname = 'FIELD1' required = abap_true user_type = zcl_adata_validator=>c_type_date ) ( fname = 'FIELD2' user_type = zcl_adata_validator=>c_type_email ) ).
扩展检查功能
使用ABAP Data Validator自带的检查逻辑可以满足很多场景下的基本检查需要,但是你也许还有更多的检查逻辑。有2种方式可以实现检查功能的扩展。
- 通过rules-regex传入正则表达式。
- 创建新的检查类型:定义新的类型名,创建它的检查类,实现接口zif_adv_check,将自定义检查逻辑写在is_valid方法的实现中。接着,将类型名和类名传入zcl_adata_validator的构造方法constructor中。这样就可以使用新的检查类型了。
正则表达式示例:如果你不仅要检查一个字段的值是否为邮件地址,还要验证它是gmail邮箱,那么可以把正则表达式’gmail\.com$‘复制给rules-regex,
DATA: rules TYPE zcl_adata_validator=>ty_rules_t. DATA: cases TYPE ty_case_t. cases = VALUE #( ( field3 = 'ZZZ2@gmail.com') "正确,是gmail邮箱 ( field3 = 'ZZZ2@qq.com') "不正确,非gmail邮箱 ). rules = VALUE #( ( fname = 'FIELD2' user_type = zcl_adata_validator=>c_type_email regex = 'gmail\.com$' regex_msg = 'Only gmail supported') ).
或者创建一个新类型和它的检查类,并且把它的检查类配置传入constructor,
DATA: check_class_config TYPE zcl_adata_validator=>ty_check_config_t. check_class_config = VALUE #( ( type = zcl_adata_validator=>c_type_new class = 'ZCL_NEW_VALIDATOR' ) ). TRY. DATA(result) = NEW zcl_adata_validator( check_class_conifg = check_class_config )->validate( rules = rules data = cases ). CATCH zcx_adv_exception INTO DATA(ex). DATA(msg) = ex->get_text( ). ENDTRY.
注意:constructor中已经包含了默认的检查类配置和检查消息配置,它们是Hard coding,一旦传入自定义配置,它们就会被覆盖。
可以按需对方法进行重定义、或者传入自己的配置。比如,从数据库或其它来源读取配置,这样可以在不修改既有代码的情况下扩展程序的功能。
DATA: check_class_config TYPE zcl_adata_validator=>ty_check_config_t. SELECT * FROM my_config_table INTO TABLE @check_class_config. TRY. DATA(result) = NEW zcl_adata_validator( check_class_conifg = check_class_config )->validate( rules = rules data = cases ). CATCH zcx_adv_exception INTO DATA(ex). DATA(msg) = ex->get_text( ). ENDTRY.
项目地址
https://github.com/hhelibeb/abap-data-validator
我提交了绝大部分代码,此外还有2位贡献者larshp和FreHu,他们帮助修正了代码格式和readme方面的一些问题。
欢迎参与这个工具的开发!如果你发现程序有任何问题,也可以在github提交issue告知我。
Q & A
怎样定义有效性?
有效性的定义十分重要,如果有效的定义是模糊的,那检查也失去了意义。
对于ABAP Data Validator,如果一个检查类型存在对应的ABAP数据类型(比如日期,时间戳等),那么“有效”是指:
- 值可以直接被赋值给相应类型的ABAP变量,不产生异常,也不产生无意义的值。
以时间戳为例,
- 2021-12-21 00:00:00 “无效
- 19000000235959 “无效
- 20200101235959 “有效
2021-12-21 00:00:00’\可能在某些情境下是合理的时间戳值,但是对于ABAP Data Validator而言,它是无效的,因为将它赋值给时间戳类型的变量会导致dump。而’19000000235959’虽然可以被赋值给ABAP时间戳变量,但因为没有实际意义,它同样是无效值。
这种类型检查的逻辑主要由SAP提供的标准功能实现,时间戳的检查实际上由正则检查、标准函数DATE_CHECK_PLAUSIBILITY和TIME_CHECK_PLAUSIBILITY组成。
如果一个检查类型没有对应的ABAP数据类型,ABAP Data Validator的检查逻辑收集自一些相对权威的来源,这些检查逻辑通常会符合行业标准。来源包括,
- Wikipedia (IMEI)
- Mozilla Developer Network (Email)
- regex-weburl.js (URL)
- W3C Markup Validation Service (HTML)
ABAP Data Validator的检查结果可靠吗?
绝大部分检查逻辑是成熟的,且它们都包含单元测试。你可以在单元测试中加上自己的用例来验证它们的行为是否符合你的期望。
HTML检查是个例外,它是唯一一个需要调用外部API(https://validator.w3.org/)的检查,而且w3.org提供的API本身也只是实验性的。请只把它的检查结果当作参考。
目前,我已经在工作中部分地应用了ABAP Data Validator,看起来工作良好。
为什么是多个类,而不是一个类、多个方法?
一个很自然的问题是为什么不把这些检查功能放到一个类里面?毕竟,有时候为了几行代码的检查逻辑创建一个新类太过奢侈。 如果将所有检查放在同一个类里面,将会有一些好处,
- 减少了全局类数量,方便记忆/不易产生命名冲突。
- 方法定义更灵活,不需要接受接口ZIF_ADV_CHECK的限制。
但是也会带来一些损失,
- 不同类型的检查之间的耦合度增加,部署单个方法的更新时,可能会导致更多的LOAD_ PROGRAM CLASS_ MISMATCH异常。
- 不同开发者的ABAP服务器可能环境不同,如果个别方法不可用,问题将波及整个类,导致其它检查也不可用。
- 失去了接口接口ZIF_ADV_CHECK的约束后,动态编程会变得复杂,也难以保持检查规则的简单性。
总之,经过一番权衡之后我选择了当前的设计,尽管它也存在某些缺点,如果你有更好的想法,请告诉我。
【自制操作系统01】硬核讲解计算机的启动过程