案例研究:Lawson 并购产品线的架构集成

阅读数:632 2007 年 7 月 2 日

话题:Java.NET架构语言 & 开发

内容提要

在并购(Mergers and Acquisitions,M&A,兼并与收购)时代,我们常常无法回避的一个问题是:如何在不影响用户体验的前提下完成产品线集成。2006 年 4 月,Lawson 软件公司(Lawson)与 Intentia 国际有限公司(Intentia)合并后,其研发团队就遇到这样的问题:新公司的两个旗舰产品线,即面向以产品为中心行业的 M3(实现产品与资产的“Make”、“Move”和 “Maintain”)、面向服务行业的 S3(实现“Staff”、“Source”和“Serve”的优质管理,让用户满意),如何形成无缝的、一致的用户体验,以提升用户生产力、满意度和信息集成能力。

在本次案例学习中,我们将研究 Lawson 对此问题的解决策略,并深入分析其中一些让人感兴趣的技术细节。

问题分析

合并之前,Lawson 和 Intentia 都提供 ERP 解决方案,但目标用户的行业和区域不同。Lawson 的产品线 S3,主要针对美国服务行业,帮助用户实现对“Staff”、“Source”和“Serve”的 管理;S3 包含的 ERP 功能很多,如企业绩效管理(Enterprise Performance Management)、业务流程管理(Business Process Management)、企业财务管理(Enterprise Financial Management)、供应链管理(Supply Chain Management)和人力资本管理(Human Capital Management)等。而 Intentia 的 M3 主要面向 EMEA(欧洲、中东和非洲)和 APAC(亚洲太平洋地区)的物流与制造行业,帮助客户实现对产品及各项资产的“Make”、“Move”和“Maintain”;其功能模块包括生产运作(Manufacturing Operations)、供应链执行与优化(Supply Chain Execution&Optimization)、客户销售与服务(Customer Sales and Service)、企业资产管理(Enterprise Asset Management)、财务管理与控制(Financial Management&Controlling)以及业务流程管理等。两家公司的合并意图非常明显——实现产品线、目标用户的行业和区域互补。

两家公司的系统在业务有共同点,但技术实现差异很大。Intentia 的 M3 以服务端 Java 业务逻辑层为中心,用户接口(UI)部分多年来的变动很大。最早使用 MicroFocus 公司的 Cobol 与 C++ 混合实现客户端,然后演变到 ASP/JSP 瘦客户端模型,最后实现了基于瘦客户端的 AJAX/JSP 访问(也就是所谓 Workplace Foundation)。Lawson 的 S3 则是一个混用了 Java 和第四代语言(4GL)的解决方案,而且正在逐步向完全 Java 演进。两个公司采用了从 Workplace Foundation 式 Web 门户,到基于 C# 的可移动富客户端在内的大量不同客户端技术。

并购之后,Lawson 需要找到能充分激发这两个旗舰产品线能量的客户端的构建策略。具体来说,该公司希望新的客户端能满足如下要求:

  • 能为 Windows XP 和 Vista 用户提供一致体验(它 98% 的用户都使用 Windows 操作系统)
  • 能为用户提供相同的应用启动和消息接收方法。
  • 实现新应用和新功能插件式添加。
  • 提供丰富的、高生产力的用户体验。
  • 实现代码库统一,保证服务端业务逻辑能为各种客户端共享。
  • 能和 Java 和非 Java 的业务逻辑层交互。
  • 保证用户现有和新开发的客户端能使用同样的服务端实例。

方案概述

为了保证正确选择新的客户端技术,Lawson 决定寻求 Frog Design 欧洲公司(www.frogdesign.com)的帮助。两家公司最后得到的解决策略是:基于 Microsoft 新推出的 WPF(Windows Presentation Foundation)和 WCP(Windows Communication Foundation)——二者都是.NET3.0 框架的组成部分——构建富客户端应用。主要有以下几点考虑:

  • 新产品与 Windows Vista 的发布时间一致,这就可在行业内为 Lawson 塑造领先采用新技术的良好形象。
  • Lawson 98% 的用户都使用 Windows 平台,因此,新应用以.NET 为平台,就是在支持用户投资的增值。
  • WPF 很好实现了应用要素的逻辑分离,如图形设计与其他实现细节的分离。
  • WPF 为窗口、控件、音频、视频、演示文档以及 2D、3D 图形处理等提供了统一基础平台。
  • WPF 提供稳定的、不依赖于屏幕分辨率的 UI 渲染技术。
  • WCF 提供了易扩展的通讯编程模型和框架。
  • WCF 实现了应用逻辑与通讯部分的高度分离。使用 WCF 的客户端无需关心服务的具体位置——跨应用、跨进程以及是否在本地机上都没有关系。
  • WCF 为复合型应用系统和 SOA 提供了申述式模型(declarative model)。
  • WCF 终端在发布后是可配置的。

客户端

由上图可见,新的富客户端(“Lawson 智能客户端”)主要设计为三个层次,另外还包括对用户数据与个人设置的考虑。最上面的是表现层(Presentation Layer),负责用户交互与宿主部件的生命周期管理。它本质上就是一块客户端“画布”(Canvas)。不同类型应用(M3、S3 等)的部件都可安置到画布上,用户使用它们可启动任务和响应各种事件。当然,用户也可以通过给画布换肤改变整个客户端的视觉体验。

逻辑渲染层(Render Logic Layer)由众多负责各自应用内容渲染的插件式引擎构成。而这些内容,可在运行时通过元数据、用户数据和其他预设资源复合得到,从而确保为用户提供一致体验。

数据接口层(Data Access Layer)利用 WPF 实现 UI 组件对业务逻辑和数据组件的绑定。它支持多种能确保 UI 从用户数据、客户端配置和 Session 环境等获取数据的策略,而最为常见的就是使用 WCF 终端,通过 Web Service 建立与服务端组件的连接。

Microsoft SQL Server Compact Edition 用于存储用户数据、用户行为统计数据和用户状态。用户完成漫游配置(roaming profile)后,用户数据将被复制到服务器,从而可实现对任何 Lawson 智能客户端设备漫游的支持。

服务端

如上图所示,Lawson 和 Intentia 这些年来针对不同行业开发了大量服务端业务逻辑和功能。事实上,图示所有服务端组件都早已先于要开发的智能客户端,以各种方式应用到客户的工作中了。为了保护客户在财力和精力上的投资,降低向新技术迁移的难度,继续使用已经存在的服务端组件(包括 Java 和非 Java 的),对 Lawson 来说将是最好的选择。

为了加快新客户端原型的实现,他们利用 Microsoft 提供的 Java 转换工具完成了 M3 移动富客户端使用中 JCA(J2EE Connector Architecture)/CCI(Common Client Interface)连接器向 J# 的转换。J# 客户端连接器使用 WCF 实现与后端 Web Service 的交互。

Web Service 框架可根据业务服务器提供的元数据生成 WSDL(Web Services Definition Language)和 Java 连接器代码。Lawson 开发人员重构了现有 J2EE Workplace Foundation 应用中的服务端组件,从而赋予了基于 AJAX 的 Workplace 瘦客户端和新的 Lawson 智能客户端使用同样业务逻辑和服务的能力。

深入研究之一:通过 Web Service 实现 Java 和.NET 的集成

新的 Lawson 智能客户端充分利用了 WPF 和 WCF 提供的特性。如下图所示,在 C# 中使用 XAML(Extensible Application Markup Language),可实现客户端部件与数据源的动态绑定。而数据源则通过代理对象与对应 Web Service 通讯。代理可使用 Windows SDK 中的元数据处理工具 SvcUtil.exe 生成,它负责 C# 与 Web Service 的通讯,并在二者之间现信息的格式转换。至于对 Web Service 的调用,自然是利用 SOAP(Simple Object Access Protocol)在 HTTP 上实现了。

例 1:智能客户端调用 Web Service

© 2007 Lawson Software, Inc. All rights reserved. This document is for informational purposes only. Lawson Software makes nowarranties, express or implied, in this summary.

例 2:客户端部件与数据源的异步绑定(XAML)

       ObjectType="{x:Type ds:CRS990DataSource}" />

ItemsSource={Binding Source=dataSourceCRS990,



Path=ResultCollection, IsAsync=True, Mode=OneWay} >

<... />

例 3:C# 数据源模拟对 WebService 代理的调用

Public ObservableCollection ResultCollection

{

get

{

InitBrowseCollection data = new InitBrowseCollection();

data.InitBrowseItem = GetCallContext();

try {

CRS990MIClient crs990 =

WSHelper.CreateClient();

InitBrowseResponseItem[] responseCollection = crs990.InitBrowse(

WSHelper.GetLWSCredentials(), data);

return FilterResponse(responseCollection);

}

}

}

例 4:WebService 代理中的方法样例

...

public InitBrowseResponseItem[] InitBrowse(headerType mws,

InitBrowseCollection initBrowse)

{

InitBrowseRequest inValue = new InitBrowseRequest();

inValue.mws = mws;

inValue.InitBrowse = initBrowse;

InitBrowseResponse retVal =

((SmartClient.Widgets.WS.CRS990MI)(this)).InitBrowse(inValue);

return retVal.InitBrowseResponse1;

}

...

M3 服务端应用套件的核心是 WSF(Web Service Framework)。这个用 Java 实现的框架,包含了负责提供 Web Service 创建、测试和热部署的设计工具。这些工具生成的代码,能够解析来自 Web Service 的 XML 请求,并生成包含了请求结果的 XML 返回,从而实现 Web Service 与业务系统 API 的交互。而每个后端 API 的部署位置和实现架构则可能各不相同。比如 M3 系统,其实是一个独立运行的服务端应用程序,需要通过 TCP/Socket 实现对它的访问。

在下图我们可以看到,框架负责生成将业务 API 封装为 Web Service 所需的全部连接信息和配置数据。它会用到很多工具,比如实现 Java/XML 绑定的 Apache XMLBeans、解析和验证 Web Service 请求的 StAX(Streaming API for XML)。另外,它还负责生成 WSDL、Web Service 的 XML 格式描述,以及用于调用业务服务的连接器代码。

Lawson 的 servlet 应用服务器,使用了 IBM WebSphere。而 Web Service 核心引擎,则是 Apache Axis2;在此基础上,Web 服务器还要负责运行时配置、连接池和安全管理等工作。Axis2 引擎性能优异,且全面支持 WS-I Basic Profile 和 WS Security(负责消息加密)等标准。技术人员还通过类加载器的分离——每个 Web Service 的类,都由独立的类加载器负责加载——实现了对各个 Web Service 的隔离运行。

深入研究之二:UI 渲染

Lawson 先前的很多业务系统都使用 XML 实现客户端、服务端通讯。要想保证老客户端能继续使用,后端服务和应用的实现就不能改变。为此,Lawson 想了不少办法。比如已有的瘦客户端(基于 AJAX/JSP),可使用 XSLT(Extensible Stylesheet Language Transformation)将 XML 格式的元数据和运行时数据转换为 HTML。至于无法直接通过 XSLT/HTML 方式处理的数据,则需要祭出 Javascript 了。

而富户端应用程序,实现 UI 时也需要用到这些 XML 数据。因为已有的一些 Java 富客户端应用动态创建 UI,Lawson 新的 C# 智能客户端也沿用了这种模式。

在使用 XML 数据渲染 UI 前,需先用 WYSIWYG 编辑器完成业务系统中的页面面板。面板定义好后,设计工具将输出描述了面板上所有部件信息的元数据。这些元数据也包含了从服务端业务系统获得的部件数据绑定信息,并说明了与这些信息的交互方式。

例:用元数据描述 UI 面板

© 2007 Lawson Software, Inc. All rights reserved. This document is for informational purposes only. Lawson Software makes no warranties, express or implied, in this summary.

元数据描述 UI 面板的代码片段:

<Panel name="MMA001E0" rtype="DETAIL" modDateField="WMLMDT" regDateField="WMRGDT" changedByField="MMCHID">

<PanelHeader>MMS001/E</PanelHeader>

<PanelDescription langId="MM00101"/>

<Objects>

<GroupBox langId="MX_0235" justification="LEFT">

<Position left="1" top="1" width="73" height="1"/>

</GroupBox>

<Caption langId="WIT0115" tab="256">

<Position left="1" top="2" width="14" height="1"/>

</Caption>

<EntryField name="MMITNO" fieldHelp="ITNO" suppressLeadingZero="true" protected="IN41|!IN45" tab="270">

<Constraints maxLength="15" uppercase="UC"/>

<BrowsePosition top="4" left="18"/>

<Position left="15" top="2" width="16" height="1"/>

</EntryField>

<GroupBox langId="MX_0238" justification="LEFT">

<Position left="1" top="4" width="73" height="1"/>

</GroupBox>

<Caption langId="WNA0115" visible="!IN21" tab="1024">

<Position left="1" top="5" width="14" height="1"/>

</Caption>

<EntryField name="MMITDS" fieldHelp="ITDS" suppressLeadingZero="true" visible="!IN21"

protected="IN01|IN21|IN45&!IN41" tab="1038">

<Constraints maxLength="30"/>

<BrowsePosition top="5" left="18"/>

<Position left="15" top="5" width="31" height="1"/>

</EntryField>

<...>

<FunctionKeys value="001011111001000000000000">

<FunctionKey fKey="F3" langId="XF03000"/>

<FunctionKey fKey="F5" langId="XF05000"/>

<FunctionKey fKey="F6" langId="XF06000" visible="!IN41" reverse="!IN41&IN57"/>

<FunctionKey fKey="F7" langId="XF07000" visible="!(!IN45)"/>

<FunctionKey fKey="F8" langId="XF08000" visible="!(!IN45)"/>

<FunctionKey fKey="F9" langId="XF09000" visible="!IN41"/>

<FunctionKey fKey="F12" langId="XF12000"/>

</FunctionKeys>

</Objects>

<RecordFields length="634">

<RecordField name="WWCLIN" type="DECIMAL" pos="18" length="3" refField="CLIN"/>

<RecordField name="WWCPOS" type="DECIMAL" pos="21" length="3" refField="CPOS"/>

<RecordField name="MMITNO" pos="24" length="15" refFile="MITMAS" refField="MMITNO"/>

<...>

</RecordFields>

</Panel>

所有元数据文件都将被发布到 Web 应用服务器上。应用服务器再将将元数据与业务系统的运行时数据合并,最后生成复合的 XML 数据。这些 XML 数据包含了 UI 部件渲染的所需的全部信息,如部件在屏幕上的位置、数据呈现格式(比如小数位数目)和输入约束(如只能输入数字)等。AJAX/JSP 的瘦客户端和新的 Lawson 智能客户端都要用到这类类型的 XML 数据。

复合了元数据和运行时数据的 XML 片段:

<Panel name="MMA001E0">

<Objs>

<FKeys val="001011001001000000000000">

<FKey val="F3">End</FKey>

<FKey val="F5">Refresh</FKey>

<FKey val="F6">Text</FKey>

<FKey val="F9">Field Audit</FKey>

<FKey val="F12">Cancel</FKey>

</FKeys>

<EFld tab="270" name="MMITNO" hlp="ITNO" acc="WD">

<Pos l="15" t="2" w="16" h="1"/>

<Constr maxL="15" type="CHAR" uc="UC"/>

B000007

</EFld>

<EFld tab="1038" name="MMITDS" hlp="ITDS" acc="WE">

<Pos l="15" t="5" w="31" h="1"/>

<Constr maxL="30" type="CHAR"/>

Air Filter, Fleetguard AF1811

</EFld>

<...>

<ChkBox name="MMECMA" hlp="ECMA" tab="4149" acc="WE">

<Pos l="54" t="17" w="3" h="1"/>

0

</ChkBox>

<...>

<CBox name="MMSTCD" hlp="STCD" tab="4366" acc="WE">

<Pos l="15" t="18" w="16" h="1"/>

<CBV val="0">0-No inv account</CBV>

<CBV val="1" sel="true">1-Inv accounting</CBV>

<CBV val="2">2-No, but planned</CBV>

<CBV val="3">3-No, but as func</CBV>

</CBox>

<...>

<GroupBox r="t">

<Pos l="1" t="1" w="73" h="1"/>

Panel Header

</GroupBox>

<GroupBox r="t">

<Pos l="1" t="4" w="73" h="1"/>

Basic Information

</GroupBox>

<...>

</Objs>

<PHead>MMS001/E</PHead>

<PDesc>Item. Open</PDesc>

</Panel>

Lawson 智能客户端从 Web 服务器取得上述 XML 数据后,会使用内置解析组件执行解析,然后根据解析结果动态创建、定位、初始化并施加约束于所有 UI 部件,最后将它们放置到面板上。当然,解析组件在此过程中也会考虑用户和其他 UI 设置的信息。

如下例子实现标签的动态渲染:

private void ReadCaption(XMLNode n) {

string text = n.InnerText;

if (text.Length > 0) {

XMLAttribute a = n.Attributes["tip"];

string name = GetStringAttribute(n, "id");

string tooltip = a != null ? a.Value : null;

bool isFixed = n.Attributes["fixFnt"] != null;

bool isAdditionalInfo = n.Attributes["addInfo"] != null;

bool isEmphasized = n.Attributes["emp"] != null;

bool isColon = n.Attributes["cl"] != null;

CreateLabel(name, text, tooltip, isFixed, false, isAdditionalInfo, isEmphasized, isColon);



ReadPosition(n);

SetPosition();

SetWidth();

ReadAccess(n);



SetAccess();

AddElement();



}

}

protected void CreateLabel(string name, string text, string tooltip,



bool isFixed, bool wrap, bool isAdditionalInfo,

bool isEmphasized, bool isColon) {

Style s;

HorizontalAlignment hAlign = HorizontalAlignment.Left;

if (isAdditionalInfo) {



s = StyleManager.StyleAdditionalInfo;

} else if (isEmphasized) {

s = StyleManager.StyleEmphasized;

} else {

s = StyleManager.StyleLabel;

}

label = controlPool.Create(ControlPool.TypeLabel, s)



as Label;

currentElement = label;

label.Name = name;

CreateControlTag();

controlTag.AdditionalInfo = isAdditionalInfo;

if (wrap) {



TextBlock tb = new TextBlock();

tb.Text = text;

tb.TextWrapping = TextWrapping.Wrap;

label.Content = tb;

} else {

if (!isAdditionalInfo) {

if (isColon) {

// Only labels with colon can be right-aligned.

// The colon is only displayed for left align

if (UserSettings.Current.RightAlignLabels) {

hAlign = HorizontalAlignment.Right;

} else {

text += ":";

}

}

}

label.VerticalAlignment = VerticalAlignment.Center;

label.Content = text;

}

label.HorizontalAlignment = hAlign;

if (isFixed) {



label.FontFamily = StyleManager.FontFamilyFixed;

} else {

label.FontFamily = StyleManager.FontFamilyBaseUI;

}

label.ToolTip = tooltip;



}

深入研究之三:利用规则引擎提升系统稳定性

Lawson 服务端的设计目标是支持多种平台。客户系统安装在各种硬件和操作系统上,数据也可能存储于各种 RDBMS 中。这些环境的异构性,将给系统的维护与监控带来巨大挑战。因此,系统无可避免会划分出很多层次,这就为性能下降和信息丢失埋下了伏笔。

为了提升系统的稳定性,Lawson 开发了所谓 Foundation Stabilizer。这是一个在支持和开发人员定义的通用约束与模式基础上的规则监控系统,它通过 JNI(Java Native Interface)组件,实现对系统运行时性能和配置数据的收集,如底层操作系统(比如 CPU 使用)信息。一旦发现可疑情况,Stabilizer 将向维护人员发出警报(可以是 RSS、Email 和 HTML 等形式),并自动实施适当的解决策略(如降低问题线程的优先级),以稳定系统运行。如下各类系统信息,构成了通过规则和模式匹配定位问题的基础:

  • 性能计数器
  • 属性文件的数据
  • Java 程序中的参数和属性
  • 系统环境信息
  • 任务基本信息、当前状态和活跃度等
  • JVM 状态及其资源消耗情况
  • 平台无关或依赖方面的约束信息

Example 3: A warning issued by the Foundation Stabilizer. © 2007 Lawson Software, Inc. All rights reserved. This document is for informational purposes only. Lawson Software makes no warranties, express or implied, in this summary.

规则引擎采用 Java 编写,其运行机制使用 Prolog(译者注:Prolog 是由事实(Fact)、规则(Rule)和查询(Query)构成的,这三个术语在下文需要用到)描述。如下代码片段中的 stabilize() 方法,将通过一个与业务系统运行在同一 JVM 中的 Java 线程定时执行。

 boolean stabilize() throws Exception {

KnowledgeBase kb = engine.getKnowledge();

engine.consult("mvx/res/Stabilizer.rules");



engine.consult("mvx/res/Constraints.rules");

engine.setQuery("cleanup");

try {



addSysInfo(kb);

} catch (IOException ex) {

KQLOG.EX("MvxStabilizer:Failed to collect system information", ex);

return false;

}

KQLOG.DT("Run Stabilizer");



boolean res = engine.setQuery("stabilize");

if (!res) {



KQLOG.E("Could not stabilize - rule engine failed");

return false;

}

KQLOG.DT("End Stabilizer");

Enumeration en = kb.elements();

while (en.hasMoreElements()) {



// Handle results from the rules engine

}

return true;



}
  • 此方法首先从规则引擎中取得已有知识库。知识库中包含了收集或计算得到的有关系统的事实项。事实的表述格式很简单:“操作 条件”或者“操作 条件 1 条件 2 …”。比如:"jobName" "IKDXLZ9A.corp.lawson.net:26100" 4676 "Looping";"jobCPU" "IKDXLZ9A.corp.lawson.net:26100" 4676 55。
  • 规则(本例中是两组)与引擎是紧密相连的。
  • 通过执行“cleanup”查询,可将陈旧信息从知识库中删除。
  • 可用系统当前信息更新知识库。
  • 执行“stabilize”查询后,规则将开始收集新的事实项。
  • 所有规则执行完毕后,就可以轮询整个知识库里的新事实并一一处理了。
...

rule(looping_nostat1):-

jobCPU(Addr, Id, CPU),

CPU>40,

jobChange(Addr, Id, Change),

(Change==0;Change>999),

not(loopingMem(Addr, Id, _)),

info(currentTime, Time),

assert(loopingMem(Addr, Id, Time)).

rule(looping_nostat2):-



loopingMem(Addr, Id, LTime),

jobCPU(Addr, Id, CPU),

CPU>40,

jobChange(Addr, Id, Change),

(Change==0;Change>999),

info(currentTime, Time),

Time-LTime>5,

jobType(Addr, Id, JobType),

jobName(Addr, Id, Name),

assert(warning(2, 4, 'Job may be looping', 'Job', JobType, Addr, Name, Id)).

rule(looping_nostat3):-



loopingMem(Addr, Id, LTime),

not(jobName(Addr, Id, _)),

retract(loopingMem(Addr, Id, LTime)).

...

上述三个规则,用于检测过度占用 CPU 资源的任务。

规则一,首先检测过度消耗 CPU 的任务,然后对其监控。jobCPU首先择定某任务(用 Address、ID 和 CPU 描述);若此任务的 CPU 占用率超过 40%,且变动率指标为 0 或超过 999,就将创建新的事实项(loopingMem),并设置 Time 值。

规则二,负责检测出被监控时间超过五分钟的任务。事实项 loopingMem(由规则一设定的)在整个知识库中搜索这些任务。接下来执行的操作和规则一相同。最后,Time 被更新,并增加了 jobType 和 jobName。在 M3 业务引擎中,会使用这些事实项实现警告功能。

规则三,负责检测已经停止运行的任务,并将它们从监控列表中删除。loopingMem 首先从知识库中搜索全部任务。如果在知识库中不能找到任务项,则将其从监控列表中清除。

总结

Lawson 与 Intentia 在 2006 年的合并,迫使开发人员不得不面对一大难题——如何实现现有众多采用了不同技术的业务系统的集成。为了在公司的两大旗舰 ERP 产品线上提供持续一致的用户体验,客户端解决方案必须采用 Java 和 4GL 实现与业务层的交互。

在与 Frog 公司共同分析现有 UI 技术后,Lawson 决定引入 Microsoft 的.NET3.0 框架——即使用 WPF 实现 UI 组件与业务逻辑和数据的绑定,用 WCF 实现客户端与现有 Web Service 的交互(当然,部分系统必须做一定程度的重构)。如此一来,客户端应用的开发就变得高效了。新的 Lawson 智能客户端定义了插件式的客户端架构,从而大大降低了新应用和新功能的添加难度。通过对全公司代码库的统一,保证了服务端业务逻辑能为各种客户端共享,各系统的可维护性也提高了。

在研发智能客户端过程中,申述式编程语言应该为 Lawson 开发人员留下了深刻印象。说实话,申述式语言的学习成本相当高,不易使用,理解难度大。但一旦掌握了它,就可在研发生产力上获得巨大收益。就 Lawson 开发人员的经历来看,尽管初期遇到的困难很多,但最终在生产力上得到的回报是巨大的,付出的努力是值得的。

Lawson 最初版本的智能客户端架构定位于利用强大而灵活的 UI 组件构建新型应用,并在提升用户生产力和满意度的同时,保证用户体验的持续和连贯。从用户目前的反馈来看,Lawson 的努力没有白费。

查看英文原文:Case study: A new approach to integrating architectures post-merger at Lawson
译者简介:罗小平,上海某大型公司互联网中心技术总监,CSDN大版主,网络 ID 为 lxpbuaa(桂枝香在故国晚秋),曾著有《Delphi 精要》一书。个人博客为http://blog.csdn.net/lxpbuaa,现在 CSDN 主持翻译国外专家Herb Sutter 的中文博客。他的 Email 和 MSN 为lxpbuaa AT 263.net