引子
很多朋友可能会因为自己做的工作不是特别核心或者业务简单而引起面试中没有自信。但是很多公司面试的时候是可以接受面试者之前岗位的并发量、交易量低一些的。比如我们要招聘和我们交易量同等级或者以上的出来的人才,业界本来就没有多少,但我们还是要招人的。所以很多时候更偏向于考察面试者的设计底蕴、思考和解决问题的能力。
我建议面试时,面试者要争取主动权,主动引导面试。一般作为面试官也很乐意被面试者引导。因为面试官的职责是发现面试者的技术特长,为此我们绞尽脑汁地从简历中、自我介绍中去发掘。如果面试者可以自己有完整清晰的思路,是面试官求之不得的事情。
假设我是一个面试者,近几年做的都是XX后台管理系统。后台管理系统嘛,没有高并发、没有高可用需求、没有复杂架构,属于三无系统。要是我的话,会把自己的以下知识技能放到项目介绍里展示给面试官:
- 可测试性设计
- 谦卑对象模式
- RESTful风格
- 领域驱动设计DDD
- 充血模型
- CQRS
可测试性设计
谦卑对象模式
作为一个后台管理系统,一般场景下微服务化的价值不大。DDD领域驱动设计这种专门用于复杂问题的解决办法在这里多半也是杀鸡用牛刀。后面会讲到一些DDD技巧还是可以用的。实际中大多是采用前后端分离的架构,这种架构实践一方面是动静分离,便于缓存优化等性能考虑,另一方面也是一个出于可测试性的考虑。分离出可自动化测试的接口层和测试难度高的展现层。
展现层对象等测试难度高的对象在整洁架构中被称为谦卑对象。通过拆分不同的类或者模块,来区分容易测试的行为和不容易测试的行为,这种设计上的隔离模式被称为谦卑对象模型。
现在的很多设计对程序的可测试性提供了友好的改进和支持。比如:程序调用数据库执行操作,mybatis等持久层框架将把sql以接口的形式对外提供服务,接口有成熟的工具来做mock打桩,这是比较典型的谦卑对象模式。
另外一个比较典型的比较典型的谦卑对象模式是feign。netflix的feign把原本需要手写的httpClient(或者OKHttp)代码使用接口调用的的形式,实现了命令式到声明式的转换。同时,谦卑对象和非谦卑对象之间有很好的隔离层,也对测试更友好。对feign想做进一步了解的可参考我之前的文章《Java&Spring过时的经典语录》,这里简单举个例子:
public interface TestHttpService {
@RequestLine("GET /xxxx?appkey={appkey}&ips={ip}&username={username}&operator={operator}") Response getTest(@Param(value = "appkey") String appkey, @Param(value = "ip") String ip, @Param(value = "username") String username, @Param(value = "operator") String operator); }
RESTful
说起后台管理系统的接口层,RESTful风格的接口是比较流行的最佳实践。虽然这个被提了很多年了,实际严格按照这种风格设计的接口并不多。大多数系统的接口风格像是跟着江南七怪学武的郭靖一般,武功路数驳杂不成体系。
来做个判断题:
下面的代码,类上用了RestController的注解,这是RESTful风格的代码吗?
@RestController public class JacksonController { @Resource private User user; @GetMapping("/writeStringAsString") public String writeStringAsString(String toWrite) throws Exception { System.out.println(user.getAge()); ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.writeValueAsString(toWrite); }}
REST(英文:Representational State Transfer,简称REST) 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
理论上REST架构风格并不是绑定在HTTP上,但是REST本身受Web技术的影响很深, 目前HTTP是唯一与REST相关的实例。
咱们来看看需要满足哪些约束条件和原则。
资源设计规则:
1>不用大写;
2>用中杠-不用下杠_;
3>用名词不用动词;
4>URI中的名词表示资源集合,使用复数形式。
动作设计规则:
1>GET(SELECT):从服务器取出资源(一项或多项)。
2>POST(CREATE):在服务器新建一个资源。
3>PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
4>PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
5>DELETE(DELETE):从服务器删除资源。
返回结果规则:
与HTTP协议标准基本没有新的约束。要注意content-type的accept,包含accept-encoding。之前出个我在测试环境出个一个问题,我们自动化测试回归平台不支持gzip,但是请求时带了gzip,其实平台并不支持导致乱码。
通过上面的约束条件和原则咱们来总结一下为什么叫REST:"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
如果大家都理解,那上面判断题的答案也呼之欲出了:因为不满足相应的约束条件和原则,所以不是RESTful风格。@RestController 只是让资源返回结果是RESTful风格的。但不管是不是RESTful风格,都是URI。统一资源标识符(Uniform Resource Identifier,URI)是一个用于标识某一互联网资源名称的字符串。 只要定位到资源了,都是URI。
领域驱动设计DDD
充血模型
贫血模型是指实体对象或者说是POJO只包含简单的set、get方法,充血模型认为一个对象是拥有状态和行为的。什么叫状态和行为呢?举个例子:
@Setter @Getter @ToString @EqualsAndHashCode public class Pojo { private String name ; private String status; public int getStatus() { return NumberUtils.toInt(status); } }
上面类代码上用了lombok的@Setter、@Getter注解之外,还用了@ToString、@EqualsAndHashCode,这两个虽然是Object对象的基本方法,实际上也是做了状态和行为的事情,而不只是@Setter、@Getter的数据存取。与之类似的还有上面的int getStatus,实际上进行了类型转换这个行为。
现在针对到底使用贫血模型还是充血模型更好说法不一。我个人更倾向于使用充血模型,因为这种方法从领域上更内聚。但是很多人不建议使用,主要是因为充血模型对个人能力有更高的要求。充血模型开发者需要自己去识别哪些是实体领域中的。对于一般的spring开发者来说,个人经验上有个简单的办法:凡是要引用@Service、@Component的都不要放到里面,之前本来要放到XXUtils的建议看看更符合哪个实体领域,不要一股脑放到util包下面,看看是否可以划分到实体领域中。
CQRS
CQRS — Command Query Responsibility Segregation,顾名思义是将 command 与 query 分离的一种模式。CQRS 将系统中的操作分为两类,即「命令」(Command) 与「查询」(Query)。命令则是对会引起数据发生变化操作的总称,即我们常说的新增,更新,删除这些操作,都是命令。而查询则和字面意思一样,即不会对数据产生变化的操作,只是按照某些条件查找数据。
在后台系统中,某些查询操作可能会过于频繁,比如页面定时刷新获取数据。这些查询操作不需要保证每次都成功。而命令操作如果失败则涉及到事务回滚等操作,需要保证操作的成功率。这时候可以使用CQRS隔离,比如将检查流量和命令流量使用hystrix隔离,架构清晰了,还可以画出下面这样清晰的架构图:
总结
上面都是后台管理系统中常用的一些技术,其实还有ACL(防腐层),批量操作的隔离、熔断、分片,数据异步转同步等限于篇幅这里就不介绍了。只要面试中能够引导面试官提问这方面的技术并且可以讲得明明白白,已经可以超过大部分的面试者。
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。