Thursday, August 30, 2018

使用 Node.js 的 Sequelize 如何看到自動產生的 SQL 語句

解法: 在初始化的時候,傳入一個函數給 options.logging

var sequelize = new Sequelize('database', 'username', 'password', {
    logging: console.log
    logging: function (str) {
        // do your own logging
    }
});

如果原本的 legacy code 的深處使用了 Sequelize.js ,但是,又沒有留下接口可以從高階模組去設置 options.logging 。由於 Node.js 是動態語言,在 testing/development  的時候,可以考慮直接去修改 node_modules/ 資料夾裡的內容來啟用 logging 選項。

Tuesday, August 28, 2018

準備 DB integration test

本來我的作法通常是這樣子:

  • CREATE TABLE new_table LIKE old_table;
  • INSERT new_table SELECT * FROM old_table;
先透過上述的指令,將資料表的子集合資料做成新的資料表,然後再透過 MySQL Workbench 將新的資料表匯出。最後再刪掉暫時存在的資料表。

==============
最近改用了新的方式
1. 對原始的資料表,透過 MySQL Workbench 只匯出 schema ,做成一個匯入的 SQL script
2. 透過 MySQL Workbench 下 query ,query result 只能做成 CSV 匯出。但是,可以透過其它的線上工具,將 CSV file 轉檔成為 SQL data file 

integration test / Export Database Data to SQL insert statement

寫 integration test 的時候,有時候會需要透過 SQL query 去抓取部分的 table ,然後再塞入 testing 資料庫裡頭。這件事,透過 MySQL Workbench 來做的話,它可以很快地 export result set as CSV file 。但是,變成了 CSV file 雖然可以透過 LOAD 指令來匯入,更一般的指令,應該還是 mysql insert 。

那要如何將 data CSV file 做成 mysql insert statement 呢?

(1) CodeBeautify 圖形化介面
(2) csvsql command line 指令

Friday, August 24, 2018

OO design for testability (Miško Hevery) --- 心得(續)

這兩天重看了一篇 OO design for testability (Miško Hevery) 的前 15 分鐘,又有新的領悟。

本來我覺得整個 talk 的重點是在於四個常見的錯誤不要犯,重新讀一次之後,我覺得重點是更抽象的設計原則:

設計 OO 的程式的時候,要把 class 分成兩大類:
(1) 一類是 Object Graph Contruction & Lookup ,比方 Factory ,它會有 new  operator。
(2) 一類是 Business Logic ,它會有 conditionals 和 loop 

依照這種方式設計,如果要測試 Business Logic 的時候,就可以透過 test unit ,重新 wire 一些 fake 的物件去測試 Business Logic 。

而如果要測試 Factory object ,也可以獨立於 business logic 來做測試。



可以測試的程式應該要有性質是:你可以單單只依賴整套軟體組裝起來的方式,就控制軟體的控制流 (You can control the path flow of your software purely by the way the application is wired together.)

於是又提到了一個專有名詞 Seam
A seam is a place where you can alter behavior in your program without editing it in that place.

跟 Seam 相關的另一個詞是 enabling point
Every seam has an enabling point, a place where your can make a decision to use one behavior or another.

Object Seam


附帶一提,我後來針對 seam 去查資料,發現 seam 出自一本書 WELC。在 Working Effectively with Legacy Code 裡,除了作者最建議的 Object seam 之外,作者還介紹了其它兩種 seam:
preprocessing seam 和 link seam 。

object seam 的 enabling point 是「我們生成待測物件的位置

link seam 在 Java 可以透過 classpath 環境變數做為 enabling point 。原文對 link seam 的描述:
The enabling point for a link seam is always outside the program text. Sometimes it is in a build or a deployment script. This makes the use of link seams somewhat hard to notice.

preprocessing seam 的 enabling point 則是 preprocessor define 語法

參考資料:
(1) Testing Effectively With Legacy Code
(2) How to use link seams in CommonJS
(3) Seams in Javascript







Friday, August 3, 2018

Conditionals v.s. Rules Engine

在 Rich Hickey 的 Simple Made Easy 這個演說中,他講了幾句有關「條件判斷」的話:

  • Conditionals are complex in interesting ways, and rules can be simpler. 
  • You can get rule systems in libraries, or you can use languages like Prolog.


首先,我本來考慮要直接研究 Prolog ,不過,Prolog 並沒有很容易學,畢竟是個不同 paradigm 的程式語言。但是查了一陣子 Prolog 的資料之後,也有想出一些有趣的東西:

比方說,Prolog 這種語言的核心是 unification,除了可以實現 rules engine 之外,還可以用於 data validation 和 data extraction, data transformation 。 想了一想之後,我想到了一件事:寫 validation 時,使用的 JSON Schema ,其實也可以視為是 rule 。

後來,我從 rules engine 的角度去查資料,這回查到的 library 就很有啟發。 json-rules-engine ,就可以讓 nodejs 可以使用類似 Prolog 的能力。

仔細想想,本來直接寫在 if 裡頭的條件判斷 (conditionals) ,被用 rules engine 這種「增加一層抽象層」的方式改寫,程式就會變得更有彈性 (flexibility) 。這又是再一次地應証了那句老話:

You can solve every problem with another level of indirection

Thursday, August 2, 2018

Clean Micro-service Architecture

Clean Micro-service Architecture 又是 Robert C. Martin 的文章。

大意如下:

Clean Architecture 與 microservices 是兩個正交的概念

  • Clean Architecture 是指軟體系統的設計要可以清楚地區分成 entity, use cases, controller & gateway & presenter 這三個圈圈。之後才可以不斷地配合需求而修改。
  • microservices 是一種 deployment option (布署選項) ,而且這個布署的選項有它的 trade-off 。此外,還有其它的布署選項。使用不同的布署選項,是為了達成不同的 scalability  。

其它相關的布署選項還有:

  • 在同一個 thread 裡的 jar ,彼此透過函數來溝通。 
  • 在同一個 process 裡的 thread ,彼此透過 mailbox 或是 queue 溝通
  • 在同一台 machine 裡的不同 process ,彼此透過 socket 溝通
  • 在 Internet 上不同的 services ,彼此透過 asynchronous API 溝通
引述文章裡的一小段話 --- The deployment model is a detail. Micro-services are deployment option, not an architecture. 作者主張:「開發軟體系統,要視實際的需求,再來決定要用哪一種合適的布署選項。」

注意: jar 也可以達成 independent deployability (可獨立布署性) 和 hot swapping (程式碼熱抽換。) 所以也是相關的 deployment option


Sunday, July 29, 2018

Thinking in Data (Stuart Sierra)

Thinking in Data

(*) Data Oriented Programming
 
總結: 
(a) 寫程式解真實世界的問題,要用 Data 來對真實世界建模。
(b) 如果要驗証程式的正確性,將程式內部的 Data 加以「視覺化」(visualize) ,常常會比 unit test 還管用。
(c) Make your data visible!


1 用 Data 來對真實的世界建模。
  => 容易除錯。
  => 比物件好太多了

2. 物件 (Object) 適合用來建模的東西,往往是完全在電腦內部的東西。
   stack, queue, GUI, widgit 
   註: Clojure 的 vector, list, set, hash-map ,就是使用 Object 來建模。Why? 因為是電腦內部的東西,一方面效能很重要、另一方面需要 Polymorphism

3. Examine Each Step
   對有使用 reduce 函數來除錯的技巧
   (a) 把 reduce 換成 reductions ,如此就可以看到運算過程的每一個 step
   (b) 使用 pprint

4. Everything is a Map
   一種抽象思考:
   Collections
   This ..  | maps from ... | to ...
   ---------------------------------
   Map     | Keys              | Values
   Set       | Values            | Values
   Vector  | Integers         | Values
   List      | first/rest         | Values/List
 
5. Add Keys with Impunity

(assoc stuart :business/role :developer)
 
用 map 來對真實世界的東西建構,隨時可以加入新的東西。
如果要避免 key 的 collision ,可以對 key 加上 namespace

6. 一個函數的 argument 超過三個的時候
   => 用 map 來把 arugment 包裝起來是更好的作法。

   (defn full-name [person]
     (let [{:keys [first-name last-name]} person]
       (str first-name " " last-name)))

7. defrecord, defprotocol 這些通常是用來處理需要高效率或是 Polymorphism 處理的情況、
   然而,十之八九,用 map 來建模就已經很夠用了。

8. 封裝(Encapulation)實作細節這個技巧,被應用的時候,我們會無法看到 data representation
   然而,將 representation 隱藏起來是有代價的。
   (a) 效能
   用同一個介面去輕易地使用不同的物件,會有不同的效能屬性,有時被誤用,很傷效能。
   (b) 實作的難度
   (c) 「網路傳輸/程式之間的傳輸」仍然依賴於 representation
   也因此,任何我們透過隱藏 data representation 得到的好處,往往會被扺消,一旦我們的程式會接觸到外部世界。
 
   What I suggest, instead of encapsulating your representation, let your representation be visible and think about them. Choose the representation that best fit a particular module, a program, or a component that your are dealing with. Then, you establish the boundary between the components, where representation can change. We change representation all the time. That is what computer can do more than anything else.

 
9. Data Literals feature
   (*) Isolate literal representation from what's in memory
   不同的記憶體格式,可以共用同一種「printed data representation」

   (java.util.Date.)
   ;;=> #inst "2012-03-03T22:50:21.656-00:00"
   (java.util.GregorianCalendar)
   ;;=> #inst "2012-03-03T22:50:21.656-00:00"

(*) 應用 data representation 的技巧來撰寫函數

1. Isolate Components
對一個 state 要經過多次的轉換,但是每一次的轉換,只修改它的一部分狀態,可以用下列的 pattern 來實作。

(defn complex-process [state]
  (-> state
    (update-in [:part-a] subprocess-a)
    (update-in [:part-b] subprocess-b)))


2. Isolate side effects
下列的程式很難測試
;; Bad
(defn complex-process [state]
  (let [result (computation state)]
    (if (condition? result)
      (launch-missile)
      (erase-hard-drive))))


相對比較容易測試的版本
;; Better
(defn complex-process [state]
  (assoc state :analysis (computation state)))

(defn decision [state]
  (assoc state :response
         (if (condition? (:analysis state))
            :launch-missile
            :erase-hard-drive)))

(defn defend-nation [state]
  (case (:response state)
    :launch-missile (launch-missile)
    :erase-hard-drive (erase-hard-drive)))


3. 由 data 思維,推導出 naming convention
A function which...       |  has a name that is...   |  return...
-----------------------------------------------------------------
Computes a value         |  noun: what it returns  |  value
-----------------------------------------------------------------
Create a new thing        |  noun: what it creates  |  value
-----------------------------------------------------------------
Gets input from ouside |  "get-noun"                  |   value
-----------------------------------------------------------------
Affects outside              |  verb: what it does       |  nil
-------------------------------------------------------------------

(*) Summary
(a) Make data visible
(b) Make functions composable
(c) Isolate computation decisions from side effects
(d) Establish boundaries
    - from computation world to side effect world
    - from one data representation to different data representation