java架构-一些设计上的基本常识
最近给团队新人讲了一些设计上的常识,可能会对其它的新人也有些帮助,
把暂时想到的几条,先记在这里。
1、API与SPI分离
框架或组件通常有两类客户,一个是使用者,一个是扩展者。
API(Application Programming Interface)是给使用者用的, 而SPI(Service Provide Interface)是给扩展者用的。 在设计时,尽量把它们隔离开,而不要混在一起, 也就是说,使用者是看不到扩展者写的实现的。
比如:
一个Web框架,它有一个API接口叫Action, 里面有个execute()方法,是给使用者用来写业务逻辑的。然后,Web框架有一个SPI接口给扩展者控制输出方式。
velocity模板输出还是用json输出等, 如果这个Web框架使用一个都继承Action的VelocityAction和一个JsonAction做为扩展方式, 要用velocity模板输出的就继承VelocityAction,要用json输出的就继承JsonAction, 这就是API和SPI没有分离的反面例子。
SPI接口混在了API接口中,合理的方式是,有一个单独的Renderer接口,有VelocityRenderer和JsonRenderer实现, Web框架将Action的输出转交给Renderer接口做渲染输出。
反正例子:

正确例子:

2、服务域/实体域/会话域分离
任何框架或组件,总会有核心领域模型,比如:
实体域:像Spring的Bean,Struts的Action,Dubbo的Service,Napoli的Queue等等 。这个核心领域模型及其组成部分称为实体域,它代表着我们要操作的目标本身, 实体域通常是线程安全的,不管是通过不变类,同步状态,或复制的方式。
服务域:也就是行为域,它是组件的功能集,同时也负责实体域和会话域的生命周期管理。比如Spring的ApplicationContext,Dubbo的ServiceManager等, 服务域的对象通常会比较重,而且是线程安全的,并以单一实例服务于所有调用。
会话域:就是一次交互过程, 会话中重要的概念是上下文,什么是上下文? 比如我们说:“老地方见”,这里的“老地方”就是上下文信息, 为什么说“老地方”对方会知道,因为我们前面定义了“老地方”的具体内容, 所以说,上下文通常持有交互过程中的状态变量等, 会话对象通常较轻,每次请求都重新创建实例,请求结束后销毁。
简而言之:
把元信息交由实体域持有, 把一次请求中的临时状态由会话域持有, 由服务域贯穿整个过程。
实例一

实例二

两 种 例 子
3、在重要的过程上设置拦截接口

1.如果你要写个远程调用框架,那远程调用的过程应该有一个统一的拦截接口;2.如果你要写一个ORM框架,那至少SQL的执行过程,Mapping过程要有拦截接口;3.如果你要写一个Web框架,那请求的执行过程应该要有拦截接口;复制代码
等等,就可以自行完成,而不用侵入框架内部。拦截接口,通常是把过程本身用一个对象封装起来,传给拦截器链。
比如:远程调用主过程为invoke(),那拦截器接口通常为invoke(Invocation),Invocation对象封装了本来要执行过程的上下文,并且Invocation里有一个invoke()方法, 由拦截器决定什么时候执行。同时,Invocation也代表拦截器行为本身, 这样上一拦截器的Invocation其实是包装的下一拦截器的过程, 直到最后一个拦截器的Invocation是包装的最终的invoke()过程, 同理,SQL主过程为execute(),那拦截器接口通常为execute(Execution),原理一样, 当然,实现方式可以任意,上面只是举例。

4、重要的状态的变更发送事件并留出监听接口
这里先要讲一个事件和上面拦截器的区别:
拦截器:是干预过程的,它是过程的一部分,是基于过程行为的。事件:是基于状态数据的,任何行为改变的相同状态,对事件应该是一致的,事件通常是事后通知,是一个Callback接口,方法名通常是过去式的,比如onChanged()。
比如远程调用框架,当网络断开或连上应该发出一个事件,当出现错误也可以考虑发出一个事件, 这样外围应用就有可能观察到框架内部的变化,做相应适应。

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某条数据库记录, 如果你的方法即做了修改状态的操作,又做了查询返回,如果可能,将其拆成写读分离的两个方法。
比如:
User deleteUser(id),删除用户并返回被删除的用户,考虑改为getUser()和void1的deleteUser()。
另外,每个方法都尽量前置断言传入参数的合法性,后置断言返回结果的合法性,并文档化。
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处理。

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!
java架构-一些设计上的基本常识的更多相关文章
- Java架构师线上问题排查,这些命令程序员一定用得到!
Java架构师线上问题排查,这些命令程序员一定用得到! 线上问题排查,以下场景,你遇到过吗? 一.了解机器连接数情况 问题:1.2.3.4的sshd的监听端口是22,如何统计1.2.3.4的sshd服 ...
- Java进阶专题(十七) 系统缓存架构设计 (上)
前言 我们将先从Redis.Nginx+Lua等技术点出发,了解缓存应用的场景.通过使用缓存相关技术,解决高并发的业务场景案例,来深入理解一套成熟的企业级缓存架构如何设计的.本文Redis部分总结 ...
- Java互联网架构-直播互动平台高并发分布式架构应用设计
概述 网页HTML 静态化: 其实大家都知道网页静态化,效率最高,消耗最小的就是纯静态化的 html 页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法, ...
- 十年阿里java架构师的六大设计原则和项目经验
先看一幅图吧: 这幅图清晰地表达了六大设计原则,但仅限于它们叫什么名字而已,它们具体是什么意思呢?下面我将从原文.译文.理解.应用,这四个方面分别进行阐述. 1.单一职责原则(Single Res ...
- Java生鲜电商平台-订单配送模块的架构与设计
Java生鲜电商平台-订单配送模块的架构与设计 生鲜电商系统最终的目的还是用户下单支付购买, 所以订单管理系统是电商系统中最为复杂的系统,其作为中枢决定着整个商城的运转, 本文将对于生鲜类电商平台的订 ...
- Java生鲜电商平台-Java后端生成Token架构与设计详解
Java生鲜电商平台-Java后端生成Token架构与设计详解 目的:Java开源生鲜电商平台-Java后端生成Token目的是为了用于校验客户端,防止重复提交. 技术选型:用开源的JWT架构. 1. ...
- JavaScript 与 Java 是两种完全不同的语言,无论在概念还是设计上。
JavaScript 与 Java 是两种完全不同的语言,无论在概念还是设计上. Java(由 Sun 发明)是更复杂的编程语言. ECMA-262 是 JavaScript 标准的官方名称. Jav ...
- Hadoop 分布式文件系统:架构和设计
引言 Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统.它和现有的分布式文件系统有很多共同点.但同时,它和其他的分布式文件系统 ...
- Hadoop分布式文件系统:架构和设计要点
原文:http://hadoop.apache.org/core/docs/current/hdfs_design.html 一.前提和设计目标 1.硬件错误是常态,而非异常情况, HDFS可能是有成 ...
随机推荐
- Java实现 LeetCode 21 合并两个有序链表
21. 合并两个有序链表 将两个有序链表合并为一个新的有序链表并返回.新链表是通过拼接给定的两个链表的所有节点组成的. 示例: 输入:1->2->4, 1->3->4 输出:1 ...
- 去摆摊吧,落魄的Java程序员
真的,我也打算去摆摊,宣传语我都想好了.沉默王二,一枚有颜值却靠才华苟且的程序员,<Web 全栈开发进阶之路>作者,CSDN 明星博主,周排名第 4,总排名 40,这数据在众多互联网大咖面 ...
- Java学习的一般过程
伴随着科学技术的不断发展,世界开始走向信息化.网络化.大数据化.自然而然,计算机专业变得十分热门.尽管如此,计算机专业人才对社会来说仍然是供不应求,当然,这里指的是高层次技术人才.因此,对于我们这些占 ...
- 【JUC系列】01、之大话并发
学习方法 学习技术的方法都很类似,大部分都有着类似的步骤: 场景 需求 解决方案 应用 原理 并发的目的 充分利用CPU 和 I/O资源 提高效率 并发的维度 分工 同步/协作 互斥 分工 线程池 f ...
- 学而思Java开发岗位面试
去学而思培优面试了. 有四道笔试题,后面会整理做法. 1.给一个文件夹,用递归的方式统计这个目录及其子目录不同文件类型的个数. 如,输出:jpg:几个文件,txt:几个文件... 2.不适用加减乘除, ...
- DockerFile构建镜像和Docker仓库
利用commit理解镜像构成 注意: docker commit 命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现 场等.但是,不要使用 docker commit 定制镜像,定制镜像应该 ...
- @codeforces - 549E@ Sasha Circle
目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定两个点集 M 与 S,求是否存在一个圆能够分割两个点集. 原 ...
- Docker学习 ,超全文档!
我们的口号是:再小的帆也能远航,人生不设限!! 一.学习规划: Docker概述 Docker安装 Docker命令 Docker镜像 镜像命令 容器命令 操作命令 容器数据卷 Doc ...
- Android学习笔记Tab代替ActionBar做的顶部导航
1.先准备5个Fragement作为标签页 package com.lzp.youdaotab; import android.os.Bundle; import android.view.Layou ...
- Zookeeper——分布式一致性协议及Zookeeper Leader选举原理
文章目录 一.引言 二.从ACID到CAP/BASE 三.分布式一致性协议 1. 2PC和3PC 2PC 发起事务请求 事务提交/回滚 3PC canCommit preCommit doCommit ...