2017-12-28

Kayako Language Engine and Caching System

先前處理 Kayako email 發送的多國語系問題時,採用新建 LanguageEngine instance,透過 LanguageEngine instance 抓取所需詞彙的方式完成。後來發現,這樣的解法會影響其他信件的發送,例如:ticket auto-close 時的通知信件,會因無法抓到所需詞彙而失敗,追根究柢後發現:無論產生多少新的 LanguageEngine instances,底層實際載入與儲存詞彙都使用相同一塊空間,無法避免動態切換語系時交互影響。

上圖是 LanguageEngine 與 SWIFT caching system 之間的關係。

  1. 首先 LanguageEngine 在載入詞彙時會提供 Laod() 或 LoadQueue() 供外部呼叫,最終都落到 SWIFT 的 caching system,使用 SWIFT_CacheStore->LoadQueue()。
  2. SWIFT_CacheStore 裡頭涉及 2 內部變數:CacheMemory (SWIFT_CacheMemcache) 與 Registry (SWIFT_Registry);因為目前系統並未設定 MEMCACHE_HOST,因此這邊只會使用 Registry 作為實際儲存位置。
  3. SWIFT_Registry 內部的 storage 分為兩層,level 1 的是 in-memory 的 _registryCache,level 2 是 database 的 swregistry table。
這邊要注意:SWIFT_Registry instance 是在 SWIFT 中生成,因此無論每次執行無論處理過多少張 tickets 都會使用相同的 SWIFT_Registry instance,且單一 SWIFT_Registry instance 中,每一個 key 值只會對應到一個 value。

為了避免語系切換時,汙染 SWIFT_Registry instance 中的語言詞彙,因此決定改採直接 SQL query 的方式載入所需語言。

ps. 這邊的 caching system 指的是 data cache,Kayako 在 4.0 剛推出時並沒有針對 Memcached 整合的功能;倒是官方推薦使用針對 code cache 的 XCache [1]。相較於新版 PHP (>5.5) 更被推崇的 code caching system 是 opCache [2]

see also:
[1] Kayako Forum: V4 Very Slow!
[2] StackExchange: PHP 7 opCache v PHP 5.6 XCache

2017-12-10

.NET Standard 2.0

先前 [1] 分別從 framework 與 virtual machine 兩個面向聊過 .NET framework 的角色,其中提及 Common Language Infrastructure (CLI) 的三大組成:Common Intermediate Language (CIL), standard libraries (BCL and FCL), 與 Common Language Runtime (CLR)。

最近因為 .NET Core 2.0 的正式發佈,越來越多人開始回頭看原本 .NET framework 的組成,以及各種實作與標準之間的關係;這兩天剛好看了 InfoQ 上的一篇文章 [2];再繼續之前,首先要抓準一個概念:現行的 .NET 平台實作不只 .NET framework 一家,還有 .NET Core, Mono, Xamarin 等等,而各家實作在跨平台的特性與實現方法上也不盡相同,根本原因在於一開始的 CLI standard library 以大鍋菜的方式設計,雖然有 BCL 與 FCL 之分,但 BCL 並未很好地把 Windows dependent 的功能剝離出去,導致後續跨平台 runtime 實作困難。

以下是順手整理的心得:
  • .NET Standard 重新定義了 BCL 的標準,讓各家 (.NET Framework, Xamarin, and .NET Core) 實作的 BCL 趨於一致。
  • 針對 BCL 之上的 application models,.NET Core 捨棄 Windows Forms 與 Windows Presentation Foundation (WPF),僅保留 Console,並實作 APS.NET Core [3]
簡單來說,.NET Standard 是未來跨平台元件或應用程式開發時的必要 (但非充要) 條件,畢竟一個可以正常運作於 .NET Standard 實作上的元件,仍可能依賴該實作的特定函式庫。

至於 Portable Class Libraries (PCL) 則是針對既有 .NET 平台實作所提的暫時方案;透過 Visual Studio 與 PCL 的幫助,Visual Studio 可自動將程式中 dependency 對應到各家實作的函式,免去開發人員額外偵測與判斷的痛苦。有關 PCL,個人認為 InfoQ 文章中參照的 [4] 寫得比較完整。

附帶一提:當微軟在 2017/8/13 發佈 .NET Standard 文件 [5] 時,裡頭的對應表可能讓人誤以為 .NET framework 4.6.1 就已經實作 .NET Standard 2.0;實際上,在 .NET framework 4.6.1 尚須加裝 .NET Core 2.0 SDK 才能支援,否則會缺少 200 多個 API 實作。因此真正的完整支援要到 .NET framework 4.7.1 才算完成 [7]

see also:
[1] .NET: Framework or Virtual Machine
[2] InfoQ: .NET Core and .NET Standard: What Is the Difference?
[3] .NET Standard - Demystifying .NET Core and .NET Standard
[4] Understanding .NET Core, .NET Standard, .NET Core applications and ASP.NET Core
[5] Microsoft Docs: .NET Standard
[6] Microsoft .NET Blog: Announcing the .NET Framework 4.7.1

Salesforce SOSL, SOQL, and Tokenization

Salesforce 在登入後的畫面頂端提供了一個搜尋框,提供使用者搜尋各式資訊,例如:Account, Contact, Opportunity, Contract 等。

但由於預設的搜尋行為使用其內部的斷詞系統,因此針對一些專有名詞搜尋時,可能就不那麼盡如人意;例如:當資料為東京都時,可以用東京或完整的東京都搜尋到,但是無法用京都搜尋到該筆資料,或許這在很多時候是好用的,尤其當大家公認東京都跟京都是完全不同的 entity,但有時候當使用者只知道公司部分名稱時,就容易造成困擾。以下是 Salesforce 搜尋框的兩個主要限制:
  • 預設行為為 start with:一如前例,系統搜尋行為會找出以京都為起始字串的資料,如果想找到東京都,搜尋時必須輸入「*京都」。
  • 底層以 morphological tokenization 為基礎的索引:換言之,當專有名詞不常見時,可能因斷詞系統無法正確辨識而找不到。

上述兩個問題在斷詞系統剛從 bigram 轉換為 morphological [1] 時備受批評,但在 2017 年 4 月 [2] 跟 6 月 [3] 有修正得更精細些,例如:「横浜市中区」已經可以使用「中区」找到,關鍵不只是 start-with 的問題,更在於斷詞系統能識別「中区」一詞;然而對於廣大可能的中文專由名詞來說,這樣的解法仍然不夠。

其實英文也不見得就完全沒事,因為有時候在專有名詞欄位,使用者可能只輸入縮寫,甚至是自創的縮寫,這時候 morphological 一樣無法正確辨識並提供搜尋時所需的索引。

想要徹底解決,自己動手寫點程式可能免不了,就 Salesforce 提供的 SOSL 與 SOQL 而言,前者仍然以斷詞系統為基礎 [4],因此只能選用 SOQL 的 like-anywhere 完成,但相對效能上難免吃重些。

see also:
[1] Salesforce Release Notes (Winter'15): Users Searching in Chinese, Japanese, Korean, and Thai Find Optimized Results
[2] Salesforce Community Known Issues: New morphology tokenization for Japanese characters after new search framework can cause unexpected morphological tokenization result
[3] Salesforce Community Known Issues: Chinese - Morphology tokenization improvement for mainly proper name noun
[4] Force.com SOQL and SOSL Reference: Introduction to SOQL and SOSL
[5] Salesforce Community Known Issue: Wrong results for Japanese OR search for file content
[6] ROSETTE.com: Why Tokenization Matters

2017-12-08

Unit Test for Kayako

接手 Kayako Fusion 這套系統超過一年了,一直沒有作單元測試,主要原因有以下兩個:
  1. License 驗證:幾乎所有測試需求都跟底層的 SWIFT framework 有關,而要載入這個 framework instance 的前提是 license 被驗證為有效。
  2. 缺乏文件:雖然它們家的 SWIFT_TestCase 也是繼承自 PHPUnit_Framework_TestCase,但不知如何執行。
然而直接在 UI 端作 acceptance testing 就不用煩惱這個問題,只要修改 client 端的 host 設定,將 license 中的 domain name 綁定測試機 IP。這時候即使測試機上設定其它 hostname,只要 client 端瀏覽器使用 license 的 domain name 連道測試機,就可以正常執行。

最近因為修改的東西愈趨複雜,很多問題無法單純在測試機上透過 acceptance testing 讓問題浮現,往往都是 patch 上正式機才爆出,因此需要回頭徹底解決,步驟上大致如下:
  1. 安裝 PHPUnit;因為 PHP 版本還在 5.4 系列;只能安裝 PHPUnit 4.8.36 [1] [2] [3]
  2. 修改 $KAYAKO_HOME/tests/index.php;因為 license 檢驗,會比對 license 中的 domains 與 $_SERVER['HTTP_HOST'],而在 UI 端作 acceptance testing 時,$_SERVER['HTTP_HOST'] 來自 HTTP request header 中的設定 [4] [5],改執行 unit testing 時缺少真正的 request header,因此要在 index.php 中自行設定,並作為執行時的 bootstrap script。
  3. 執行範例如下:/usr/local/bin/phpunit.phar --bootstrap ./index.php ./__swift/library/Date/class.SWIFT_DateTest.php
官方預帶了一些 test cases,分別針對 KQL, KQL2, Cron, Date,可以針對需求仿造。

後續可能要再看一下 mock object 與相關 inject testing database 的方法,這樣就可以把單元測試這塊補上。

see also:
[1] PHPUnit
[2] PHPUnit Download
[3] PHPUnit 4.8 Manual (PDF)
[4] PHP.NET: $_SERVER
[5] StackOverflow: $_SERVER['HTTP_HOST'] not set
[6] Kayako Classic User Guide
[7] Kayako Developer Resources

2017-11-05

Salesforce Secured Callout

在應用系統介接時,使用 Web API 是目前常見的手段;Salesforce 也提供這樣的模式,無論是扮演 service provider (使用內建的 SOAP Web Services,或是自訂 RESTful API),或是 service consumer,都有對應的支援;其中當 Salesforce 扮演 service consumer 時,最常見的是使用 Apex 自行呼叫外部 Web API,這種呼叫模式在 Saleforce 中稱為 callout。

在安全性方面,系統介接時一般會優先討論連線方面的安全性,更嚴謹的才會觸及資料面的安全性;這邊我們只討論前者,也就是透過 TLS 對 HTTP 加密,這樣的外部呼叫稱為 secured callout。

值得注意的是:Salesforce 在實作 secured callout 時,對簽發 target service 憑證的 CA 只認可正向表列中 root CA 簽發的憑證 [1] [2],然而這份列表有些陳舊 [3],許多常見的 root CA 都不在列表中,例如 TWCA Global Root CA,若 target service 不是自有的服務,無法變更憑證時,可能會被迫使用未加密的 HTTP。

另外在 protocol 版本上,Salesforce 支援 TLS 1.1 與 1.2 [4],TLS 1.0 由於可降階至 SSL 3.0 且 SSL 3.0 設計上有安全性缺陷,因此目前已停用 TLS 1.0 [5]

除了 secured callout 外,另一個會使用到憑證的場合是:使用 Salesforce 實作自訂的網站,並掛上自家的網域名稱;這時候對於憑證的限制比前者稍微放寬,允許不是由 root CA 直接簽發的憑證。管理者可以新增所需的 intermediate CA 到 Salesforce [6],但根源的 root CA 仍必須是列表中認可的。

至於新增 custom site 憑證到 Salesforce 時,由於這邊扮演的是 service provider,因此上傳的資訊中必須包括 private key;使用的檔案格式是 Java KeyStore,這方面的基礎知識可以參考 [7]

see also:
[1] Outbound Messaging SSL CA Certificates
[2] Know More about All the SSL Certificates That Are Supported by Salesforce
[2] Salesforce Community Idea: Manage List of Trusted Certificate Authorities 
[3] Summer '15 Release Notes: Test and Use Advanced Networking Protocols
[4] Salesforce Disabling TLS 1.0
[6] SSL Certificate Standards
[8] Making Authenticated Web Service Callouts Using Two-Way SSL

Salesforce Release Cycle and Certification

Salesforce 維持每年三次的更新節奏 [1],分別為:
  • Spring Release: February
  • Summer Release: June
  • Winter Release: October
其中 Winter release 會標示來年的年份,例如 2017/10/16 發表的 Winter release 會標示成 Winter'18。

此外,每次新版發表後,官方會給予所有證照持有人約 8 個月的時間,對更新內容進行測驗;起算時間從測驗提供的起始日起算,例如:Winter'18 的發表日是 2017/10/16,但是更新測驗的起始日是 2017/11/20,往後約 8 個月,到 2018/7/13 是證照持有人需完成測驗的期限。

費用方面,目前基本的 Salesforce Developer Certification 考試費用是 USD $200,之後針對新內容的測驗費是 USD $100/year,如果同時擁有多張證照,每年維持有效的測驗費會有些可觀,但目前上限是 USD $300。

see also:
[1] Salesforce Blog: Seasonal Release
[2] Salesforce Certification: Maintenance Exam
[3] Salesforce: Certification Mainenence Overview
[4] Salesforce: Cost to Maintain a Salesforce Credential

2017-10-30

Salesforce: Objects Generalization and Specialization

Salesforce 在設定畫面上的 object 名詞可能是個讓習於物件導向程式開發者困惑的地方:畫面上的 object 代表 type definition,而非 instance,真正描述 instance 的名詞是 record。

然而在處理 generalization & specialization 議題時,畫面上又給了一個新名詞,record type。系統管理者可以把所有可能用到的欄位都開在同一個 object 裡,透過 record type 的方式,限定各個 record type 所能看到的欄位。值得注意的是:這邊的縮限手段,是採用綁定 layout 的方式達成,如果使用 Apex 程式存取,則不在此限。

從 generalization & specialization 的抽象層次來看,record type 確實提供了一種 specialization 的途徑,與物件導向中的 subclass 類似,但很不一樣的是:record type 處理的是資料面的 specialization,讓特定 record type 只能看到原來眾多欄位的子集合,而 subclass 同時兼顧資料面與行為面的 specialization,各個 subclasses 能夠自己額外定義欄位並實作不同行為。

此外,record type 並不像物件導向的繼承關係將共同的部分獨立抽取至 super class,相對於 record types,object 等於是所有欄位的大雜燴,看不到哪些是共同的欄位,對於 generalization 有潔癖的人來說,似乎只能用類似物件導向 composition 的方式處理 [1],也就是 Salesforce 中的 master-detail object relationships [2]

使用 master-detail relationships 實現 generalization 難免有強加物件導向觀念的味道,但更不幸的是:即使採用這麼複雜的設定,仍然無法達成目的。原因在於:使用 master-detail relationships 實作 generalization 時,生命週期控制在 master 端,相當於是 specialized subclass;但在數量對應上 master 端屬於 one-side 而非 many-side,違背 generalization 的初衷。

see also:
[1] Salesforce Developers Forum: Custom Objects and Inheritance
[2] Salesforce Object Relationships

2017-10-24

Apex Serialize Objects to XML

先前的文章 [1] 中提到,Salesforce 的 REST 同時支援兩種輸出格式,XML 與 JSON;client 可以透過 HttpRequst header 中 Accept 屬性指定想要的 content type;這兩個 serialization 的過程對於 REST service 的實作者而言是完全 transparent 的,意即實作時 method 並不 return string 型別,而是直接 return sObject,由系統的 outbound messaging 機制直接處理掉。

然而對於 Apex 開發者而言,程式語言級別對 XML 與 JSON 在 serialization 的支援度就差很遠。以 JSON 為例,無論物件多複雜,要轉成 JSON string 只要一行 JSON.serialize() 就可以搞定 [2],但是轉成 XML string 時,無論使用 XmlStreamWriter [3] 或 DOM.XmlNode [4] [5] 都需要針對物件中各個 fields 作繁瑣地處理。

為了達到快速生成 XML 字串,我們可以透過 (1) 實作一 REST service;(2) 在 Apex 中呼叫該 service,並將 HttpRequest Accept header 設定為 'application/xml' 的方式,快速從 HttpResponse.getBody() 取得 outbound messaging 生成好的 XML 字串。

概念上是走 loopback invocation 的方式,利用既有的機制,寫最少的程式,完成任務。

see also:
[1] Salesforce RESTful Services: Response Content-Type
[2] JSON Support: Roundtrip Serialization and Deserialization
[3] XML Support: Writing XML Using Streams
[4] XML Support: Reading and Writing XML Using the DOM
[5] XmlNode Class
[6] Salesforce Trailblazer Community Idea: Convert Object to XML (Serialize)

.NET: Framework or Virtual Machine

一般提到 .NET 多半使用 .NET framewrok 這個名詞,內容可能包括:
  • Programming Languages: 包括 C#, C++/CLI, F#, IronLisp, IronPython, VB.NET, Powershell 等 [1]
  • Compiler
  • CLI Standard Library: 裡頭主要有 Framework Class Library (FCL) [2] 與 Base Class Library (BCL) [3]
  • Common Intermediate Language (CIL)
  • Common Language Runtime (CLR)
對於傳統使用 C++ 搭配 MFC 開發視窗程式 (GUI desktop application) 的開發者而言,視線會集中在 C# 與 CLI Standard Library;此時 .NET 更趨近於 framework 的角色,提供眾多的 classes,開發人員根據 framework 規範撰寫 component。

然而就 .NET 整體架構來看,最大的改變在於提供了統一的 intermediate language (IL) 介面,使得前端可以同時支援多種程式語言,呼叫同一套 standard library;且這個 IL 設計是獨立於 CPU architecture 與 operating system platform 之外的 [4]。換言之,開發者可以自由地在任何想要的平台上開發 IL 所需的 runtime,因此角色上更接近 virtual machine。

雖然說大多數的 .NET 應用程式都在 Windows 與其內建的 runtime,但還是有不少開發者作過這方面的努力:
但實際使用上,卻不那麼順利。主因在於 .NET 雖然 CIL 提供了優良的隔離,獨立於 Windows 之外,但最重要的 CLI Standard Library 卻因為服務視窗程式開發者的初衷,納入了太多 Windows 特有的 library,尤其是圖形介面 (GDI) 這段,導致許多在 Windows 上開發的元件,無法順利過渡到其它 runtime 執行。

換言之,雖然 CIL 與 CLR 提供了很大 virtual machine 的特點,但身為 runtime infrastructure 重點的 standard library 並未提供相應的設計,導致整個 .NET 回到近乎 Windows 獨有的情況。

這幾年微軟開始改革 Windows Server 的產品線 [8],將圖形介面剝離出來,打破原本 Windows runtime 包山包海的狀況,同時也開始推更乾淨的 .NET Core;就架構而言這應該有助於第三方 .NET runtime 的普及,但微軟將發展 Mono 的 Xamarin 收入麾下卻讓非微軟勢力瞬間消失。

注意:Mono 在 runtime 實作仍是按照舊有的 CLI (ECMA-335) [9] 而非 CoreCLI,因此 Mono 是否會如 .NET Core 壯大仍有很大變數。

此外,Mono 的貢獻在不僅僅於 runtime,還有跨桌面平台的解決方案,這塊的未來應該屬於 Electron [10];至於依賴 Mono runtime 的跨行動平台方案 - Xamarin,個人認為疑慮就很大,目前 Windows Phone 已確定式微,微軟還願意花多少資源給跨行動平台開發方案,看來非常不樂觀。

這兩年微軟也開始引入 LLVM 技術,實作相關的 compiler (LLILC),想要深入這個主題的可以一起參考看看。

see also:
[1] Wikipedia: CLI Languages
[2] Wikipedia: Framework Class Library (FCL)
[3] Wikipedia: Standard Libraries (CLI) 
[4] Wikipedia: Common Intermediate Language (CIL)
[5] Wikipedia: DotGNU
[6] Wikipedia: Protable.NET
[7] Mono Project
[8] Wikipedia: Microsoft Windows Server Core
[9] ECMA-335: Common Language Infrastructure (CLI)
[10] Electron

2017-10-13

SSL Certificate Standards

要搞懂系統認證之前,首先要對非對稱式加密有基礎瞭解,不外乎就 2 種 key:
  • Public key: 用於加密與驗證簽章
  • Private key: 用於解密與產生簽章
至於這兩把 key 的產生通常會由共同信任的第三方,也就是 CA (Certificate Authority) 發出。[1]

Standards: Encoding Rules & Formats

接下來可能會遇到各種檔案,這裡頭牽涉的標準主要有 2 類:
  1. Encoding rule: 說明內容要如何編碼,主要有 DER 與 PEM [2] 兩種,前者的輸出為 binary,後者為 base64。
  2. Message or file format: 定義檔案應該包括那些資訊,主要有 PFX, P12 (PKCS #12), CSR (PKCS #10), X.509
  • PFX 或 P12 (PKCS #12) [3]: 也就是 CA-signed certificate;這類檔案帶有 private key,一般會被保護起來,很少複製傳遞。
  • CSR (PKCS #10) [4]: Certificate Signing Request; 為 client 向 CA 要求簽發憑證時使用,帶自身站台資訊,最少使用。
  • X.509 [6] 或 P7C (PKCS #7) [7]: 最常見,用於存放 public certificate 或 signature,其中 P7C 預定為 BER encoding,因此不會與其它 encoding rule 併用。
File Name Extensions

上頭這些標準名稱都常見於副檔名,譬如說:
  •  *.pfx 與 *.p12 一定是 CA-signed certificate,未指定 encoding rule,但通常為 binary,可與 PEM 合併使用。
  •  *.p7c 一定是 public certificate,固定為 BER encoding。
  •  *.der 與 *.pem 通常僅為 public certificate,用副檔名描述編碼方式。
除此,*.cer 與 *.crt 也是常見的副檔名,大多也僅為 public certificate,可套用任何 encoding rule。

最後是這些 key 如何被保存到 client 與 server;對系統來說,存放位置分為 TrustStore 或 KeyStore,分別存放扮演 consumer 與 provider 時,使用的 public certificate 與 private keys。
  • 與一般 symmetric authentication 所稱的 certificate 一詞不同,symmetric authentication 中的 certificate 多半為 username/password,但這邊的 certificate 泛指 signature 或包含 private key。
  • 例如:CA-signed certificate 即為兩者組合,而 public certificate 則為 signature。所以當單純看到副檔名 *.crt 或 *.cer,並無法確定內容是否包括 private key,類似的情況也發生在 *.pem 的檔案。

ps. Java 的 KeyStore 副檔名通常為 *.jks,由於 KeyStore 用來存放 CA-signed certificate 的關係,有些系統便使用 Java KeyStore 檔取代 *.pfx 或 *.p12,作為匯入 CA-signed certificate 時使用。


see also:
[1] Public Key Infrastructure
[2] PEM (Privacy-enhanced Electronic Mail): Base64 encoded DER certificate
[3] PKCS #12: Personal Information Exchange Syntax Standard
[4] CSR (Certificate Signing Request)
[5] PKCS (Public Key Cryptography Standards)
[6] X.509
[7] PKCS #7: Cryptographic Message Syntax
[8] DER vs. CRT vs. CER vs. PEM Certificates and How To Convert Them


Salesforce RESTful Services: Publish

當使用 Salesforce 作為 API provider 時,預設採用 OAuth 2.0 作為認證機制,如果想簡化認證過程,則可透過將 API 發佈到公開站台的方式完成,此時需要針對 site profile 進行設定。
  • 首先要進到 site details page;在 Setup / Build / Develop / Sites,點選 Site Label 欄位 (不是 Edit 也不是 Site URL)。
  • Site Details page 上方有個 Public Access Settings button,點進去後可能會因為 Org 不同而出現不同畫面:
  • Personal Develop Org: 畫面為一個很長的表格,要設定的地方有:Enabled Apex Class Access, Field-Level Security 與 Object Permissions。
  • Production Sandbox Org: 分成 Apps 與 System 兩段,需設定的連結落在 Apps 裡頭,分別是 Apex Class Access 與 Object Settings。
設定的重點不外乎:service class 本身以及相關需要存取的物件欄位,因此只要分別在 Apex Class 與 Objects 中將相關權限打開即可。

see also:
[1] Public RESTful Web Services on Force.com Sites
[2] Public Access Settings for Force.com Sites
[3] Public Access Settings in Salesforce

Salesforce RESTful Services: Response Content-Type

Salesforce 實作 RESTful 時,不需額外的程式碼,系統會自動支援 2 種 response Content-Type:JSON 與 XML。

但由於回傳 XML 時,內部的 tag 名稱會代出自訂欄位與物件的名稱,並不存在於 W3C 預設 namespace 中,而 Salesforce 亦不會代為產生對應的 XML schema,因此對於某些 XML parser 來說會造成 parsing error,比較直覺的解法是繞過回傳 XML 改使用 JSON。

ps. 實際測試時,回傳的 XML 字串會設定 namespace 為 http://soap.sforce.com/schemas/class/ 開頭的 URL,但並未實際生成 XML schema。

前面說過 Salesforce 會自動支援 2 種 response Content-Type,切換的依據是根據 request header 中的 Accept 屬性。
  • 一般瀏覽器 (Chrome, Firefox, IE) 預設的 Accept Content-Type 為 text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8,此時 Salesforce 會優先取用 application/xml
  • 當未設定 Accept Content-Type,或指定 Accept Content-Type 為 application/json 或空白時,Salesforce 預設會以 JSON 作為 response Content-Type。
如果想直接在 server 端指定只支援 JSON,則必須將 method 的 return type 改為 void,並自行指定 HttpResponse,填入 JSON 字串。

RestContext.response.addHeader('Content-Type', 'application/json');
RestContext.response.responseBody = Blob.valueOf('{ "result": true}');

ps. 回傳的 charset 預設皆為 UTF-8,不需要額外設定 Accept-Charset。

see also:
[1] Apex REST Methods
[2] How can I return JSON object from REST service?

2017-10-06

Apex Describe Information

Salesforce 針對 SObject 與 field 分別提供相關的 describe information (a.k.a. metadata),有點類似 Java 中的 java.lang.reflect.Type 與 java.lang.reflect.Field;不過 Salesforce 進一部拆分成 token 與 describe result,前者為一個 compile-time generated & runtime immutable reference,沒有細部資料,但可用於比對 (==),而後者才是真正具備細部資料的結構。

  • Token 轉換至 describe result 的行為稱之為 describe,呼叫 getDescribe() method,可視為 dereference & data loading。
  • SObject 與 field 的 token 與 describe result 資料型別與相關取用方式分別為:
下圖為 4 種 describe information 的產生與轉換方式:
  • 除了 SObject 的 token 與 describe result 有機會從 instance 取得,其餘都必須在 Apex code 中指定 SObject 或 Field name 才能獲得。
  • 不只 SObject instance 有提供 getSObjectType() method,其它提供此 method 的還包括:SObject Describe Result, List 與 Map;其中 List 與 Map 呼叫 getSObjectType() 時等同於針對其 element instance 呼叫該 method。

see also:
[1] Apex Developer Guide: Schema Class
[2] Apex Developer Guide: Schame Namespace
[3] Apex Developer Guide: Dynamic Apex

2017-09-28

Salesforce Apex REST

REST 是目前實作 web API 時較為普遍的選擇,Salesforce 原生在 APEX 中也提供了這方面的支援,方便開發者自訂 API 作為系統整合使用。在 APEX 有以下 6 個 REST annotations:
  1. @RestResource
  2. @HttpDelete
  3. @HttpGet
  4. @HttpPatch
  5. @HttpPost
  6. @HttpPut
值得注意的是:APEX REST annotations 中只有 RestResource 才有 urlMapping 參數,且該 annotation 為 class level;換言之:
  • 每一個 service implementation class,針對各種 HTTP requests 類型只能有 1 個 method;不能同時宣告多個 @HttpGet methods 在 1 class 中;
  • Service implementation class 中的 class name 並不會浮現在 URL 中。
see also:
[1] Apex REST Annotations
[2] Creating REST APIs using Apex REST

Kayako Template & Language Engine

Kayako Fusion 是一套 on-premise helpdesk 系統,使用的語言為 PHP,framework 為自有的 SWIFT framework,在修改或整合上會需要一些功夫去瞭解系統架構,才不至於出現難以維護或資料不一致的現象。最近處理了一段有關多國語系的問題,這邊順便將心得記錄一下。

首先,Kayako 中提供了兩個元件來處理多國語系:Template Engine 與 Language Engine,分別負責處理文本的模版 (template) 跟各語言詞彙 (phrase),資料來源都同時支援 database 與 file system, 下圖說明這兩個元件的關係與 data flow。


值得注意的是:
  • Language Engine 透過一個 key-value array 記錄某個 phrase key 對應的字串,其中 phrase key 並不包括語言別資訊,因此一個 engine instance 只能處理一種語言,否則會出現 key conflicts 現象。
  • Template Engine 不涉及語言別的問題,因此系統可共用一個 template engine instance;反之 Language Engine 必須針對需要的產出的語言,生成對應的 instance。
在 Kayako 原本的邏輯中
  • 取用 Language Engine instance 的邏輯被放在 TemplateEngine->Get() 中,除了 Web 操作根據 cookie 在底層 SWIFT 生成不同語言的 instances 外,其餘都使用預設的 (English) Language Engine instance。
  • TemplateEngine->Get() 不帶 phrase keys 參數,Language Engine 負責載入所有的 phrases,交由 Dwoo core 產出最終文本。

因此如果我們要讓非 Web 操作也能產出多語言內容的話,有兩種選擇:
  1. 修改  TemplateEngine->Get(),新增語言別參數,動態產出對應的  Language Engine instance。
  2. 在呼叫  TemplateEngine->Get() 前預先產出所需的 phrases,並於呼叫時傳入。
這邊我採用第 2 種作法,目的在於降低 Language Engine 載入的 phrases 數量。

假設今天我要製作一個具備多國語系的畫面,除了預先將 template 跟 phrase 準備好存入 database 或 file system 外,主要的 rendering 邏輯如下:
  • 根據需要的語言別,初始化 Language Engine instance。
  • 根據 phrase keys 與 language id (or language code) 透過 Language Engine 載入 phrases。
  • 呼叫 Template Engine 中的 Get(),傳入 template key 與 phrases,生成 output content;其中 template engine 會在 SWIFT_Base 中初始化,如果已繼承 SWIFT MVC classes 或 SWIFT_Library,這邊可以直接取用,不需要另行初始化。

2017-09-21

Salesforce: More about SOQL

上一篇整理了 Salesforce 中各式 object relationships 與相關 SOQL patterns;這邊想多聊一些 SOQL 撰寫時要注意的小地方。

Lazy Fetch

SOQL 算是把 lazy fetch 發揮得淋漓盡致;首先,它不像 SQL,SOQL 中並不提供 SELECT * 的語句,所有需要的 fields 除了 ID 都必須完整列舉。其次,即使只 SELECT 一個 field,回傳型別仍為 List<T> where T extends sObject,以下面這個針對 Account 物件搜尋的 SOQL 為例:
SELECT Name FROM Account
回傳的型別是 List<Account> 而不是 List<String>。

Query from One-side
如同操作 relational database,Salesforce 也提供了一些介面方便使用 SOQL 查詢系統中的資料,無論是 Workbench 或 Eclipse 中的 schema view,都會將結果以表格 (tabular) 的方式呈現。這跟 SOQL 以 object-oriented 的操作思維有些衝突,尤其是針對 one-to-many relationships 查詢時。以下是從 one-side 出發使用 outer JOIN 後在 Workbench 中得到的錯誤訊息。
看來 Salesforce 在這塊並不像 report 介面提供表格中再展開子表的能力,直接給了 parent relationships queries are disabled 的錯誤訊息。

2017-09-19

Salesforce Object Relationships

Salesforce 中有關 objects relationships 對新手往往是比較難懂的,主要原因包括:
  1. Object modeling 時採用的 schema builder 看似 ER model,但實際使用 SOQL 操作時,卻是以物件導向的觀點。
  2. SOQL 沒有 JOIN,只能透過 relationship traversal with path expression 完成各式的 implicit JOIN。
  3. Object 中記錄 relationship 的 field naming,有獨特的 convention,有時使用 __c,有時卻要使用 __r。
  4. Relationship 的 cardinality 固定為 one-to-many;使用 SOQL traverse relationship 時,從 one side 或 many side 出發會需要截然不同的 SOQL statement patterns。
另外還有幾點對撰寫 SOQL 關係不大,但也容易造成困擾:
  1. 設定 relationships 可能分為兩類:lookup 與 master-detail (a.k.a. MD)。
  2. 以物件導向的角度,上述分別是 aggregation 與 composition;前者是 whole-part 的關係,後者有一致的生命週期。
  3. 文件中的 parent 與 child,並非專屬於 master-detail relationship,純粹分別用於 relationship 中的 one side 與 many side。
[1] 是目前看過的參考資料中,最為完整的一篇,建議可以從這篇開始入門;相對其他文件,此處的名詞定義叫為明確,範例也相對清楚。這邊特別說明了如果 SOQL 以 one side (parent) 作為起點時,需使用 nested SELECT:
  • Both relationships are defined from the many-to-1 side, namely from a child to a parent. Note that we have utilized a NESTED select to obtain the records of related children from the parent. This kind of expression is very powerful in obtaining related records traversing from the 1 side in a 1-m relationship.
  • It is a useful pattern to obtain related information on a parent and all its children via traversing path expressions with relationship fields.
另外,當 SOQL 以 one side 出發並使用 nested SELECT 時,回傳結果的數量並非 Cartesian product,而是以 nested object 的方式呈現,意即每一筆 parent object 中含有多筆 child objects。

下表整理了 SOQL 針對不同 side 作為起點,如何透過 relationship traversal 達到各式 JOIN 的 patterns:
Outermost FROM Clause
One Side Many Side
Inner JOIN 在 WHERE clause 中使用 nested SELECT
SELECT Name
FROM Position_c
WHERE Id IN (SELECT Position__c FROM Job_Application__c)
在 WHERE clause 中直接取用 relationship
SELECT Name,Position__r.Name,
FROM Job_Application__c
WHERE Position__r.Department__c = ‘Sales’
Outer JOIN 在 SELECT clause 中使用 nested SELECT
SELECT Name, (SELECT Name FROM Job_Applications__r)
FROM Position__c
在 SELECT clause 中直接取用 relationship
SELECT Name, Position__r.Department__c
FROM Job_Application__c
ps. 上述 SOQL 中,只有在取用 relevant object 的欄位時才需要使用 __r notation,若只需該物件 id,則用 __c 即可。

[2] 說明了定義 relationship 時的命名原則:
  • 在 parent object 中定義 relationship 時:名稱須 unique to the parent,並且使用複數形的 child object name 命名。
  • 在 child object 中定義 relationship 時:名稱須 unique to the child,並且使用單數形的 parent object name 命名。
[3] 提供了一個從 child object (Daughter) 定義 lookup relationship 的範例
  • 按前述第 (2) 項原則,relationship filed 的命名應使用單數的 parent object name,此處命名為 Mother_of_Child (應可簡化為 Mother)。
  • Child Relationship Name 則以 child object 為主,命名為 Daughters。
    => Lookup relationship 中才談 Child Relationship Name,master-detail relationship 無此欄位。
  • 從 child object 為起點實作 outer JOIN:SELECT Id, FirstName__c, Mother_of_Child__r.FirstName__c FROM Daughter__c WHERE Mother_of_Child__r.LastName__c LIKE 'C%'
  • 從 parent object 為起點實作 outer JOIN:SELECT LastName__c, (SELECT LastName__c FROM Daughters__r) FROM Mother__c
see also:
[1] A Deeper Look at SOQL and Relationship Queries on Force.com
[2] Understanding Relationship Names
[3] Understanding Relationship Names, Custom Objects, and Custom Fields

2017-07-20

MVC, ASP.NET, and Salesforce VisualPage

表面上寫了好一陣 web application,骨子裡更多是寫 backend, libraries 與一些整合性的 service,UI 這塊一直非常薄弱。一開始想學 Struts2,看了些書,但一直沒落實到開發上;後來因為 Activiti BPM 接觸到 Vaadin 6,大概知道 component-based MVC 的樣貌,之後又碰了些 ASP.NET WebForm 這種以 view 為主的架構,算是有些粗略印象,該是要好好釐清的時候。

傳統的 MVC 源自單機的圖形化程式,model, view, controller 都在同一個 runtime 上,彼此溝通無礙;model 變化時可以順利更新 view,controller 只要負責處理 request 與 response 就好,簡單來說就是源自 UI control 與 model 的 events。其中 model 更新 view 這段連結又稱為 data binding。

這件事到了 web application 世界中有了大挑戰。

在 web application 中,view 屬於 client code,由 rendering engine 負責生成,而 controller 與 model 為 server code,server 端無法隨時連接 client 作畫面更新;因此出現了 MVC Model2。MVC Model2 並沒有解決問題,只是因應限制,釐清與傳統 MVC 的不同,MVC Model2 中,model 不負責將結果更新到 view,改由 controller 負責,至於如何實現一時還沒有答案。

這個時期出現幾種作法,最直覺得就是在 view 上頭掛一段 JavaScript,定期刷新頁面,包括畫面跟資料。微軟為了比較好的使用者體驗,在 1999 率先提出 XMLHttpRequest (XHR) 的概念,也就是說 client 刷新不再以整個 view 為單位,可以只針對需要的資料取得相關的 XML 即可。整個標準化的過程一直到 2002/6 Mozilla Gecko 發佈 1.0 版、2004/2 Safari 1.2 發佈之後才真正落實。

2004-2005 這兩年迎來巨變。

Gmail 跟 Google Maps 相繼於 2004/4 與 2005/2 發表,大家也發現 XMLHttpRequest 可以搭配 client-side JavaScript asynchronous 的行為,讓 JavaScript 負責處理 XMLHttpResponse 就好,不但可以刷新部份頁面,也可以部在一開始就載入完整頁面,當使用者觸發後再進一步抓資料,且不影響既有畫面瀏覽。AJAX 正式登場,並作為 2004/11 Firefox 1.0 發表時的主打特點。

某程度上來說 AJAX 算是解決了部份 data binding 的問題,但主動權仍落在 client 端,並不是真的由 controller 更新 view,屬於單向 (one-way) data binding。即便如此,AJAX 也讓更多人把目光從 data binding 轉移到另一個重點,model 的複用,衍生 component-based MVC frameworks。

對於初入前端的人來說,最棘手的就是各瀏覽器相容性問題,一種解法是使用 cross-browser JavaScript UI libraries,另一種就是交由 MVC frameworks 產出 cross-browser UI components。Component-based MVC 就是後者的產物。

這邊的 component 指的是 UI component;例如一個下拉選單,或 textbox;通常會帶一些資料跟檢核機制。相對於 MVC,component 比較像是 model 在 view 中的呈現,卻又帶一點 controller 對資料檢核的邏輯,但其呈現的不是一整的 page 而是各別 model。使用 component-based MVC 有個很大的好處,前面說的 one-way data binding 都在 framework 中處理好了,不需要額外程式碼,開發人員只要專注如何組裝、複用這些 UI components 即可。

但也因為 components 大大削弱了原本 MVC Model2 中 controller 的角色,處理 request 與更新 view;因此這類 component-based MVC framework 標榜的 architectural pattern 往往不是 MVC Model2,而是 MVP 或 MVVM 等。

ASP.NET 中的 code behind 算不算是 controller?
先回頭看看 controller 的 3 項主要職責:(1) 處理 request;(2) 根據執行結果選擇不同的 view;(3) 更新 view 中 model 的值。從功能性來看,code behind 完全能達到上面三者的要求,那究竟怪在哪裡?

首先 code behind class 裡頭的 member fields 多半宣告在 ASPX/ASPC 中,而 ASPX/ASPC 又只能綁定一個 code behind class,導致兩者 1:1 的強耦合關係,微軟更因此給了個很妙的名稱,page controller;這樣的模糊命名讓原本寫 ASP.NET Web Forms 的開發人員,瞬間也可以宣稱自己在寫 MVC;只是沒有以 ASP.NET MVC 為基礎,真正的 ASP.NET MVC 用的是 front controller pattern。

除了耦合性議題,另一個 page controller 讓人覺得詭異的地方是:在針對執行結果導向對應的 view 時,page controller 往往使用 HttpRequest.Redirect,依賴瀏覽器處理 HTTP redirect 行為,而不是走 server-side internal routing。

所以這個問題若使用微軟模糊的回答,答案是 YES;但面對多數的 MVC framework 是完全對應不起來。

Salesforce VisualPage 的 controller
相對於 ASP.NET ASPX/ASPC 與 code behind 的結構,VisualPage 也有個類似 code behind 的 class,稱為 controller。比 code behind 稍微好一點,VisualPage 所有欄位的宣告被放在 controller,換言之,controller 可脫離 VisualPage 獨立測試。此外在預載欄位資料上,ASP.NET 透過 code behind 塞入資料,而 VisualPage 的 controller 只提供 getter,把塞資料的主動權交給 view 本身,跟 AJAX data binding 的作法一致。

至於連動選單的更新,VisualPage 的作法就完全按照 AJAX data binding,不需要重載整個頁面,由瀏覽器送出 XMLHttpRequest 後刷新 UI component 完成。

See also:
[1] XMLHttpRequest (wikipedia; Mozilla.org)
[2] AJAX Programming (wikipedia)
[3] Ajax: A New Approach to Web Applications
[4] GUI Architectures (Martin Fowler)
[5] MVC是一個巨大誤會
[6] MVVM (wikipedia)
[7] MVP (wikipedia)
[8] Webforms vs MVC and Why MVC is better?
[9] MVC-structured Visualforce Example

2017-05-02

Visual Studio Solution File and IIS Express Settings

使用 Visual Studio 開發 Web application 時,solution file (*.sln) 是主要的設定檔。Visual Studio 這邊一個設計不好的地方在於,sln 檔裡頭會記錄 IIS Express 使用的 port number。

傳統上一般開發好的 Web application 會透過手動複製或 web deploy 的方式佈署到執行環境上,但在開發測試時,實際佈署的執行環境是 Visual Studio 自帶的 IIS Express;如同一般的 IIS 一樣,IIS Express 也需要一個 port number 來作為 HTTP entry point,例如:http://localhost:54321。

應用程式跟 port number 的對應關係會記錄在 %USERPROFILE%\Documents\IISExpress\config\applicationhost.config 中,主要記錄 site name, physical path, and port number;一般當多人協同開發時,各自的 physical path 皆不相同,不會將 applicationhost.config commit 上 version control,而是交給開發人員各自管理。

麻煩的是帶有 port number 作為開發專案的重要設定必須 commit 到 version control,加上該設定又帶了 port number,間接限定了所有共同開發的人員對於相同的 solution 都必須手動維護 IIS Express applicationhost.config 設定,使用同一個 port number。

ps. Windows 預帶一些指向特定目錄的環境變數,這些變數在設定應用程式時經常用到,但卻在 系統內容/環境變數 中找不到,它們的正式名稱是 Constant Special item ID List (CSIDL),這邊的 %USERPROFILE% 就是常見的一個。

see also: CSIDL

2017-04-20

Headset 3.5mm Conncector Standards

現在的人因為智慧型手機汰換很快,手邊越來越多附贈的耳機跟充電線,這些耳機多半具備音量線控、來電接聽、跟麥克風的功能,跟傳統立體聲耳機不太一樣,雖然說很早就遇過插進一般電腦耳機孔左右聲道會有些問題,但也一直沒細究。查了一下才發現這裡頭還有不一樣的標準,雖然都是 3.5mm 接頭,卻又互不相容。

TRS 接頭
TRS 的全名是 tip-ring-sleeve [1],也就是傳統常見的立體聲耳機接頭,金屬接點分成三段,分別是尖端 (tip)、基部柱狀 (sleeve)、以及中段 (ring)。Tip 跟 ring 這兩個名稱源自古早手動接線的電話系統 [2],當時並沒有左右聲道,但後來用在耳機接頭時,分別作為左右聲道訊號使用;而 sleeve 則用於接地。至於更古早的單音耳機,因為只需要兩段金屬接點,因此使用的接頭是 TS 接頭。

目前常見智慧型手機附贈的耳機,接點分成四段,稱為 TRRS 接頭。就訊號來說,尖端兩個與 TRS 相同,靠基部這邊的 RS 則又分成 CTIA (又稱為 American Headset Jack; AHJ) [3] 跟 OMTP [4] 兩種標準,前者的 RS 分別為接地與麥克風,後者則剛好顛倒過來。因為聲音輸出時,訊號必須對應接地才能成功推動發聲元件,因此 CTIA 與 OMTP 間混用會因為找不到接地線,而無法正確發聲。

這兩個標準大致上老舊跟山寨的產品多使用 OMTP,像是 SONY ERICSSON 跟 Coolpad/OPPO/ZTE;而 CITA 則廣泛用在現在市面上多數手機,包括 Apple/ASUS/LG/Meizu/SONY/Xiaomi;至於 HTC/MOTO/NOKIA/SAMSUNG 則要看機型而定。

如果需要在電腦上使用手機附贈的耳機,可以購買轉接線完成。

See also:
[1] Phone Connector (Audio)
[2] Tip and Ring
[3] Cellular Telephone Industries Association (CITA)
[4] Open Mobile Terminal Platform (OMTP)

2017-04-11

Customer Relationship Management (CRM)

一般來說 CRM 系統會包括 3 個主要功能模組:行銷 (marketing)、業務 (sales)、客服 (customer support);可以直接對應到公司面對客戶時的主要三個階段。其中 CRM 的業務模組通常只提供報價 (quote) 功能,至於成交後的訂單 (order)、合約 (contract)、倉儲 (inventory)、發票 (invoice) 等管理功能,多半會回到 ERP 的模組中,而不在涵蓋範圍中。

這 3 個模組中,又以業務功能為 CRM 的最核心,CRM 的行銷功能往往專注於 target list、bugget planning,輔以更專業的系統負責 custom portal、multi- campaign channel management (newlettter, email, phone call, social networks)、閱讀追蹤、SEO 等功能;而客服系統也往往會拆分出 helpdesk 系統,提供更多案件 (case) 追蹤功能。
MarketingSalesCustomer Support
Accounts
Contacts
Targets
Campaigns
Leads
Opportunities
Quotes
Cases

上表是 CRM 中常見名詞跟 3 個功能模組的關聯對應,其中 campaigns, opportunities, cases 是分屬 3 個功能模組的活動紀錄,其他則為客戶資料。Accounts 跟 contacts 是相對中性的;targets 是蒐集到客戶資料後的第一階段,經 marketing/sales 資料審核後轉成 leads,視連繫狀況再轉為 prospects,等到報價確定成交成為訂單後,再轉為 customers,由客服模組負責。[1][2]

客戶數在各銷售階段的轉換過程中會逐漸縮小,呈漏斗形,故稱為 sales funnel。一般來說 sales funnel 以 leads 為起點 (更之前的 anonymous 資料未存於系統中),後續才是 prospects 跟 customers;在 Salesforce 跟 SuiteCRM 的設計中,lead 可轉換為 account,prospect 跟 customer 都被視為是一種 account type [3][4]。

Salesforce 預設的 account type 有 Analyst, Press, Competitor, Prospect, Customer, Reseller, Integrator, Investor, Partner, and Other [4].

部分專營 B2B CRM 的廠商會在 CRM 前再多一個 Marketing Automation Systems (MAS),負責蒐集潛在客戶資料,經過評分過濾成為 lead [5]。

see also:
[1] Customer Relationship Management: Aspects, Strategy and Use of Technology
[2] What is the Difference between Campaign, Opportunity, and Case?
[3] SuiteCRM User Guide
[4] What are the default picklist values in Salesforce?
[5] B2B Marketing Stack Basics: CRM, MAS, and The B2B Lead Lifecycle
[6] The True State of Open Source CRM
[7] 行銷與業務有何不同?這45件事讓你搞清楚


2017-04-07

Contract, Interface, and API Document

有一些程式經驗的人大多聽過 design by contract 這個詞,意思大致是按照 contract 實作;部份習慣於 object-oriented programming 的人很自然的就把 contract 誤以為等價於 interface,中了 interface-as-contract myth [1]。

Interface 跟 contract 最大的差別在於:interface 是純粹的介面,不涉及實作後的行為,也就是說按照 interface 實作的 client code,只確保能呼叫得到,不能確保呼叫後的行為是否符合預期;但是 contract 是包括 precondition 與 postcondition,比 interface 更進一步限制了實作行為。

但這也並不代表 contract 就能完全涵蓋 interface,主要因為 contract 通常並不涉及實作的語言;例如 interface 可以指定 method arguments 的順序跟型別,但這些通常不會涵蓋在 contract 裡頭。也因此當我們談 test code generation 時,interface 扮演主要角色,而 contract 則在 test case (input/output) generation 扮演主要角色。

最後是 API document ...
API document 主要目的是讓編寫 client code 的人能正確地產生參數呼叫 API,目的在於彌補 interface 的不足,一般會先解釋一下這個 API 的用途,加註各個參數 domain 上的意義而不單只是型別,另外也會描述拋出例外的情況等;因此就意義上偏向 contract 更多一點,但又比 contract 多依賴了特定 programming language 或 protocol 等;甚至可以包括所有 contract 的資訊形成 API contract。

近年來因為 microservice architecture 的大量興起,讓 Web API 又受到重視,許多 interface description language (IDL), API description language, API documentation language 也相繼產生,釐清上面三個的角色跟功能,可以幫助我們回頭檢視這些 description languages 應該有的功能跟後續自動化的可能。


see also:
[1] API Design Myth: Interface as Contract
[2] An API Definition As The Truth In The API Contract
[3] Design by Contract

2017-03-28

Tagging System Design

Tagging 也算是目前系統中常見的功能,實作上也被討論了不少,大致上分底下 3 種類型:
  1. Denormalized solution
    Pros:
    • 單一 table 易於直接人工閱讀資料與維護。
    Cons:
    • 所有 tag value 集中於一個 cell,需要保留一特殊字符作為分隔使用。
    • 查詢時將大量使用 LIKE,查詢條件字串組成受限;但效能上可能優於 JOIN [1]。
  2. Entity-Relation solution
    Pros:
    • 使用獨立的 table 專門紀錄 tag value 與 entity 對應的關係。
    • 個別 tag value 被拆分為獨立記錄,不需要保留特殊字符,也不會大量使用 LIKE 運算。
    Cons:
    • 必須嚴格確保每一個 entity id 與 tag value 組合只會存在一筆記錄;否則使用 HAVING-COUNT 查詢時會出現錯誤。
    • Tag 本身無法帶有其它屬性,例如:引用次數/日期、從屬關係、有效與否、與內部/顯示名稱等。
    • Tag 更名時需要更新大量資料。
  3. Intersection Table solution
    Pros:
    • 所有的 tag value 獨立一個 table 存放,易於統整 tag value 或更名。
    • Tag 本身被視為另一個獨立的 entity,可自由定義屬性。
Cascade Updates: 當使用 normalized solution 時,寫入時需要 cascade updates 往往是需特別注意的地方,但只要程式邏輯無誤,便不會產生 orphan relation/entity 的情形。

Tag vs. Category

  • Tag 是 many-to-many 的關係,category 是 one-to-many。
  • Tag 一般不實作階層式結構。階層式結構必須搭配 recursive search 才有效果,也就是以 parent tag 搜尋時,結果應包括與 child tag 關連的項目;但一般像 GMail 雖然表面上具備階層式的 label,實質上卻沒有 recursive search 的能力,屬於 一種 faux hierarchical tagging。

另外 [2] 提到 3 種常見的 tag 使用情境:(1) 使用 tag 搜尋;(2) 透過某一文件的 tags 尋找擁有類似 tags 的其他文章;(3) 透過一群 tags 搜尋其他相關 tags。

see also:
[1] Tags: Database schema
[2] In Lieu of the Promised Article on Tags and SQL

Windows Name Resolution

名稱解析 (name resolution) 雖然是存在已久的機制,但有時候還是會遇到一些因為實作差異而導致行為不同的地方。一般在 Windows 下常用的解析工具大概有 nslookup 跟 ping,撇除 single-label server name (a.k.a. Windows host name) 隸屬於 NetBIOS/WINS 的管轄範圍,當使用這兩個工具作一般網域名稱解析,需要注意的有:

  • Windows 內建 DNS Client 服務 (service name: Dnscache),會讀取系統網路介面 (network interface) 的 DNS 設定,負責 DNS cache。
  • 當變更時網路介面 DNS 設定,DNS Client 並不見得會立即套用新設定,cache 資料也可能不會 flush。
  • ping 解析時會先向 DNS Client 查詢,因此單獨變更 DNS 設定可能導致 ping 仍然讀取到舊資料;nslookup 不透過 DNS Client 因此會直接套用最新設定查詢。
  • 可以透過系統管理介面,重新啟動 DNS Client 服務,達到套用新設定與 flush cache 的目的;單獨 flush DNS cache 可使用指令 ipconfig /flushdns
  • 當 DNS Client 無該筆資料時,DNS Client 會根據 DNS 設定向外查詢,並將結果寫回 cache;nslookup 則無 cache 機制。
  • 在 nslookup shell 中變更 default server 只會暫時有效,並不會寫回系統。
  • 重啟網路介面時,亦會會重啟 DNS Client。
  • %Systemroot%\System32\Drivers\Etc\hosts 檔案為 DNS Client cache 預載內容。
see also: ping vs. nslookup

2017-03-10

Open Session in View

突然想起以前設定過 Struts2 中某個 filter,順手把之前的筆記再重新整理釐清。

Entity Manager and Transaction Manager
  • 簡單來說,entity manager 關係到資料讀取,而 transaction manager 關係到資料寫入。
  • 但兩者並非完全脫鉤,例如:在一個 transaction 中可能要參照其他 entity 的資料,或讀取 entity 時要避免 dirty data。
  • 因此 entity 要負責追蹤資料的狀態 (entity state),提供 transaction 中使用。

Open Session in View (OSIV)
  • Open Session in View 中的 session 指的是 Hibernate session
  • 用意在於避免 view rendering 的過程中,Hibernate session 過早結束導致後續 data lazy loading 產生 LazyInitializationExceptions
  • 換言之,Open Session in View 拉長 Hibernate session 的時間,並交由 view request 控制。
  • 值得注意的是:Hibernate session 結束與否不同等於 transaction commit or rollback;在純粹讀取資料的應用,操作是不需要 transaction 保護的,若是讀寫混合,Hibernate session 關閉的時機涉及 transaction 如何向 session factory 取得 Hibernate session,getCurrentSession() or openSession(),前者由 SessionFactory 自動控制,後者才需要手動在 commit or rollback 後關閉。

主張 OSIV 為 anti-pattern 的觀點集中於:
  • OSIV 忽略 readonly 與 read-write 操作本質上的不同,不應該都由 entity 決定一次存取的資料量。
  • 直接拉長 Hibernate session 給予底層實作傾向 lazy fetch 的空間,嚴重將導致 (n+1) select issue。
  • 精細一些的作法,應該由 view 決定底層應該一次 fetch 哪些欄位的資料,使用 DTO projection,而非單純由 entity 決定一次取回的欄位數量。
  • 但此種作法將提高 view 與底層 DTO 的耦合;傳統 view 只依賴 domain model,DTO 與 view 之間互不依賴。
  • 注意:這邊指的一次存取資料量,與分頁查詢無關,分頁查詢的 query 會帶有 order 與起始/結束的 row number。
Hibernate Session and Entity Manager
  • Entity Manager 是 JPA 定義的名詞,而 Hibernate session 及 SessionFactory 則是 Hibernate 特有。
  • 一般起始的順序是:transaction manger, entity manager, Hibernate session,透過 entity manager 控制 session,避免手動控制。
see also:

2017-01-26

Data Backup and Redundancy

接觸 IT 之後才發現很多人對 redundancy 跟 backup 之間的分野是混淆的,這種疑問就像前幾年 high availability 很熱門的時候,不少人都認為都已經有 data redundancy 了,還需要作備份嗎?

這種疑問通常忽略了萬一系統寫入異常或中毒的狀況,單純的 data redundancy 並沒辦法取回之前的資料狀態。

這篇文章寫的還不錯,提出了幾點:

  • Data redundancy and backups are both intended to prevent data loss, but the two technologies are quite different.
  • Data redundancy often takes the form of a synchronized copy of the organization’s data.
  • Data redundancy can help to prevent service outages.
  • Backups, on the other hand, are copies of data and other resources.
  • Backups are also a good choice for granular recovery (recovering a single file, email message and so on). In contrast, redundant systems are better suited to situations in which the organization needs to keep critical systems online and cannot tolerate a long recovery period.
相關於 backup 與 redundancy 的字眼分別有:
  • Backup: recovery, point-in-time, versioning, mean time to recovery (MTTR), revert, offline
  • Redundancy: failover, hot spare, online synchronizing, performance, reliability
Availability 是從使用者的角度出發,無論系統是忙碌或是失效,對使用者都是 not availability,所以 availability 這字眼會同時涵蓋 backup 與 redundancy。