2015年9月21日

Scala Puzzlers: Init You Init Me

Scala Puzzlers 是收集很多人貢獻的 scala 的題目,類似 Java Programmer 認證的考題,主要是確認 Programmer 是不是有了解 scala 語言的規格與特性。

Init You, Init Me 的問題是以下的 scala statements 會列印出什麼結果?

object XY {
  object X {
    val value: Int = Y.value + 1
  }
  object Y {
    val value: Int = X.value + 1
  }
}

println(if (math.random > 0.5) XY.X.value else XY.Y.value)

答案是 2

lazy, strict

首先我們要知道 scala 的保留字: lazy,當某個變數宣告為 lazy 時,則表示 scala 會等到要存取該變數時,才會初始化這個變數。跟 lazy 相反的是 strict,strict 表示會在定義時,就馬上將該變數初始化,scala 的 collection 中就有區分 lazy 與 strict 的兩種版本,因大量的 collection elements,並不一定會在定義時,就馬上需要使用到所有的 elements,因此不需要直接將 collection 所有元素初始化,等到需要使用時再處理。

如果以 recursive 的方式定義變數,一開始會發現 REPL 警告要定義資料型別。

scala> val x = y; val y = x
<console>:15: error: recursive value y needs type
       val x = y; val y = x
               ^

當我們將 y 的資料型別,明確地定義出來,compiler 就接受我們宣告的 x 與 y 的定義,另外出現一個警告,告訴我們有個 statement 參考到了沒有定義初始值的 y,然後compiler 就自動將 y 的數值視為 0,所以將 x 也設為 0。

scala> val x: Int = y; val y = x
<console>:12: warning: Reference to uninitialized value y
       val x: Int = y; val y = x
                    ^
x: Int = 0
y: Int = 0

如果加上 lazy 的宣告,則 x 與 y 並不會在宣告時,就被初始化。

scala> lazy val x: Int = y; lazy val y = x
x: Int = <lazy>
y: Int = <lazy>

比較合理的使用方式,應該是在宣告 x 時,宣告為 lazy,但是讓 y 直接初始化,因為一開始可能不知道 y 的數值,後來知道了 y 時,再給予

scala> lazy val x: Int = y; val y=x; println("x="+x+", y="+y)
x=0, y=0
x: Int = <lazy>
y: Int = 0

另外因為 scala 規格中提到 "the value defined by an object definition is instantiated lazily",在 object 中定義的變數,scala 會自動以 lazy 的方式初始化。

結果不一定永遠是 2

因為題目只有運算過一次以下這個 statement

println(if (math.random > 0.5) XY.X.value else XY.Y.value)

如果我們多呼叫幾次,會發現結果也有可能會變成 1

scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
2

scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
1

scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
1

分析題目

把原本的題目 math.random 的部份用變數紀錄起來,會比較清楚處理的過程。

object XY {
  object X {
    val value: Int = Y.value + 1
  }
  object Y {
    val value: Int = X.value + 1
  }
}

val randomnumber=math.random
println( if (randomnumber > 0.5) XY.X.value else XY.Y.value )

當我們在 REPL 執行這些 statement時,可查看到結果為 2,在以下這個情況下,XY.Y.value 為 2 XY.X.value 為1,因為在使用 XY.Y.value 的時候,發現參考到 X.value,但 X 也還沒初始化,發現參考到 Y.value,這時發現 recursive 的狀況,就直接將 Y.value 視為 Integer 的初始值 0,接下來 XY.X.value 就等於 1,而 XY.Y.value 則為 2。

scala>  object XY {
     |   object X {
     |     val value: Int = Y.value + 1
     |   }
     |   object Y {
     |     val value: Int = X.value + 1
     |   }
     | }
defined object XY

scala> val randomnumber=math.random
randomnumber: Double = 0.24469087654673705

scala> println( if (randomnumber > 0.5) XY.X.value else XY.Y.value )
2

我們必須在 REPL 先執行 :reset,然後再運算一次這些 statements,結果才會永遠是 2,在以下這個情況下,XY.X.value 為 2 XY.Y.value 為1。

scala> :reset
Resetting interpreter state.
Forgetting this session history:

scala> object XY {
     |   object X {
     |     val value: Int = Y.value + 1
     |   }
     |   object Y {
     |     val value: Int = X.value + 1
     |   }
     | }
defined object XY

scala> val randomnumber=math.random
randomnumber: Double = 0.7747791382247455

scala> println( if (randomnumber > 0.5) XY.X.value else XY.Y.value )

因為以上兩個狀況都有可能會發生,我們也可以知道,因為 XY.X.value 跟 XY.Y.value 兩個數值,其中一個是 1 另一個是 2,我們多呼叫幾次,就會發現結果也有可能會變成 1。

scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
2

scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
1

2015年9月14日

Rest API documentation: Swagger, RAML, API Blueprint

以往在製作網路服務 web service 時,通常會率先想到 WSDL,但在 REST 以及輕量化網路傳輸的概念普及後,大家已經逐漸放棄 XML 這個冗長的描述文件格式,轉向使用 JSON,並用 HTTP REST 的方式提供網路服務 API。至於 API 文件規範,目前比較常見的有 Swagger, RAML, API Blueprint 三種,三種規範使用的技術背景不同,呈現出來的文件樣式差異也很大。

REST API

WSDL 一開始就是從規格開始做起,大家習慣 WSDL 的實作,必須要先定義 WSDL 描述檔,然後透過不同語言的 server 及 stub 的程式產生工具,產生對應的程式碼,在分別去實做 server 與 client side 的程式。

然而 REST 的概念是由技術概念開始推廣的,技術上強調我們平常都只有使用到 HTTP 規範中的 GET 與 POST method,但其實還有 PUT DELETE,這四個 http method 正好可以對應到一個資源的 CRUD operation,再加上 JSON 這個簡潔的資源表示方式的流行,這讓大家接受 REST API,而 WSDL 逐漸被淘汰。

REST 的規格,其實就是 HTTP,因此 REST API 就沒有什麼特別的規範,大家也都是直接撰寫 API,至於文件的部份,每一個人都有自己的文件規格與寫法,這跟 WSDL 的實做方式完全不同。

換句話說,想到 WSDL 就會想到要先定義 API 的規格,才能繼續往下工作,而想到 REST API,就會直覺想到 agile,反正程式先寫了再說。

Rest API Spec

API 是要給其他程式設計師使用的,因此就必須要有對應的文件,描述 API 的內容、用途,並提供範例。在沒有 WSDL Spec 的共通標準之下,後來才逐漸有了標準化 API 的想法,Swagger 是目前討論最多、也發展最久的 API Spec,對應可以使用的工具也很多。

Another API-Blueprint, RAML and Swagger Comparison 提供了Swagger, RAML, API Blueprint 三種 API Spec 的比較表,以下節錄一小部份的內容。

Specs API Blueprint RAML Swagger
Format Markdown YAML JSON
License MIT ASL 2.0 ASL 2.0
Version 1A8 0.8 1.2
Support Apiary Mulesoft Reverb
Initial Commit 2013/4 2013/9 2011/7
Web Site https://apiblueprint.org http://raml.org http://swagger.io/
API design approach top-down top-down bottom-up

Hello World Product API With Blueprint, RAML And Swagger 這篇文章以實際的例子告訴大家,這三種規格的差異在哪裡,API Blurprint 以 Markdown 語法撰寫主要是用來閱讀的,swagger 很明顯地是想要取代 WSDL,希望大家能夠先撰寫 swagger API 定義,然後用工具產生程式碼,而 RAML 比較想取得中庸的位置,既有閱讀性,又能夠讓程式閱讀,產生一些對應的工具。

API Blueprint

# Group Product
Resources for working with a single product.

## Product [/products/{id}]
Provides access to a single product.

+ Parameters
    + id (string) ... ID of the product

+ Model (application/json)

    JSON representation of products.

    + Body

            {
            "id": "1",
            "name": "Product One",
            "description": "This is the full description of the product.",
            "url": "http://example.com",
            "image": "http://example.com/image.jpg"
            }

RAML

#%RAML 0.8
---
#===============================================================
#  Products API - RAML example
#  References:
#    - RAML Specification - http://raml.org/spec.html
#    - RAML Projects - http://raml.org/projects.html
#    - RAML Tools  - http://www.apihub.com/raml-tools
#===============================================================
title: Products API
version: v0.1
baseUri: 
#List of media type to support
mediaType:  application/json
#List of protocols to support for baseUri
protocols: [ HTTP, HTTPS ]

#===============================================================
#  API documentation
#===============================================================
documentation:
  - title: Home
    content: | #This is a prototype product API.
  - title: Getting Started
    content: TODO   

#===============================================================
# API resource definitions
#===============================================================                        

/products:
  uriParameters:
  displayName: Products
  description: A collection of products
  post:
    description: Create a product
    #Post body media type support
    #text/xml: !!null  # media type text, xml support
    #application/json: !!null  #media type json support
    body:
      application/json:
        schema: |
          {
            "$schema": "http://json-schema.org/draft-03/schema",
            "product": {
                "name": {
                    "required": true,
                    "type": "string"
                },
                "description": {
                    "required": true,
                    "type": "string"
                },                
                "url": {
                    "required": true,
                    "type": "string"
                },                
                "image": {
                    "required": true,
                    "type": "string"
                }
            },
            "required": true,
            "type": "object"
          }        
        example: |
          {
            "product": {
              "id": "1",
              "name": "Product One",
              "description": "This is the full description of the product.",
              "url": "http://example.com",
              "image": "http://example.com/image.jpg"
            }
          }
  get:
    description: Get a list of products
    queryParameters:
      q:
        description: Search phrase to look for products
        type: string
        required: false
    responses:
      200:
        body:
          application/json: 
            #example: !include schema/product-list.json

  #---------------------------------------------------------------
  # Nested resource representing a  single product - name parameter as part of the path.
  #---------------------------------------------------------------
  /{productId}: 
    description: | # Retrieve a specific product using its ID.
    uriParameters:
     productId:
       displayName: Product ID
       type: integer    
    get:
      description: Get a single product
      queryParameters:
        productId:
          description: The ID of the product
          type: integer
          required: true
      responses:
        200:
          body:
            application/json: 
              #example: !include schema/product-list.json

swagger

{
    "apiVersion": "1.0",
    "swaggerVersion": "1.2",
    "basePath": "",
    "resourcePath": "/products",
    "produces": [
        "application/json"
    ],
    "apis": [
        {
            "path": "/products/",
            "operations": [
                {
                    "method": "GET",
                    "summary": "Pulls a listing of products",
                    "notes": "Returns a list of products, allowing you to filter by keyword query.",
                    "nickname": "getJobs",
                    "type": "products",
                    "parameters": [
                        {
                            "name": "query",
                            "description": "a text query to search across products",
                            "required": false,
                            "allowMultiple": false,
                            "dataType": "string",
                            "paramType": "query"
                        }
                    ],
                    "produces": [
                        "application/json"
                    ],
                    "responseMessages": [
                        {
                            "code": 404,
                            "message": "No Jobs To Return"
                        }
                    ]
                }
            ]
        },
        {
            "path": "/products/{id}",
            "operations": [
                {
                    "method": "GET",
                    "summary": "Retrieve an product using its ID",
                    "notes": "Returns a products detail",
                    "type": "people",
                    "nickname": "getJobs",
                    "produces": [
                        "application/json"
                    ],
                    "parameters": [
                        {
                            "name": "id",
                            "description": "id for the video, notice this is in the path, not a query variable",
                            "required": false,
                            "allowMultiple": false,
                            "dataType": "integer",
                            "paramType": "path"
                        }
                    ],
                    "responseMessages": [
                        {
                            "code": 400,
                            "message": "Invalid Application Information ID supplied"
                        },
                        {
                            "code": 404,
                            "message": "People not found"
                        }
                    ]
                }
            ]
        }
    ],
    "models": {
        "products": {
            "id": "products",
            "properties": {
                "id": {
                    "type": "integer"
                },              
                "name": {
                    "type": "string"
                },
                "description": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                },
                "video": {
                    "type": "string"
                },
                "thumbnailUrl": {
                    "type": "string"
                },
                "keywords": {
                    "type": "string"
                },
                "inLanguage": {
                    "type": "string"
                },
                "creator": {
                    "type": "string"
                }
            }
        }
    }
}

用途決定使用方法

不同的文件規範,加上支援的工具多寡,可以決定這份規格的影響範圍,也就等於告訴我們該選擇那一種規格。

swgger 是目前討論最多的,原因在於這是最早推出的規格,有 UI, editor, SDK generator 等等完整的工具可以使用,但是因為這是使用 JSON 的格式,脫離了工具的優勢,文件本身的可讀性就會差了一些,畢竟這是要給程式碼閱讀的一種規格。

另外 swagger 的 bottom-up 開發方式,也比較不那麼直覺,一般人在描述事情時,會習慣先從大維度的方向說起,然後再一步一步說明細節,而 swagger 為了減少文件 parsing 的次數,必須以 bottom-up 的方式描述規格的內容。

因此 RAML 與 API Blueprint 都是用 top-down 的方法,而且選擇了比較適合直接閱讀的文件格式。

不遵循規格也不會有問題

規格是為了互通以及共通的工具而定義出來的,REST API 並不像是 WSDL,缺少了 API 的定義文件,就沒辦法繼續進行程式開發,很多時候都是以先做出來為出發點,這時候也不會考慮什麼文件的規格,直接用文字檔案的方式,直接寫一寫 API 簡述,就可以馬上進入 coding。

在資料互動與專案成員之間的溝通方式中,如果已經有了既有的習慣跟方法,那麼沿用自己團隊的方法也沒關係,但如果我們需要對外發布 API Spec,或許就需要選擇一種規格與方法,讓團隊以外的人,能遵循該規格的處理方式,以最舒服且方便的方式,取得文件的說明,甚至是直接產生 API 的 stub code。

2015年9月7日

The Reactive Manifesto 響應式宣言

作者 Matin Thompson 在 InfoQ 談到 為什麼要有 Reactive Manifesto 2.0 ,對於我們來說,透過這樣的宣言,讓我們意識到系統架構以及程式語言,必須因應時代的更迭,提出新的架構與設計,也可以說是一種 Reactive System/Architecture。

The Reactive Manifesto 響應式宣言

The Reactive Manifesto, September 16 2014 (v2.0)

響應式宣言 中文版

現在的系統以大量的 muticore 處理器處理,使用者預期的是數毫秒的反應時間,以及永不中斷的線上服務,資料量達到 PB 的規模。我們需要一個協調一致的 Reactive System,這樣的系統有 flexible 彈性、lossely-coupled 鬆耦合與 scalable 可擴展性的表現,並具有以下四個特性:

  1. Responsive 響應式
    系統在任何情況下,都能及時響應,在可靠的時間內提供訊息且有穩定品質的服務。

  2. Resillient 韌性、堅固
    系統在失敗時,仍然保有 Responsive 特性。這是由 replication 複製, containment 容忍, isolation 隔離以及 delegation 委託的方式達成的。系統的任何組件發生問題時,可以隔離此部份,並獨立恢復,過程中不會影響整個系統。

  3. Elastic 彈性
    系統能即時量測效能,可透過演算法增加或減少服務的資源分配,滿足不同工作負載量的系統要求。

  4. Message Driven 訊息驅動
    組件之間透過非同步訊息傳遞方式互相溝通,用以確保 loose coupling 鬆耦合, isolation 隔離, location transparency 與區域無關的特性。還能提供錯誤訊息的委託處理機制。

由圖片可得知,這四個特性是以 Message Driven 為基礎,並以 Responsive 為終極目標,Elastic 跟 Resilient 像是天平的兩端,需要架構師權衡架構,在提供更多彈性的系統要求下,還能讓系統保持穩定。

Enterprise Application Integration Patterns: Messaging Patterns

Messaging Patterns Overview

因為企業內部有很多樣化的資訊系統,但通常系統之間會缺乏資料傳遞與整合的溝通管道,就算有了傳遞的管道,每一個系統的效能瓶頸以及運算時間不同,也會影響到應用整體的表現。

EAI (Enterprise Application Intergartion) Patterns 專門討論在企業內部該用什麼方法來整合不同的資訊系統,而其中所有 Pattern 的核心就是使用 Message Queue 系統。

Message Queue 的用意在於協調兩端的處理速度,道理就像是健康檢查,檢查時會區分不同的專業檢查診間,每個診間的處理速度不同,因此會有不同長度的排隊序列,每一個參加健康檢查的人,最終的目的就是要到最後一關醫生問診的地方,結束整個健康檢查的流程。

Reactive System 跟 EAI 的背後的概念是相同的,就是要將整個 end-to-end 的需求,依照不同的應用特性切割成特定的應用模組,然後在模組之間,以 MQ 來協調並同步處理的結果,然後一次將結果輸出給前端使用者。

Message Queue 的應用已經不單只是以獨立的 MQ System 存在,現在的趨勢,也有內建在程式語言中的趨勢,過去熱門的程式語言都是以 Multithread 以及共享記憶體的方式,進行多工處理,現在的趨勢也轉換成 Actor Concurrency Model,以訊息佇列來區隔不同的內部模組,以因應不同模組的處理速度。

設計 -> 切割 + 分析 -> 實作

京東技術架構(一)構建億級前端讀服務

京東技術架構(二)構建需求響應式億級商品詳情頁

這兩篇文章,可看成 Reactive System Architecture 的一個實際例子,京東商城 是中國一家 B2C 的購物網站,就跟一般購物的網站一樣,它必須因應中國的節日產生的大量購物流量,不斷地更新自己的系統架構,達到 Reactive System 的基本要求:反應速度快,系統不斷線,客戶是不會等人的。

從文章中可以看到,他們的整個商品頁面先有設計之後,再將頁面的功能,進行分析並切割成多個部份,根據商業應用的要求,決定該怎麼實作。

系統實作的過程,歷經了三個版本的更新,一樣是以 MQ 為核心,將頁面的工作拆散後,丟給多個商業邏輯集群,在最後進行頁面整合,丟給前端的使用者查看頁面。