架构实战(3)——优雅的使用列举
本人负责一个专案上线了,非常开心。这个专案是To C的,未来应用的访问压力较大,并且要求高可用性,提供7*24小时的不间断服务。根据专案的业务需求以及团队情况,采用Spring Cloud微服务架构。把用到的技术,和解决的技术问题,做一个总结。
在Java应用程序的开发中,列举是经常被用到的功能,常常被用来约束几个特定的值,例如状态、型别、性别等。在我们的应用中,也大量应用了列举,并且做了一些的改进,以期能够更优雅的使用列举。
在Java中,列举是用enum关键字来定义的。对其使用的过程中,做了更强的约束,定义了所有列举必须实现的界面,BaseEnum,要求必须满足有value和desc两个值。value表示真实的值,desc表示其描述。

用法非常简单,实现BaseEnum界面,并指定value和desc的型别就可以了。以性别的列举为例:

优雅的使用列举,需要解决四个问题:
一、列举型资料的储存和读取,能够把value储存到表中的字段,并且在读取资料的时候,能够正确对映到实体类(entity)的属性。
二、列举型资料的能够在前端被简单的使用。资料要通过json的方式输出到前端,前端如何才能更简单有效的使用列举,至关重要。
三、列举型资料的接收。前端或者其他呼叫方,通过列举值呼叫服务,服务能够正确的接收到列举。
四、列举型资料的快取,能够正确的从redis中读写。能够解决上述4个问题,就能保证列举在服务内部、服务之间以及前后端之间正确传递,就像使用Integer、String一样简单的使用列举值。
一、列举型资料的储存和读取。
数据库的读写是采用MyBatis框架实现的,并且应用了一个外挂,是baomidou。这个外挂确实非常值得推荐,对entity的CURD提供了预设支援。更重要的是,也提供了对列举的支援,只需要实现IEnum界面就可以了。getValue的值,就是储存到数据库字段的值。

再来看一下我定义的BaseEnum界面,是继承了IEnum界面的,也就是说,实现BaseEnum界面的列举,都预设实现了IEnum界面。
如果没有用到baomidou外挂怎么办?MyBatis提供了对型别的扩充机制,下一节《优雅的使用字典》中详细描述。
二、列举值的前端应用。前端对列举值的应用,无外乎展示和编辑/选取两种场景。
展示场景非常简单,在页面上正确展示描述(desc值)就可以了。比较一般的做法就是,给前端返回value值,前端根据value值替换desc进行展示。这样做前端工作量有点儿大,如果后端的列举值有变动,前端的程式码需要同步进行调整。我的解决方案就是,在输出给前端资料的时候,把value和desc两个字段同时写在json文件中。前端的同学拿到资料直接使用即可,避免了转换的过程。

输出结果如上所示,性别(gender)字段在json中变成了两个字段,约定好xxTitle就是这个字段的描述(desc)值。
是怎么做到的呢?这就要感谢jackson了。jackson提供了对序列化的扩充套件能力,可以实现自定义的序列化。并且在配置中,使用jackson作为预设的讯息转换器。看到这里,应该算是打开了一扇大门,通过这种方法,可以自定义特定型别资料的例项化。我把关键程式码粘贴出来。


再来看一下编辑/选取的场景。我截图了微信公众号的后台,看下状态字段,有3种状态,这里就可以用列举来实现。最直接的方法,就是前后端的工程师同时定义列举,并约定好取值,看似非常简单。随着列举值的增多,前后端需要保持一致,任何变动都要事先沟通,后端工程师做了修改,前端工程师必须同步修改。事情就这样变得越来越烦琐复杂。

有没有简单容易一些的方法呢?我的解决方案是给前端提供一个界面,输出所有的列举定义,前端只需根据列举的编码,选用适当的列举就可以了。看下效果:

怎么实现的呢?这里的实现逻辑类似于MapperScan,即定义好扫描的package,在这个package中的所有列举,组成一个list,然后定义controller,输出这个list就可以了。具体的程式码逻辑有些复杂,这里就不展开了。
三、列举型资料的接收。在java程式接收引数的时候,对于Post提交的json字串,是采用了jackson提供的逆序列化能力。并且呢,jackson预设就提供了对列举的支援,并不需要自定义逆序列化方式。具体就是@JsonCreator注解。

对于Get提交的资料,或者post form提交的资料,该如何处理呢?我的方案是,自定义自己的@FormParam注解,此注解可以把get引数、post form引数以及json字串,组装在一起,通过jackson转换成特定的VO。这样就完美解决了传入引数中列举值的转换。
四、列举型资料的快取。资料的快取,是储存到redis的。通过上面的介绍,已经知道jackson对列举的序列化、逆序列化都有了很好的支援。快取也是一样的道理,只要解决这两个过程就好了。储存和读取资料的时候,统一约定好用如下定义的ObjectMapper就可以了。

通过上面的介绍,在整个应用过程中,列举型别的资料就像Integer、String等一样,被优雅的使用。在这个过程中,解决问题的核心出发点,是序列化和逆序列化。我这里采用了jackson作为json的序列化工具包,如果采用其他工具包,也应有类似的解决方案。
后续的内容可能有:
优雅的使用字典自实现的界面代理读写分离分散式ID使用快取使用伫列使用retryeureka安全模式。。。