Java架构-一些设计上的基本常识

简介:

最近给团队新人讲了一些设计上的常识,可能会对其它的新人也有些帮助, 把暂时想到的几条,先记在这里。

1、API与SPI分离

框架或组件通常有两类客户,一个是使用者,一个是扩展者。 API(Application Programming Interface)是给使用者用的, 而SPI(Service Provide Interface)是给扩展者用的。 在设计时,尽量把它们隔离开,而不要混在一起, 也就是说,使用者是看不到扩展者写的实现的。

比如:

  1. 一个Web框架,它有一个API接口叫Action, 里面有个execute()方法,是给使用者用来写业务逻辑的。然后,Web框架有一个SPI接口给扩展者控制输出方式。

  2. velocity模板输出还是用json输出等, 如果这个Web框架使用一个都继承Action的VelocityAction和一个JsonAction做为扩展方式, 要用velocity模板输出的就继承VelocityAction,要用json输出的就继承JsonAction, 这就是API和SPI没有分离的反面例子。

SPI接口混在了API接口中,合理的方式是,有一个单独的Renderer接口,有VelocityRenderer和JsonRenderer实现, Web框架将Action的输出转交给Renderer接口做渲染输出。

反正例子:

030170259741e334bed95faa38ebd075af8e0ee5

正确例子:

837fc2b88621132186f654e8c1d66d1aad791c0a

2、服务域/实体域/会话域分离

任何框架或组件,总会有核心领域模型,比如:

实体域:像Spring的Bean,Struts的Action,Dubbo的Service,Napoli的Queue等等 。这个核心领域模型及其组成部分称为实体域,它代表着我们要操作的目标本身, 实体域通常是线程安全的,不管是通过不变类,同步状态,或复制的方式。

服务域:也就是行为域,它是组件的功能集,同时也负责实体域和会话域的生命周期管理。比如Spring的ApplicationContext,Dubbo的ServiceManager等, 服务域的对象通常会比较重,而且是线程安全的,并以单一实例服务于所有调用。

会话域:就是一次交互过程, 会话中重要的概念是上下文,什么是上下文? 比如我们说:“老地方见”,这里的“老地方”就是上下文信息, 为什么说“老地方”对方会知道,因为我们前面定义了“老地方”的具体内容, 所以说,上下文通常持有交互过程中的状态变量等, 会话对象通常较轻,每次请求都重新创建实例,请求结束后销毁。

简而言之: 把元信息交由实体域持有, 把一次请求中的临时状态由会话域持有, 由服务域贯穿整个过程。

实例一

9c3838a6b4604cbb0d7a1ee1ffca4d5c589783b6

实例二

284640b56e0033cb2647e8ecd399c68edcc2a470

3、在重要的过程上设置拦截接口

1.如果你要写个远程调用框架,那远程调用的过程应该有一个统一的拦截接口;
2.如果你要写一个ORM框架,那至少SQL的执行过程,Mapping过程要有拦截接口;
3.如果你要写一个Web框架,那请求的执行过程应该要有拦截接口;

等等,就可以自行完成,而不用侵入框架内部。拦截接口,通常是把过程本身用一个对象封装起来,传给拦截器链。

比如:远程调用主过程为invoke(),那拦截器接口通常为invoke(Invocation),Invocation对象封装了本来要执行过程的上下文,并且Invocation里有一个invoke()方法, 由拦截器决定什么时候执行。同时,Invocation也代表拦截器行为本身, 这样上一拦截器的Invocation其实是包装的下一拦截器的过程, 直到最后一个拦截器的Invocation是包装的最终的invoke()过程, 同理,SQL主过程为execute(),那拦截器接口通常为execute(Execution),原理一样, 当然,实现方式可以任意,上面只是举例。

01c8a113fabcddcd79703efeaef71da257719161

4、重要的状态的变更发送事件并留出监听接口

这里先要讲一个事件和上面拦截器的区别:

拦截器:是干预过程的,它是过程的一部分,是基于过程行为的。 事件:是基于状态数据的,任何行为改变的相同状态,对事件应该是一致的,事件通常是事后通知,是一个Callback接口,方法名通常是过去式的,比如onChanged()。

比如远程调用框架,当网络断开或连上应该发出一个事件,当出现错误也可以考虑发出一个事件, 这样外围应用就有可能观察到框架内部的变化,做相应适应。

cfef7be8dfe2b2a7615d83a85522a1afa4dc06e1

5、扩展接口职责尽可能单一,具有可组合性

比如,远程调用框架它的协议是可以替换的, 如果只提供一个总的扩展接口,当然可以做到切换协议, 但协议支持是可以细分为底层通讯,序列化,动态代理方式等等, 如果将接口拆细,正交分解,会更便于扩展者复用已有逻辑,而只是替换某部分实现策略, 当然这个分解的粒度需要把握好。

6、微核插件式,平等对待第三方

大凡发展的比较好的框架,都遵守微核的理念

Eclipse的微核是OSGi, Spring的微核是BeanFactory,Maven的微核是Plexus。

通常核心是不应该带有功能性的,而是一个生命周期和集成容器, 这样各功能可以通过相同的方式交互及扩展,并且任何功能都可以被替换, 如果做不到微核,至少要平等对待第三方, 即原作者能实现的功能,扩展者应该可以通过扩展的方式全部做到, 原作者要把自己也当作扩展者,这样才能保证框架的可持续性及由内向外的稳定性。

7、不要控制外部对象的生命周期

比如上面说的Action使用接口和Renderer扩展接口, 框架如果让使用者或扩展者把Action或Renderer实现类的类名或类元信息报上来。然后在内部通过反射newInstance()创建一个实例, 这样框架就控制了Action或Renderer实现类的生命周期, Action或Renderer的生老病死,框架都自己做了,外部扩展或集成都无能为力。

好的办法是让使用者或扩展者把Action或Renderer实现类的实例报上来, 框架只是使用这些实例,这些对象是怎么创建的,怎么销毁的,都和框架无关, 框架最多提供工具类辅助管理,而不是绝对控制。

8、可配置一定可编程,并保持友好的CoC约定

因为使用环境的不确定因素很多,框架总会有一些配置, 一般都会到classpath直扫某个指定名称的配置,或者启动时允许指定配置路径, 做为一个通用框架,应该做到凡是能配置文件做的一定要能通过编程方式进行, 否则当使用者需要将你的框架与另一个框架集成时就会带来很多不必要的麻烦。

另外,尽可能做一个标准约定,如果用户按某种约定做事时,就不需要该配置项。 比如:配置模板位置,你可以约定,如果放在templates目录下就不用配了, 如果你想换个目录,就配置下。

9、区分命令与查询,明确前置条件与后置条件

这个是契约式设计的一部分,尽量遵守有返回值的方法是查询方法,void返回的方法是命令, 查询方法通常是幂等性的,无副作用的,也就是不改变任何状态,调n次结果都是一样的。比如get某个属性值,或查询一条数据库记录。

命令是指有副作用的,也就是会修改状态,比如set某个值,或update某条数据库记录, 如果你的方法即做了修改状态的操作,又做了查询返回,如果可能,将其拆成写读分离的两个方法。

比如:

  1. User deleteUser(id),删除用户并返回被删除的用户,考虑改为getUser()和void1的deleteUser()。

  2. 另外,每个方法都尽量前置断言传入参数的合法性,后置断言返回结果的合法性,并文档化。

10、增量式扩展,而不要扩充原始核心概念

我们平台的产品越来越多,产品的功能也越来越多, 平台的产品为了适应各BU和部门以及产品线的需求。势必会将很多不相干的功能凑在一起,客户可以选择性的使用, 为了兼容更多的需求,每个产品,每个框架,都在不停的扩展, 而我们经常会选择一些扩展的扩展方式,也就是将新旧功能扩展成一个通用实现。

我想讨论是,有些情况下也可以考虑增量式的扩展方式,也就是保留原功能的简单性,新功能独立实现。我最近一直做分布式服务框架的开发,就拿我们项目中的问题开涮吧。

比如:远程调用框架,肯定少不了序列化功能,功能很简单,就是把流转成对象,对象转成流, 但因有些地方可能会使用osgi,这样序列化时,IO所在的ClassLoader可能和业务方的ClassLoader是隔离的, 需要将流转换成byte[]数组,然后传给业务方的ClassLoader进行序列化。

为了适应osgi需求,把原来非osgi与osgi的场景扩展了一下, 这样,不管是不是osgi环境,都先将流转成byte[]数组,拷贝一次。然而,大部分场景都用不上osgi,却为osgi付出了代价, 而如果采用增量式扩展方式,非osgi的代码原封不动, 再加一个osgi的实现,要用osgi的时候,直接依赖osgi实现即可。

再比如:最开始,远程服务都是基于接口方法,进行透明化调用的, 这样,扩展接口就是,invoke(Method method, Object[] args), 后来,有了无接口调用的需求,就是没有接口方法也能调用,并将POJO对象都转换成Map表示, 因为Method对象是不能直接new出来的,我们不自觉选了一个扩展式扩展, 把扩展接口改成了invoke(String methodName, String[] parameterTypes, String returnTypes, Object[] args), 导致不管是不是无接口调用,都得把parameterTypes从Class[]转成String[]。

如果选用增量式扩展,应该是保持原有接口不变, 增加一个GeneralService接口,里面有一个通用的invoke()方法, 和其它正常业务上的接口一样的调用方式,扩展接口也不用变, 只是GeneralServiceImpl的invoke()实现会将收到的调用转给目标接口, 这样就能将新功能增量到旧功能上,并保持原来结构的简单性。

再再比如:无状态消息发送,很简单,序列化一个对象发过去就行, 后来有了同步消息发送需求,需要一个Request/Response进行配对, 采用扩展式扩展,自然想到,无状态消息其实是一个没有Response的Request, 所以在Request里加一个boolean状态,表示要不要返回Response, 如果再来一个会话消息发送需求,那就再加一个Session交互。然后发现,原来同步消息发送是会话消息的一种特殊情况, 所有场景都传Session,不需要Session的地方无视即可。 如果采用增量式扩展,无状态消息发送原封不动。

同步消息发送,在无状态消息基础上加一个Request/Response处理, 会话消息发送,再加一个SessionRequest/SessionResponse处理。

91d028f025fde46e13a98e69444f42d81b2ea3f1 e7c402272b05e88826eb6e15f2dd8077f2f0d01e陌霖Java架构

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!

原文发布时间为: 2018-11-28
本文作者:陌霖Java架构
本文来自云栖社区合作伙伴“ Java技术驿站”,了解相关信息可以关注“ Java技术驿站”。
相关文章
|
26天前
|
消息中间件 架构师 NoSQL
咕炮课堂Java架构师课程
针对1-5年经验开发者,【Java架构师培训】聚焦互联网热门技术,如Redis、MongoDB、Dubbo、Zookeeper、Kafka,讲授高并发、高可用分布式解决方案。由资深讲师指导,提升技术水平。
28 2
咕炮课堂Java架构师课程
|
1月前
|
Java 开发者 微服务
Java企业应用软件系统架构演变史
Java企业应用软件系统架构演变史
28 0
|
2月前
|
监控 负载均衡 Dubbo
|
2月前
|
监控 数据可视化 关系型数据库
微服务架构+Java+Spring Cloud +UniApp +MySql智慧工地系统源码
项目管理:项目名称、施工单位名称、项目地址、项目地址、总造价、总面积、施工准可证、开工日期、计划竣工日期、项目状态等。
304 6
|
2月前
|
人工智能 监控 安全
java基于微服务架构的智慧工地监管平台源码带APP
劳务管理: 工种管理、分包商管理、信息采集、班组管理、花名册、零工采集、 现场统计、考勤管理、考勤明细、工资管理、零工签证
287 4
|
2月前
|
供应链 Java
云HIS技术架构:Angular+Nginx+Java+Spring,SpringBoot
标准数据维护 用户信息:维护用户的基本信息,所在科室以及各个系统所具体的权限。 科室信息:维护医院的科室信息。 数据字典:标准字典信息的维护。 药品/诊疗目录维护:维护药品和诊疗目录的基本信息。
31 2
|
2天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
14天前
|
消息中间件 安全 Java
解密 Java 后台架构设计之道
【4月更文挑战第5天】本文探讨了Java后台架构设计的最佳实践,包括分层架构、微服务、异步处理与消息队列、RESTful API设计、数据库优化、安全控制、容错高可用、配置管理、CI/CD和监控日志。强调了使用微服务、Spring Boot/Spring Cloud、异步消息队列、RESTful API、安全框架Spring Security等技术的重要性,以及监控和自动化部署在确保系统稳定性和效率上的关键角色。通过这些实践,开发者能构建高效、稳定且可扩展的后台系统。
|
30天前
|
分布式计算 安全 Java
Java的三大体系架构:深入剖析Java的三大体系架构,包括Java SE、Java ME和Java EE等
Java的三大体系架构:深入剖析Java的三大体系架构,包括Java SE、Java ME和Java EE等
34 1
|
1月前
|
设计模式 缓存 Java
Java新时代:微服务架构下的性能优化实践
【2月更文挑战第12天】 在当今快速发展的软件工程领域,微服务架构因其灵活性和可扩展性而成为主流。随着应用程序变得日益复杂,性能优化成为了开发者不可回避的挑战。本文将探讨在Java环境下,利用微服务架构进行性能优化的策略和实践。我们将从微服务的基本概念出发,深入分析如何通过设计模式、数据库优化、缓存机制以及并发处理等手段,有效提升Java应用的性能。此外,本文还将分享一些实际案例,以帮助读者更好地理解和应用这些优化技术。