栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

设计模式之门面模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

设计模式之门面模式

最近有点“堕落”,公众号更新频率太低,已经被好几个朋友吐槽了,节奏要找回来。

后续会开启一个新的议题:Java 中的日志。这里借用《每天学习一点点之关于 Maven 的那些事儿(一)》中的一段话:

在越来越卷的 Java 行业,动不动就是“分布式、高并发、架构设计”,还得让你从 JVM 的源码来分析下 synchornized,但很少会有人提到 Maven,其实相比那些花里胡哨的,Maven 才是真正与日常开发息息相关的“基本功”。

跟 Maven 一样,我个人也觉得日志也是 Java 开发过程中容易被忽略但又非常重要的一个基本功。

在《阿里巴巴 Java 开发手册》中的“日志规约”章节第一条就提到:

【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

里面提到了“门面模式”,也就是本文的议题。

门面设计模式也可以称为外观设计模式,在《大话设计模式》中是这么描述的:

为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

其实门面模式在平时开发中使用的还是挺多的,在过年的一年中大部分时间都在参与系统重构,很多设计讨论都与门面模式有关。这里举几个实际开发过程中的场景。

返回数据问题

现在我们需要给前端返回订单详情数据,订单数据包含订单自己本身的信息(如创建时间、创建来源,节点状态等),还有用户信息(如用户姓名、电话、地址等),还有订单处理人信息(如处理人 ID、姓名、所在机构、角色、权限等)。正常肯定订单信息要从订单服务中获取,用户信息要从用户服务中获取,订单处理人信息肯定要从内部的员工服务中获取(这里的服务不一定是微服务,至少是不同的 Service)。

那这时候怎么设计呢,难道让前端调用后端三次,分别获取订单、用户、处理人的信息吗。肯定也不太合适,所以这时候后端肯定会提供一个“订单详情”接口,一次调用就把订单、用户、处理人的数据返回给前端了。其实这里的“订单详情”接口就相当于是一个“门面”接口,这样既减少了对外暴露的接口数量,也减少了网络通信的次数。

问题到这里就结束了吗,也不一定。因为可能有的前端页面的“订单详情”需要用户、处理人的信息,但是有的只要一个订单的基本信息就行了。我现在只需要一个订单的基本信息,但是你却把用户、处理人的信息都给我了,这样传输的数据增加,而且服务底层可能还会多次调用数据库或者多次 RPC,反而影响了性能。这时候可能需要将两个接口区分开来,前端按需调用,但这样会出现“冗余”接口。其实如果用户、处理人的信息数据量不大,且获取非常轻量级,其实多点冗余数据问题也不大(当然之前也见过那种一个接口返回几百个字段、多层级的 JSON 数据)。没有绝对完美的,选择一个最适合的方案就行。

之前听组内来自订单中台的大佬说过,他们之前做过一套这样的流程,就是对外的查询接口,调用方可以自己选择需要哪些数据,底层服务会根据调用方所需的数据按需去 RPC、查数据库对数据结果集进行组装。这也是一种解决思路,同样的,这里的查询接口,也是一个“门面”接口。

代码分层问题

目前系统层次划分是这样的:

WEB->Dubbo Service->Local Service->Mapper

其实这里面也有很多分层细节值得探讨。

    Mapper 与数据库表是一一对应吗?

    先不谈连表的问题。就举我们系统中的实际问题,现在的场景是关于订单有两张表:订单表,订单扩展表。如果严格一表一 Mapper,也就是说 Mapper 提供了最基本的单表操作。那么必然会有一个本地的订单 Service 去同时操作订单 Mapper 和订单扩展 Mapper,在这里这个本地的订单 Servicie 其实就是“门面”。

    Dubbo Service 和 Local Service

    以上文的查询数据详情为例。Local Service 会分别去获取订单信息、用户信息和操作人信息,而 Dubbo Service 则负责将从 Local Service 获取到的数据进行组装,统一对外提供,此时这里的 Dubbo Service 就是一个门面接口。

事务问题

以最近做的支付为例,用户在 APP 端点击“去支付”后,系统会做两个操作:

    交易单状态扭转为“支付成功”;对应订单状态流转为“已支付”;

这里交易单和订单的状态扭转需要保证事务一致性,所以后端服务只会对 APP 提供一个“去支付”接口,由后端服务进行事务保证,而不需要前端进行多次调用,这里的“去支付”接口也是一个“门面”接口。

门面模式在 Tomcat 中的应用

我们都知道 Tomcat 是基于 Servlet 规范实现的。根据 Servlet 规范,每个 Request 请求,都会创建一个 javax.servlet.ServletRequest 和 javax.servlet.ServletResponse。然后在调用 javax.servlet.Servlet#service 方法的时候会把它们作为参数传递进去。

简单来说,Web 服务器就做这么几件事:

    接收客户端的请求,解析数据业务处理数据响应数据给客户端。

所以也有这么一个说法:Web 容器 = HTTP 服务器 + Servlet 容器。这里就分别对应了 Connector(Coyote) 和 Container(Catalina),而 Catalina 就是标准的 Servlet 实现,Coyote 会将 Socket 输入转换为 Request,交由 Catalina 进行处理,处理完成后,Catalina 通过 Coyote 提供的 Response 将结果输出。

但这里要注意的是,Coyote 其实与 Servlet 没有直接关系,所以 Coyote 中的 org.apache.coyote.Request 和 org.apache.coyote.Response 需要适配成 Catalina 所需的 javax.servlet.ServletRequest 和 javax.servlet.ServletReponse。所以这里就有了一个适配器 CoyoteAdapter。

有这么一个转换关系:

org.apache.coyote.Request->org.apache.catalina.connector.Request->org.apache.catalina.connector.RequestFacade(继承自 javax.servlet.ServletRequest)

其实 org.apache.catalina.connector.Request 本身也是继承自 javax.servlet.ServletRequest ,而且说实话 org.apache.catalina.connector.RequestFacade 也没干啥:

public class RequestFacade implements HttpServletRequest {
    protected Request request = null;
    protected static final StringManager sm = StringManager.getManager("org.apache.catalina.connector");

    public RequestFacade(Request request) {
        this.request = request;
    }

    public void clear() {
        this.request = null;
    }

    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    public Object getAttribute(String name) {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return this.request.getAttribute(name);
        }
    }

    public Enumeration getAttributeNames() {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return Globals.IS_SECURITY_ENABLED ? (Enumeration)AccessController.doPrivileged(new RequestFacade.GetAttributePrivilegedAction()) : this.request.getAttributeNames();
        }
    }
  ...
  ...
}

底层实际还是调用的 org.apache.catalina.connector.Request。

那么为啥还要用 org.apache.catalina.connector.RequestFacade 在中间加一层呢。可以这么设想一下,把 Tomcat 比作我们的业务系统,有两个团队在开发,一个团队开发 Coyote,一个团队开发 Catalina,Coyote 要给 Catalina 传请求、响应参数,开发 Coyote 的团队需要去了解 Servlet 规范吗,是没必要的,开发 Coyote 的团队在处理请求响应的时候肯定会有自己的抽象,完全不 Care Servlet ,也就是说 Coyote 中的请求和响应包含了很多非 Servlet 相关的方法。两个团队在进行数据交互的时候,对于 Catalina 团队来说,肯定是希望我这边请求、响应要啥样的,你就给我啥样的,你自己那边其它的一堆属性我不需要,也不想看见;同样地,对于 Coyote 团队来说,自己的底层实现也不想对外暴露太多,这时候 RequestFacade 就出现了。Coyote 适配一个 Catalina 所需要的请求对象即可。

其实甚至更“夸张”点说,我们平时封装的各种 Utils 也是门面模式的应用,比如 DateUtils 就是 Calendar 和 SimpleDateFormat 对外的门面,JsonUtils 就是 ObjectMapper 对外的门面。

总结

本文举了很多平时开发中与门面模式相关的示例,同时也以 Tomcat 为例分析了门面模式在开源框架中的应用。其实门面模式很简单,应用也很广泛。本质就是一种封装,对调用方屏蔽底层细节,提供简单合适的接口;在代码或者系统分层结构中,可以通过门面简化各层次之间的调用。

References

《大话设计模式》https://baike.baidu.com/item/%E8%BF%AA%E7%B1%B3%E7%89%B9%E6%B3%95%E5%88%99/2107000《阿里巴巴 Java 开发手册》《深入剖析 Tomcat》《Tomcat 架构解析》

欢迎关注公众号
​​​​​​

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/727389.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号