Hybris平台Web架构模式演变:前后端分离

HRUDemetria 7年前
   <p>“前后端分离”显然已不是什么新鲜的话题,表面上看是一场架构模式的变革,但实质上是为了解决以往传统的服务端MVC设计模式的一些诟病和痛点。前后端分离带来的全新的前后端协作方式能够让专业的人做专业的事,无论前端后端都能更专注在自己擅长的方面。那么如何基于一个成熟的Hybris平台进行前后端分离?接下来,我们将会逐一剖析这个演变过程。</p>    <h2>Hybris平台Web层现状</h2>    <p>众所周知,Hybris平台是一套成熟的电商解决方案,当然也包括Web层的定制化。由于Hybris平台项目至今已经经历过许多版本,对电商的核心流程进行了不同程度上的重组与优化。对于Web层,相对于核心流程来说,升级的速度和频率则显得稍微缓慢。慢慢的我们会发现,在当前前端技术突飞猛进的发展趋势下,Hybris平台Web层的技术体系会变的相对陈旧,比如:缺少了动静分离。因此,在这个技术体系下进行Web层的二次研发,往往还面临着前后端耦合依赖的局面,导致项目进度缓慢。</p>    <h2>Hybris平台Web层架构的不足</h2>    <p>Hybris平台Web层基于传统的服务端MVC(Model-View-Controller)设计模式,传统的服务端MVC架构在为我们提供优势的同时也会带来以下不足:</p>    <ol>     <li>对于简单的页面,严格遵循MVC,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率</li>     <li>视图与控制器之间过于紧密的连接。视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。</li>     <li>View的渲染通过服务端完成,最终呈现给浏览器的是带有Model的视图页面,性能无法得到很好的优化</li>     <li>控制器会变得复杂,很多人会在Controller(Spring),Action(Struts)中写业务代码已经变得很常见,所有的操作都在控制器中,导致业务与控制器相耦合</li>     <li>对象间接地通过控制器耦合在一起,一个对象在控制器中查询获得,然后复制给另外一个对象,这两个对象就耦合在一起</li>     <li>View通过服务端完成后,视图页面包含CSS, JS等资源,这些资源需要重新请求(虽然可能已经进行了缓存)</li>    </ol>    <h2>Hybris平台前后端协作的痛点</h2>    <p>由于Hybris平台基于传统的服务端MVC(Model-View-Controller),在这个模式下,前后端协作模式一般采用以下两种方式:</p>    <ol>     <li>前端直接在服务端View中编写模版,这样做的问题在于,编写的过程中强依赖服务端环境。在服务端没有完成的情况下,前端无法进行完整测试</li>     <li>前端先编写静态原型,完成后,后端在View中套用静态原型。这样做的问题在于,服务端需要对前端代码进行浏览,以免出错</li>    </ol>    <p>这两种协作方式都存在问题。</p>    <p>在模式一下,前端必须掌握一定的服务端JSP技术,并且还需要对Hybris平台内部View的划分机制有一定的了解。因此,学习成本是我们第一要面对的问题。此外,视图的测试依赖服务端环境,导致测试滞后。</p>    <p>在模式二下,首先带来的是时间、成本的消耗,静态原型对于用户来说不是最终的产品,只是开发过程中的一个过渡品。其二,将静态原型转换到服务端View的过程中,后端开发人员需要了解前端的设计,比如:如何找到正确的切分点将一个完整的静态页面切分成若干小的片断,并应用于服务端。在比如:如何正确地使用前端的第三方库?这些问题,不同的前端技术有着不同的做法,因此当后端开发人员在套用静态原型的过程中,无形当中增加了了解,学习前端技术的成本。其三,在静态原型没有完成情况下,如果服务端开发工作已经完毕,那么前后端集成工作则处于等待状态,造成集成缓慢。</p>    <p>如何解决这些不足,并消除痛点呢? 这就是我们近期在使用传统技术经过搭建了几十个Hybris电商运营网站之后进行的一次大胆的前后端分离的尝试,取得了很好的效果。下面,将为读者分享Hybris平台Web架构模式演变及并行化实践。</p>    <h2>Hybris平台Web前后端分离</h2>    <h3>Web架构</h3>    <p><img src="https://simg.open-open.com/show/4352652678024ff92011ddc53d302bb0.png"></p>    <p>上图可见,我们将View、Controller从传统的服务端MVC架构中迁移到客户端。客户端负责视图的渲染,交互的控制。数据的获取通过Restful API接口使用JSON格式交互。而后端只需要负责业务逻辑,数据的存储,数据模型的定义,并为前端提供JSON格式的数据。</p>    <p>这样改变之后,页面的渲染完全从服务端分离出来,并且渲染之后的后续交互,数据都交由客户端代码完成。在这样的架构模式下,前端代码仓库与后端隔离,前端独立负责静态资源,View模板的维护和发布。</p>    <h3>代码组织方式</h3>    <p><img src="https://simg.open-open.com/show/2babe5db5708255c42531ab03c8f35c2.png"></p>    <p>前后端未分离:传统的服务端MVC架构下,前后端代码放置在同一个代码仓库中,前端开发过程中需要导入整个代码仓库,并且很难独立部署与运行。</p>    <p>前后端分离:前后端代码库分离,前端代码可以进行数据的本地化Mock,因此前端可独立开发和测试,以及部署。而后端 代码中除了功能实现外,还有着详细的测试用例,以保证API的可用性,降低集成风险。</p>    <h3>带来的挑战</h3>    <p>新的Web架构在给前端带来更多的便利性的同时,也同样带来了不小的挑战。比如,如何继续遵循Hybris平台后端开发最佳实践,如何统一进行JSON数据转换等等一系列的问题,在我们开发过程中一一浮出水面。有些是在做出这种架构选择时就预见到的,有些是在具体实施中遇到的。</p>    <p>1.前端技术选型</p>    <p>既然客户端负责View,我们就需要选择一种适合的前端框架来满足要求,从而丢弃传统的Hybris平台前端框架JQuery. 在进行斟酌后,对于前端技术采用如下:</p>    <p>ECMAScript + React + Node + NPM+ Typescript</p>    <p>其中React最为核心,它不但提供了虚拟DOM机制,并且组件化的开发思想使的页面结构化合理。前端开发可以完全关注到组件的开发中,进行模板的编写、数据的绑定、事件的处理。开发过程中不会受到后端的影响,顺利完成本地化测试。</p>    <p>2.遵循Hybris平台后端开发最佳实践</p>    <p>Hybris平台对于每一个页面请求,都有与之对应的控制器,并且平台的Web层基于Spring MVC框架,利用这些特点,我们决定将每一个OOTB 控制器包装成为一个完成数据交互的Endpoint,从而为前端提供Rest风格的API接口。与此同时,在Hybris平台内部,仍然采用DTO(Data Transfer Object )作为API服务层与Façade层之间的数据传输对象,平台内部的数据转换过程不需要发生任何改变,仍然采用Converter/ Populator机制。</p>    <p>3.JSON数据转换</p>    <p>正如上文提及,每一个页面请求,都有与之对应的控制器。比如:购物车页面对应CartPageController,产品分类页面对应CategoryPageController,当浏览器根据不同的页面完成PageLoad后,将会触发控制器中默认的Get方法中,通过该方法为视图提供所需要的数据。由于在Rest API层面上,我们仍然采用DTO作为数据载体,可见,在这些默认的Rest Get方法中,将会侵入一些代码片断用来完成DTO到JSON的转换。显而易见,如果在每一个默认的Rest Get方法中都加入转换代码不是一个很好的处理方式,会造成代码的过渡重复,产生坏味道。那么,我们如何从原始的代码中将数据转换的过程分离出来呢?我们采用了BeforeViewHandler拦截器。</p>    <p>优点:</p>    <ul>     <li>拦截器可以自动拦截到需要进行数据转换的Rest Get 请求</li>     <li>数据转换代码与控制器解偶,通过拦截器完成</li>     <li>易于配置管理</li>    </ul>    <p>4.组件中的JSON处理</p>    <p>对于可以重复使用的页面片断,Hybris平台采用组件的方式进行处理。比如:Global Header, Global Footer. 在前后端分离的架构下,客户端同样负责View的渲染,那么Hybris 的组件又该如何为前端提供所需要的数据呢?Hybris允许为组件配置对应的控制器,当一个请求导向至组件时,对应的控制器将会自动触发。但是由于Hybris对于组件控制器的管理与常规有所不同,在受置于其的约束下,我们无法使用BeforeViewHandler拦截器来处理,因此,采用JSON-taglib为前端View提供JSON数据则成为一种可选方案。</p>    <h2>提供安全机制</h2>    <p>前后端分离的Web架构中,如何解决交互过程中产生的安全性风险是需要考虑的另一个问题。</p>    <p>1.基于Hybris平台自身的特性,通过Spring Form 提交的表单,自身已经加入CSRF Token 校验机制。那么如何在HTML Form提交的过程中避免CSRF攻击呢?</p>    <p>解决方式:</p>    <ul>     <li>利用Hybris OOTB 服务生成CSRF Token, 并将 Token返回前端</li>     <li>在每一次提交过程中,Token会作为数据的一部分提交给后端</li>     <li>利用Hybris OOTB CSRF校验机制进行Token的验证</li>    </ul>    <p>2.敏感数据的加密处理</p>    <p>在数据传输的过程中,我们需要对敏感数据进行必要的加密处理,避免明文数据的传输,减少非正常请求的攻击。</p>    <p>解决方式:</p>    <ul>     <li>Hybris通过RSA生成公钥,私钥(公钥/私钥是基于1024+ Base64S 加密),并将公钥返回前端</li>     <li>前端使用公钥以及RSA client lib进行加密,并把加密后的数据传递给Hybris。此时在网络传输过程中,使用的是加密后的数据</li>     <li>Hybris 使用私钥进行解密后,再使用必要的敏感数据</li>    </ul>    <p>3.Restful API的安全机制</p>    <p>对于Restful API的调用,采用授权认证的安全机制来约束匿名与非匿名请求。比如,对于下订单流程中所暴露的API,必须是登录成功后的用户才能访问,限制匿名用户请求。</p>    <p>解决方式:</p>    <ul>     <li>采用注解方式,显示地声明一个Rest API为Require Hard LogIn</li>    </ul>    <h2>性能考虑</h2>    <p>在前后端分离的架构模式下,前端有且仅有静态内容。View视图的渲染脱离服务端,不需要任何服务端技术进行动态化组装,因此对于前端资源可以考虑CDN加速,缓存机制,从而提高性能。</p>    <p>由于前端内容是完全的静态内容,在初次获取以后的大部分时间内,浏览器使用的就是本地缓存,也就是说,服务器的压力主要来自于承载数据的Restful API调用。因此,合理的对象创建,以及业务逻辑的优化能够帮助我们减少性能的开销。比如:</p>    <p>1.减少DTO对象的创建</p>    <ul>     <li>对于一个页面所需要的数据,尽量一次性提供完毕,减少DTO的多次创建,使DTO到JSON的转换只发生一次,从而减少数据转换带来的性能开销</li>    </ul>    <p>2.拦截器最小化配置</p>    <ul>     <li>对于BeforeViewHandler,删除不需要监听的请求,从而减少BeforeViewHandler内部流程,提高代码性能</li>    </ul>    <p>最后,对于电商网站来说,图片资源的管理与使用同样是性能的一个考核指标。在这里,我们对于图片资源的规划采用的方式是:将图片实体独立出Hybris 电商文件系统。即Hybris电商文件系统不存储任何图片实体,通过OOTB Media对象保存产品或者内容与图片的映射关系。真正的图片实体则被保存在其他第三方系统,例如:Scene7,Amazon S3。由于Hybris 只保存映射关系,简单来说就是资源的URL,并通过Restful API将URL返回给前端。那么当请求访问一个具体的图片时,前端可采用懒加载的机制,根据需要才将图片URL赋予SRC属性,从而提高前端性能,减轻服务端负担,提高页面的加载速度。此外,缓存的合理使用同样也是提高性能的一种手段。</p>    <h2>前后端调用流程</h2>    <p><img src="https://simg.open-open.com/show/66558a03467754196411fa3a0be250f0.png"></p>    <p>如上图所示(Promotion detail为例),一套完整的渲染流程包含以上步骤,其中不乏一些技巧,比如:前端如何利用Hybris Page Type属性来完成JS/CSS文件的查找,从而避免不相关JS/CSS文件的加载。关键伪代码参考如下:</p>    <ul>     <li>利用Hybris OOTB Page type属性动态加载对应JS/CSS文件</li>    </ul>    <pre>  If “empty pageType”  <script type="text/javascript" src="XXXXXXXXXX/js/static.bundle.js?v={assetVersion}"></script>  <link href=" XXXXXXXXXX /css/static.css?v={assetVersion}" rel="stylesheet">  Else  <script type="text/javascript" src=" XXXXXXXXXX /js/pageType.bundle.js?v={assetVersion}"></script>  <link href=" XXXXXXXXXX /css/pageType.css?v={assetVersion}" rel="stylesheet"></pre>    <ul>     <li>统一的export function处理JSON数据以及Mock数据的切换,其他组件只需import即可</li>    </ul>    <p><img src="https://simg.open-open.com/show/1bada8d12b22ebc331eca0b95f42f1d8.png"></p>    <ul>     <li>BeforeViewHandler拦截器定义与配置</li>    </ul>    <p><img src="https://simg.open-open.com/show/9794fd8faa07473d521c08be8e0894b5.png"></p>    <p><img src="https://simg.open-open.com/show/1f17dea997f67db7f04d20e8fee0723b.png"></p>    <ul>     <li>JSON数据转换</li>    </ul>    <h3>结束语</h3>    <p>前后端分离的Web架构使得前后端职责更加明确。清晰的分工,可以让开发并行,减少相互依赖,提高开发效率。View的渲染来自于客户端,性能上得到进一步的提升。部署相对独立,很好地应对了复杂多变的前端需求。同时,前后端分离后,应用代码不再是前后端耦合,只有在运行期才会有调用依赖关系,易于管理与维护。最后,基于Hybris平台的前后端分离的Web架构模式同样易于向SPA Web应用转型,带来更快,更好的用户体验。</p>    <p>作者:杨智,现就职于奥博杰天软件有限公司,担任多个电子商务项目的解决方案架构师。曾担任未来国际软件股份有限公司多个项目的技术负责人,负责政府网站以及政务平台设计,研发。参与国家林业局多个系统集成项目。 关注电子商务应用,微服务架构以及DevOps。</p>    <p> </p>    <p>来自:http://blog.csdn.net/dev_csdn/article/details/79415323</p>    <p> </p>