# 似水流年:企业管理系统的前世今生

站在 2018 年 7 月这样一个时间点上,工程师们在谈起 Web 应用开发时已经很少再会提起如 JSP(JavaServer Pages)ASP.NET(Active Server Pages)等这些传统的服务端 Web 应用开发方案,与此同时像 jQueryBootstrap 等这些直接处理样式及交互的工具库也逐渐淡出了历史舞台。随着 React、Angular 及 Vue 这些 MVVM 框架的流行,使用组件去组合页面逐渐代替了 Web 应用过去以页面为单位的开发方式,各种各样的组件库也应运而生成为了支撑现代 Web 应用开发的中坚力量。

# 历史

时间退回到 2011 年,那是千团大战(团购)的元年,也是移动互联网开始真正进入人们日常生活的开始。从那之后,不论是 O2O、垂直电商,还是互联网金融等风靡全国的商业模式,各家创业公司都进入了一个 App 就可以创业的时代,同时也催生了技术领域学习 iOS 和 Android 开发的热潮,会用 Xcode 就可以月薪过万的 iOS 工程师成为了那个时代最好的代名词。但后来随着各大互联网巨头之间的合纵连横,能够不依赖投资机构独立运营的创业公司越来越少,客户端开发的需求也开始大量萎缩。在大起大落的移动端开发之外,其实这几年 Web 端开发的日子也并不好过。传统公司的官网项目连外包公司都喂不饱,除去个别大厂外又有多少公司一定要开发一个 Web 端的应用才能做生意呢?

# 传统企业管理系统(ERP)

抛开一直在走下坡路的面向用户的客户端应用不谈,长期占据 Web 应用开发需求主流的各种企业管理系统却从来都没有沉寂过,只是因为这些内部应用的使用者通常来讲不过是内部员工而已,人们对这些管理系统的期待与忍耐度都已经被培养到了一种近乎变态的地步。

但事实上对于企业管理系统开发方式的改良从未停止过。

随着互联网在办公领域的深度普及,几乎所有的传统企业都在将老旧的 CS(client/server)架构的内部系统迁移至更方便灵活的 BS(browser/server)架构。但令人遗憾的是,很多这样的升级只是将原来 C# 写的代码转换成了 JavaScript 而已,相较于现在各种交互流畅、动画炫酷的面向终端用户的应用,大部分企业管理系统的设计与交互依然停留在 Win97 时代。

# 雕版印刷术 vs. 活字印刷术

发明于唐朝的雕版印刷术一直到明清时期都还在被广泛使用,而更为人所称道的宋代毕昇所发明的活字印刷术却一直都没有成为古代中国主流的印刷技术。这其中最主要的原因一方面是古代所要印刷的书大部分都是比较固定的(如四书五经等),雕版印刷的可复用性并不比活字印刷低多少。另一方面是制造和使用一块雕版还是几千个活字模在成本与复杂度上也是不可同日而语的。换句话说,如果现在就是要印 1000 册《论语》,你是会选择做一块雕版印 1000 次,还是先做几千个活字模并把它们组装好再印上 1000 次呢?答案不言而喻。而且别忘了印完这 1000 册《论语》后,接下来要印的可能是《大学》也可能是《中庸》,上次积累的这几千个活字模并不能够完全覆盖到其他的书,额外的拆卸与组装成本也是一笔不小的开销。再者而言,组装和拆卸活字模要求所有的印刷工人都必须识字,相反雕版印刷对于印刷工人来说几乎是零门槛的,任何人在接受了一定的训练后都可以把这个活儿干好。又由于一个个活字模本身是独立制造的,将几千个活字模组装好后印出的书也难免会出现字与字之间风格或样式不统一的问题,而统一制造的雕版就不存在这个缺陷。在古代,人们所达成的共识也是雕版印刷书的质量和美观程度都要远远胜过活字印刷。

聪明的你可能已经猜到了,上面我们提到的活字印刷术就像是现代前端开发中的组件库,活字模就是一个个独立的组件。如果说我们要解决的是整个 Web 应用领域的终极问题,我们自然需要应用活字印刷的思想在最细的粒度上去进行抽象,以达到边做新项目边充实组件库的目的。但如果我们要解决的就是企业管理系统这样一个具体的问题,应用雕版印刷的思想在更粗的粒度上进行抽象,完成任务所需要的时间和人工成本可能只是原来的百分之一甚至千分之一。

# 机遇

积重难返的企业管理系统同样是一片充满了变革机遇的绿洲,但有机遇的地方就会有挑战。

企业管理系统作为一个已经存在了几十年的传统行业,一直以来都没有人能够总结出一套较为通用的解决方案,这也从侧面说明了解决这一问题要面临着多大的挑战。相较于注重展示的面向终端用户的应用,企业管理系统的核心在于对工作流程的抽象,这一部分根据企业的不同,其复杂度也不尽相同。这导致了抽取不同系统之间的共性变得异常困难,经验或者说知识很难沉淀下来。另一方面,再小的企业管理系统也都是“麻雀虽小,五脏俱全”,通用布局、用户登录、权限管理、菜单路由、消息通知、操作反馈、多语言支持等等这些模块一个都不能少。这样分散且琐碎的组成形式,让解决企业管理系统开发这个问题不仅需要强大的技术背景支持更需要耐心与细心。

当然,目前在这个领域也有着许多优秀的先行者,我们一起来看几个开源的企业管理系统。

react-admin

ngx-admin

blur-admin

ant-design-pro

# 工程师 vs. 设计师

对于上面提到的这些优秀的开源项目,许多开发者所抱的态度很多时候都是又爱又恨。在网络讨论中,一个经常被人拿来讨论的问题是,互联网大厂的工程师和传统公司的工程师之间有什么区别?但其实除了工程师之间存在着不同外,互联网大厂设计师与传统公司设计师之间的区别才是最为巨大的。

在传统公司中绝大部分的设计师都是项目导向的,这导致设计师很少有时间去思考和沉淀所做过的东西。素材库、图标库、设计理念及交互方式等这些本该有确定答案的部分几乎都是缺失的。造成这一结果的除了设计师自身的原因之外,“罪魁祸首”其实是公司内部的管理机制,即外行领导内行。许多非软件开发行业出身的项目经理经常简单粗暴地认为两个系统之间长得像就是偷懒,一个系统用了栅格式布局,另一个系统就非得是列表式,这不仅造成了巨大的资源浪费,更带坏了许多设计师的设计思想,将设计系统变成了设计广告,力求标新立异,推陈出新。

说回企业管理系统,许多认可这些开源项目的开发者在新项目开始时遇到的第一个问题就是,设计师已经给出了第一版的设计稿,如果我现在要拿上面这样大而全的解决方案去实现的话要怎么才能跟设计师交代呢?如果要深入内部去把这些开源项目不符合设计要求的部分都改成自己这边的实现,是否还不如另起炉灶再做一个?

是的,在非一线的互联网公司中,困扰程序员的关键往往不是技术,而是不同部门之间因为各自背景、诉求、视野等不同而产生的不协调。大而全的方案看起来很美,但实践起来却困难重重。

# 组合式开发

为了解决大而全的方案在实践中不够灵活的问题,我们是不是可以将其中包含的各个模块解耦后,独立发布出来供开发者们按需取用呢?让我们先来看一段理想中完整的企业管理系统应用架构部分的伪代码:

const App = props => (
  <Provider>                                        // react-redux bind
    <ConnectedRouter>                               // react-router-redux bind
      <MultiIntlProvider>                           // intl support
        <AclRouter>                                 // router with access control list
          <Route path="/login">                     // route that doesn't need authentication
            <NormalLayout>                          // layout component
              <View />                              // page content (view component)
            </NormalLayout>
          <Route path="/login">
          ...                                       // more routes that don't need authentication
          <Route path="/analysis">                  // route that needs authentication
            <LoginChecker>                          // hoc for user login check
              <BasicLayout>                         // layout component
                <SiderMenu />                       // sider menu
                <Content>
                  <PageHeader />                    // page header
                  <View />                          // page content (view component)
                  <PageFooter />                    // page footer
                </Content>
              </BasicLayout>
            </LoginChecker>
          </Route>
          ...                                       // more routes that need authentication
          <Route render={() => <div>404</div>} />   // 404 page
        </AclRouter>
      </MultiIntlProvider>
    </ConnectedRouter>
  </Provider>
);

在上面的这段伪代码中,我们抽象出了多语言支持、基于路由的权限管理、登录鉴权、基础布局、侧边栏菜单等多个独立模块,可以根据需求添加或删除任意一个模块,而且添加或删除任意一个模块都不会对应用的其他部分产生不可接受的副作用。这让我们对接下来要做的事情有了一个大体的认识,但在具体的实践中,如 props 如何传递、模块之间如何共享数据、如何灵活地让用户自定义某些特殊逻辑等都仍然面临着巨大的挑战。我们需要时刻注意,在处理一个具体问题时哪些部分应当放在某个独立模块内部去处理,哪些部分应当暴露出接口供使用者自定义,模块与模块之间如何做到零耦合以至于使用者可以随意插拔任意一个模块去适应当前项目的需要。

# 小结

在本节中我们从企业管理系统的历史讲起,一起探讨了为什么活字印刷式的组件库并不能够很好地解决企业管理系统这样一个需要更高层抽象的问题,并由此引出了组合式开发的概念,即将不同的核心模块分别抽象再根据项目需要最终组合在一起的开发方式。

在下一节中我们将从最基础的项目脚手架讲起,一起来搭建一个简单却功能齐全的项目脚手架。

如果你想参与到文章中内容的讨论,欢迎在下面的评论区留言,期待与大家的交流。

阅读全文