Lightning Connect Custom Adapters (技术贴)

作为一个自认为特别有人文情怀的人,始终觉得写技术贴特别没劲儿,于是“云上的世界”这个栏目基本上不死不活地空置了两年,最近突然觉得这么空下去挺对不起这个浪漫的专栏名,于是决定从即日开始,在这里写一写自己在force.com这个庞大的生态系统中探索时的些许感悟和积累。

Salesforce在北美欧洲澳大利亚日本等地非常地不可一世,但在中国却因为价格策略市场认知等因素,再加上本地竞争者的有力阻击,基本上还处在蓄势待发的状态,不用说普通读者,即便对大多数互联网或IT从业者而言,也还是一个相对比较新鲜的事物。

因此,我决定当一个布道者。

虽然也了解的不多,但就当投石问路,说不定有读者看了这些文章之后决定成为一个Salesforce的管理员、开发者、咨询顾问、架构师呢,要是再能由此遇到一些志同道合的朋友,那就更完美了。

好了,闲话少说,直奔主题吧。

首先预告一下,“Lightning Connect”这个系列都将会是纯技术贴 - 我会努力把技术贴和和非技术贴分开,这样,不同兴趣的读者可以各取所需 - 如果你不是Salesforce的开发者,这篇文章可能不适合你。

Salesforce在Winter '15的产品发布当中第一次介绍了Lightning Connect - 这是一个新的将Salesforce与外部数据源进行整合的接口。

简而言之,它允许你用非ETL的方式(这么说听起来貌似很高端,其实说白了就是通过HTTP Callout来获取数据)将外部数据源集成到Salesforce中来,创建所谓的external object,并承诺这些external object和那些生存在Salesforce自己底端Oracle数据库中的standard object或custom object有相似的行径与平等的地位。

Lightning Connect本身的设置并不复杂,Salesforce的免费在线教学网站Trailhead中有专门的一个Module来介绍,所以我就不赘言了,有兴趣的朋友可以自行移步查看。

https://developer.salesforce.com/trailhead/module/lightning_connect

然而,Lightning Connect的局限性在于它只能识别那些符合OData协议的数据源。

即便一些大公司的产品,比如SAP或Microsoft Dynamics,都有基于OData协议的数据接口,但毕竟不甚方便,于是在Summer '15的产品发布中,Salesforce推出了一个让人激动的new feature,也就是这篇文章的主题 - Lightning Connect Custom Adapters。

Lightning Connect Custom Adapters,或者叫做Apex Connect Framework,允许开发者自己用Apex来编写自定义适配器(Custom Adapter),然后实现和任何格式或协议的数据源进行实时的完美集成。

Apex为此增加了一个叫做DataSource的命名空间,所有新的方法和数据类型都在此命名空间之下定义。作为开发者来说,需要做的只有两件事:

1. 创建一个Apex class,继承DataSource.Provider这个接口,实现接口里预定义的函数;

2. 创建另一个Apex class,继承DataSource.DataSourceConnection这个接口,然后实现接口里预定义的函数。

这两个Apex class完成之后,在创建外部数据源时你会在Type下拉菜单中看到你自己的DataSource.Provider,选择该Provider,这个新的外部数据源就和你自定义的Adapter联系起来了。

在新创建的数据源中点击“Validate and Sync”按钮(该截图是取自Edit一个已有的数据源,所以只有“Sync”按钮),这时后台代码会调用DataSource.DataSourceConnection那个Apex class中的sync()函数(后文中会看到),�并返回外部数据源中所有的表信息(该例中外部数据源只包含了一个叫做“Looper”的表,真实环境中每个外部数据源可能含有多个表)。

选择你想要同步的表,然后进行同步,Salesforce则会按照你在sync()函数中的定义创建相应的外部数据(external object)。如果你点击新生成的外部数据查看其详细信息的话,你会发现界面非常熟悉 - 和Salesforce自身的standard object与custom object基本一样。

需要注意的是,external object的后缀是__x,而custom object的后缀是__c。

另外,由于外部数据的出现,Salesforce新增加了两个字段类型External Lookup Relationship Field和Indirect Lookup Relationship Field。简单说就是,两个都是用来建立Lookup Relationship的,不同之处是,前者external object是parent object,child object可以是standard object或external object;后者external object是child object,而parent object是standard object。这些东西上面那个Module里都有详细讲解,在此我就不细说了。

接下来我们仔细看看DataSource.Provider和DataSource.DataSourceConnection这两个接口。

DataSource.Provider

实现该接口的Apex class需要实现下面三个方法:

getAuthenticationCapabilities()告诉Salesforce外部数据源支持何种验证方法,包括匿名、BASIC、Oauth、或者Certificate。这些选项会在上面截图的Identity Type选项中出现,本例支持匿名和BASIC,但因为不做任何Http Callout,所以并没有什么用处。

getCapabilities()告诉Salesforce外部数据源支持何种操作,ROW_QUERY、ROW_UPDATE、ROW_CREATE、ROW_DELETE、SEARCH、REQUIRE_ENDPOINT、以及QUERY_PAGINATION_SERVER_DRIVEN。

getConnection()则返回一个实现了DataSource.DataSourceConnection的Apex class实例,来做真正的Adapter工作。注意,由于每一次对外部数据的SOQL或SOSL操作都会调用该函数产生一个DataSource.DataSourceConnection的Apex class实例,所以该Apex clas的构造函数中不应该有任何expensive的操作,比如callout之类。

P.S. 关于DataSource.Provider的一些额外解释

ROW_QUERY - 所有的SOQL操作,包括浏览UI时候系统产生的SOQL操作;

ROW_UPDATE、ROW_CREATE、ROW_DELETE - 常规的CRUD操作;

SEARCH - 所有的SOSL操作,包括在UI中进行全局搜索;

REQUIRE_ENDPOINT - 控制是否在设置新外部数据源页面中要求输入一个endpoint地址;

QUERY_PAGINATION_SERVER_DRIVEN - 告诉Salesforce外部数据源的分页是否是server端控制的;

DataSource.ConnectionParams - getConnection()函数中会传入一个叫做connectionParams的参数,这个参数的值取决于设置外部数据源时管理员选择的何种验证方式 - 如果是BASIC的话则会包含USERNAME和PASSOWRD,如果是Oauth的话则会包含oauthToken...不过Salesforce建议使用named credentials来做callout,而不是直接把credential提供给外部数据源。本例中因为不涉及callout,所以用不着connectionParams,我会在后续的文章中介绍相关的用例。

DataSource.DataSourceConnection

实现DataSource.DataSourceConnection的Apex class实际上是真正做所有工作的幕后英雄。

这个class里面涉及到的东西很多,但大多数都只是一些DataSource下新的数据结构而已,Salesforce的文档很详细,我在代码中也做了比较细致的注释,所以基本的东西就不重复了(DataSource.Filter是一个很有意思的数据类型,我在注释中也给予了额外的一些篇幅来解释,提醒一下注意)。

我这里想着重说的是我觉得最重要的、但文档中感觉并没有解释的很详尽的两个概念,query()函数和search()函数。

先说query()函数。

文档中说每次对external object进行SOQL操作时都会触发query()函数,然后所有和该SOQL相关的内容都会以QueryContext参数的形式传递给query()函数,然后完全交给函数代码来做相应的操作。那么问题来了 - 究竟query()函数需要处理多少种SOQL用例呢?处理 SELECT columns FROM table 当然不用说,处理 SELECT columns FROM table WHERE externalId = 'something' 也不用说,那么再复杂一些的SOQL有处理的必要吗?

我不知道别人有没有这样的疑虑,总之我是思忖了许久,后来终于醒悟过来 - 这完全取决于你啊!你如果希望这个external object仅仅支持最基本的UI SOQL,那就实现上述两个用例外加 SELECT COUNT()就好,你如果希望在代码中对external object进行更复杂的SOQL操作,那就实现那些更复杂的SOQL操作,当然,如果你没有在query()代码中实现相应的SOQL操作逻辑,而又在别处试图对这个external object做相应的操作,那等待你的当然就是exception了。

先来看两个最简单的例子。

假设你在Salesforce中已经通过你的外部数据源创建了某个external object,并以此为基础创建了一个tab(完全和对standard object的操作一样),那么你在浏览该tab的时候Salesforce后台会自动对该external object进行SOQL查询。

比如/x00/o 这个操作是查找最近浏览过的数据,而/xoo 则是列出所有的数据。

下面是DEBUG LOG中对两个操作的数据。

第一个截图是对/xoo/o 的操作,虽然QueryContext的信息看不全,但其实它传进来一个filter,columnName是Id,type是EQUALS,columnValue是‘0013....’,我的query()函数捕捉到了这些信息,然后对应的进行了操作(因为我的外部数据其实是映射Account数据,所以我做了一个SOQL操作,其它情形还可能是对某个REST API进行callout,诸如/endpoint/data?id='0013...',然后在对返回的数据进行整理)。

�第二个截图是对/xoo 的操作,QueryContext中filter为null,所以我就简单做了一个SELECT的操作,返回数据。

如果你只是要通过UI来访问external object,那这些基本上就差不多够了(除非你设置定制化view),但如果你想让external object支持更复杂的SOQL操作,那就要将这些操作都在你的query()代码中予以捕捉和实现。

本例中的external object支持了WHERE从句中的大部分SOQL关键字,我们可以通过workbench做一个实验:

如果我把代码中对DataSource.FilterType.LIKE_的支持去掉,那么执行下面SOQL时会出现exception;

如果将代码中对DataSource.FilterType.LIKE_的支持加回来,那么数据会顺利返回;

对有AND的逻辑从句也能够应付(DataSource.Filter的type属性和subfilters属性居功至伟)。

再说说search()函数。

理解了query()函数后,search()函数就简单多了。当Salesforce进行全局搜索的时候,调用search()函数,而你无非就需要考虑两件事:1. 外部数据源中的哪些external objects被搜索(可以通过遍历SearchContext的tableSelections属性来作出决定);2. 某个external object中的哪些字段被搜索。

比如Salesforce提供了一个Util方法searchByName(),其实就是当search发生时,拿着searchPhrase对external object的Name字段进行一个关键字为CONTAINS的query,如果使用这个Util方法,那么当你在Salesforce里做如下针对电话号码的全局搜索时,对应的external object数据不会出现,因为该方法只针对Name做query。

在本例代码中,我注释掉了该Util方法,转而自定义了一个针对Name和Phone两个字段的query,这样的话,做同样的一个全局搜索,你可以看到external object对应的数据出现在了返回结果中。

最后补充一句,每当你做全局搜索的时候,�一个SearchThreadPools操作会在DEBUG LOG中产生,你可以看到,在后代的确调用了我们自定义的search()函数代码,做了一个基于Name和Phone两个字段的query操作。

好了,这篇文章已经很冗长了,就在这里结束吧,�下一篇再见~

GitHub

https://github.com/jacky1999cn2000/lightningconnect

参考资料

https://developer.salesforce.com/blogs/engineering/2015/05/introducing-lightning-connect-custom-adapters.html

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_connector_top.htm

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_connector_example_loopback.htm

MAY THE FORCE BE WITH YOU!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容