搜尋此網誌

2019/03/11

Restfull Web API

Restfull Web API

Restfull Web API最明顯的特徵就是用唯一的 uri 識別資源的類型與個體。不同的 HTTP method 表達對資源的不同操作,差不多對應到 CRUD。基本上就是叫人們不要使用一堆亂七八糟的路徑或查詢參數傳遞資料或操作類型😂

這個道理很容易懂:如果只使用HTTP GET與POST,那麼很多操作(例如刪除、修改)就不得不使用路徑、查詢參數、或者在JSON 資料內放置旗標來表示,這樣的寫法將缺乏自我表達性(很難讀的意思)。

2019/02/13

Vue/React/Angular

Vue/React/Angular

所謂的組件就是一塊 HTML CSS JS 組合的網頁片段。

SPA 就是在一個頁面中反覆切換顯示所謂的組件,如果組件佔滿整個畫面,人們就會覺得換頁了。如果只是切換一部分,你就會覺得數據更新了。

路由就是在指示框架要切換什麼,所以一定要有路由出口才能顯示。框架也順便讓開發者可以放一些數據當參數以供被調用的組件使用。
框架再厲害也不可能超過瀏覽器的限制,也不可能改變 HTTP 的規矩。這就是為什麼三大框架概念這麼像的原因,因為大家都是在這些限制性實現 SPA 的樣子,所以解決方案自然英雄所見略同了。

Vue - Scoped Slot

Vue - Scoped Slot

總結一下 Vue Slot (這可能是Vue中比較難理解的東西)

Examplehttp://jsfiddle.net/albertdong/xnevpb0f/133/

slot 是為了處理子組件開口與閉口標籤之間的內容而生。這樣父組件就有機會改變一些子組件的內容。

<child>
想要插入的內容…
</child>

子組件用 slot 標籤預留插值位置,可以有名稱也可沒有(沒有名稱的名稱就叫做default)

父組件使用子組件(標籤)時,使用 template 標籤(非必要)將子組件開口與閉口標籤之間的內容取代上面子組件的slot標籤。

如果子組件聲明的 slot 標籤有名稱時父組件可以使用 slot 屬性指定名稱表示要放在哪個 slot,否則都會取代那個沒有名稱的 default slot 。


2014/01/17

開發 SAP RFC 專案的架構技巧

開發 SAP RFC 專案的架構技巧

情境:使用 DotNET Client + SAP RFC 進行庫存管理

要測試 SAP RFC (Remote Function Call) 似乎真的要有 SAP 系統以及其 SAPGui 工具(很龐大),
就算把 SAP 架起來了也不是隨時有環境可進行測試,這一定會影響功能面的開發進度
因為大部分的功能看來都與 SAP RFC 無關,所以被此卡住就不划算了。

先介紹一個常見的 SA/SD 技巧:虛擬倉 + 虛擬儲位
  • 仔細想想就會發現所有的庫存交易都是Movement(移動)
  • Movement 一定要有的資料是:品項、來源、目的、與數量。
  • 若把系統範圍之外的地方想像成虛擬倉/虛擬儲位的話,
    所有的
    庫存交易都可用一致的的 Movement 邏輯處理掉,不必再裡廠內或廠外的問題

舒緩此類系統邊界問題的架構技巧如下(此技巧也常用於 module 邊界):
  • 建立一個 SapRfc 父類別並定義出要使用的函式介面。
  • 定義兩個 SapRfc 的子類別例如:SapRfcMock 與 SapRfcReal:
    • SapRfcMock 是模擬介面,裡頭要用一般 DB 或 只用 Collection 隨你的便,這部分很單純。
    • SapRfcReal 是真正去做 RFC 呼叫的類別,將 RFC 的 code 都放在這裡。
    • 這樣功能面與 RFC 的工作就可平行地進展
  • 若使用 DotNET,建議不要再用 DataTable 之類的東西(因為也不會有DB),
    因為靈活的 DataSource 屬性
    才是 DotNET的強項
    • 最好是建立 Model 物件(EX: Warehouse, Locator, Inventory, Product,...)
    • 將上述的 Model 物件放到 List 之類的東西就可與控制項 binding 了。
    • 若一鄧要用 DataTable 將面臨更多的轉換動作
事實上 Model + SapRfcMock 定義完成後,一至三天內馬上就可開始功能面的開發。
而且這個定義可以用遞增的方式進行,不用一次全部做完,所以只需要很短的時間就做可出第一版。

UI(client端) 必須使用 SapRfc 呼叫以read/write需要的資料。
千萬不要一開始就去想真正的 RFC 呼叫,因為研究 RFC 會卡住功能的開發
只需用一個 flag 決定傳回 SapRfcMock 或 SapRfcReal 即可,整個感覺如下:
C# 檔案見:
https://drive.google.com/file/d/0B9exai2F1cufMHRPLWlpVUVXWGs/edit?usp=sharing

01 public sealed class SapRfcFactory {
02     private static SapRfc singleton = null;
03 
04     public static SapRfc GetInstance() {
05         // 在此處決定要使用 real 或 mock 物件
06         if (singleton = null)
07             singleton = new SapRfcMock();
08         //singleton = new SapRfcReal();
09 
10         return singleton;
11     }
12 
13     private SapRfcFactory() {}
14 
15     // other codes...
16 }
17 
18 // 定義 RFC 呼叫介面
19 public abstract class SapRfc {
20     // 可以在此成員存放共用參數
21     Dictionary<string, object> dicParams = new Dictionary<string, object>();
22 
23     public abstract bool Logon();
24     public abstract void Logout();
25 }
26 
27 public class SapRfcMock: SapRfc {
28     public override bool Logon() {
29         // 直接回傳 true
30         return true;
31     }
32 
33     public override void Logout() {
34         // do nothing.
35     }
36 }
37 
38 public class SapRfcReal: SapRfc {
39     public override bool Logon() {
40         // 進行實際的 RFC 登入...
41     }
42 
43     public override void Logout() {
44         // 進行實際的 RFC 登出...
45     }
46 }


2013/11/12

柔性 vs 硬式管理

我認為柔性與硬式管理的效果在理論與實務上都是對的,二者並不衝,這本就是管理上的藝術面
管理學自己都承認:「天底下沒有放諸四海皆準的管理準則」與「管理既是科學也是藝術」。

以專案管理為例好了,大家常聚集在工作分解、資源分配、排時程等事項,這都屬於專案管理的科學面
菜鳥 PM 也常誤以為這些事做好之後專案就一定成功,因此所有的專案管理書籍與講師最後一定會提醒大家:

科學面的技術與工具只是專案成功的必要條件,而不是充分條件
專案到頭來還是人去執行的,所以最後的成敗還是在人員管理

所以要提高成功機率就必須技術/工具與人員管理並重。

以我自己的經驗來說,柔性管理:
  • 重點主要是讓經理人(Manager)變成領導者(Leader)。
  • 第一步要分清楚經理人與領導者的區別。
    • 經理人只靠職權去驅動人員(利用大家懼怕職權的心理)
    • 但是領導者靠其專業、提供方向、以身作則...等來引導人員。(完全不靠職權)
    • 這個時代只靠職權是什麼都做不了的。(上有政策,下有對策)
  • 在工作以外的部分可以放入更多人性,例如人員的職涯規劃與訓練,在金錢上儘量在一定範圍內多滿足人員,...
  • 簡言之就是收買人心的方法。
硬式管理:
  • 通常指的是堅持工作原則與規範,目的是吸引優秀的人同時快速淘汰無法勝任的人
  • 提供成員必要的訓練,預先掃除其執行障礙,然後要求成員必須遵守流程與準則。
融合上述兩個方式的期望效果是:

  • 找出願意學習與進步的人並收買其人心,同時也過濾出學習態度不佳也不努力的庸才並加以淘汰。
  • 最終目的就是留住人才快速淘汰庸才
庸才的影響力遠比想像的還大因為
  • 這樣的人會做出不良示範,讓真正的人才心中不服,接著必然無法貫徹團隊目標
  • 如果經理人放任此行為將造成其他人心中誤以為這是經理人的意向,這損失就大了!
  • 接下來經理人就會被認為無能,同時也會錯誤暗示大家這裡有特權是個不公平的地方。

真搞成這樣的話,即使團隊的人再強也將無法有任何作為了(團隊癱瘓)。

最近看到 Google 的人員管理才發現原來在這個人人稱羨的工作環境之中,
新進人員竟然只有一年可以證明自己,若證明不了的話一年後就得
因為 Google 只願意給人才享用這些被人羨慕的環境與福利,對於庸才 Google 連沾都不讓他沾。
(我的猜測:Google 應該只是把庸才的薪資與福利分配給人才而已,總成本並未增加多少)

Google 這樣算是軟或硬呢?

專案計畫 vs 工作計畫

專案計畫 vs 工作計畫

很多人誤會了一件事,誤以為好像只有專案經理才需要做計畫。
其實專案管理是要求專案經理去引導所有的專案成員去共同規劃

專案計畫與個人工作計畫是兩回事個人工作計畫不會自動支援專案計畫尤其在跨部門時更明顯。
如果沒有專案計畫的話,專案管理為保險起見會把所有項目都說成第一優先,但卻沒有什麼根據。

但是一個面對多個專案的人員來說:可能把所有項目都擺在第一優先
所以最後只能以職位與權力來排優先順序,所以高層人員通常感覺不到有任何影響(與事實不符的感覺)
但是誰都知道這遲早會出問題的。(因為當所有事項都是第一優先就等於沒有任何優先,除非你的權力夠大)

把若干人組成一個小組也是很不錯的想法,因為這可以在小範圍下培養出 leader
因為大家都知道要直接培養出一個專案理是非常費時、費成本,也無人能保證一定成功。

所以幾乎所有軟體公司在 PM 之下都會再分出多位 module leader 的原因之一也是如此。
不談空降的 PM,內昇儲備 PM 通常就是由這些 module leader 挑選出來做進一步培訓的。

通訊的設計模式: Channel-Queue

通訊的設計模式: Channel-Queue



2013/08/20

一個簡單的 Delphi O/R Mapping 工具

一個簡單的 Delphi O/R Mapping 工具。
這裡好像無法上傳檔案,需要者可與我聯絡:

  albertdongtw@gmail.com

或由此下載:

Download

Entity Builder主要的功能在於產生Delphi類別來對映(mapping)既有的資料表。基本上,資料表的欄位會對映到Delphi類別的屬性(property)。但是手動做這些工作是非常辛苦的,所以我們可以使用這個工具程式來建立基本的類別骨架。
執行程式後的啟始畫面。


按下「建立ADO連線」按鈕設定ADO連線。

接著如同使用TADOConnection元件般設定ConnectionString



此時會顯示資料庫與資料表(不含系統資料表)清單。

由資料表清單選擇一個資料表。


按「預覽」按鈕即可顯示產生的程式碼。可按滑鼠右鍵儲存到檔案或是複製到剪貼簿。




  
產生出來的完整程式碼如下:
{-------------------------------------------------------------------------------
This file is generated by EntityBuilder v1.2
provided by Albert Dong, albertdongtw@gmail.com

The Template Engine is from:
    http://sourceforge.net/projects/delphi-templeng/
--------------------------------------------------------------------------------

Class: User
Table: user
Author: XXX
Date: 2013/8/20 下午 07:06:39
Description:
-------------------------------------------------------------------------------}

unit uUser;

interface

uses
  Windows, Messages, SysUtils, Variants, Contnrs, Classes, DB, uEntity;

const
  TBL_User = 'user';

type
  {**
   * Class description.
   *
   * @author XXX, 2013/08/20 19:06:39. Created.
   *}
  TUser = class(TEntity)
  private
    // ------ Fields ------
    FNo: string;
    FName: string;
    FNameEng: string;
    FEmail: string;
    FRecvCalNotify: integer;
    FRecvCalOverdue: integer;
    FIsAdmin: integer;

  protected
    // ------ Property Accessors ------
    function GetNo: string;
    procedure SetNo(value: string);

    function GetName: string;
    procedure SetName(value: string);

    function GetNameEng: string;
    procedure SetNameEng(value: string);

    function GetEmail: string;
    procedure SetEmail(value: string);

    function GetRecvCalNotify: integer;
    procedure SetRecvCalNotify(value: integer);

    function GetRecvCalOverdue: integer;
    procedure SetRecvCalOverdue(value: integer);

    function GetIsAdmin: integer;
    procedure SetIsAdmin(value: integer);


    procedure FillData(DataSet: TDataSet); override;

  public
    // ------ Utility Class Methods ------
    class function ListAll: TList;
    class procedure DeleteAll;

    constructor Create; override;
    destructor Destroy; override;

    function ToString: string; override;

    procedure Reload;
    procedure Insert;
    procedure Update;
    procedure Delete;

  published
    // ------ Properties ------
    property No: string read GetNo write SetNo;
    property Name: string read GetName write SetName;
    property NameEng: string read GetNameEng write SetNameEng;
    property Email: string read GetEmail write SetEmail;
    property RecvCalNotify: integer read GetRecvCalNotify write SetRecvCalNotify;
    property RecvCalOverdue: integer read GetRecvCalOverdue write SetRecvCalOverdue;
    property IsAdmin: integer read GetIsAdmin write SetIsAdmin;
  end;

implementation

uses
  uToString;

{ TUser }

class function TUser.ListAll: TList;
var
  stmt: string;
begin
  stmt := 'SELECT * FROM ' + TBL_User;
  result := TEntity.ExecQueryForList(stmt, TUser);
end;

class procedure TUser.DeleteAll;
begin
  ExecUpdate('DELETE FROM ' + TBL_User);
end;

constructor TUser.Create;
begin
  inherited Create;
  // TODO: Constructor...
end;

destructor TUser.Destroy;
begin
  // TODO: Destructor...
  inherited Destroy;
end;

(**
 * Fill object properties with the specified dataset.
 * Every subclass of TEntity inherits AsXXX() methods.
 *
 * @param DataSet the specified dataset.
 *)
procedure TUser.FillData(DataSet: TDataSet);
begin
  if DataSet = nil then
    raise Exception.Create('Nil DataSet!');

  if DataSet.IsEmpty or DataSet.Eof then
    Exit;

    FNo := AsString(DataSet, 'no');
    FName := AsString(DataSet, 'name');
    FNameEng := AsString(DataSet, 'name_eng');
    FEmail := AsString(DataSet, 'email');
    FRecvCalNotify := AsInteger(DataSet, 'recv_cal_notify');
    FRecvCalOverdue := AsInteger(DataSet, 'recv_cal_overdue');
    FIsAdmin := AsInteger(DataSet, 'is_admin');
end;

procedure TUser.Reload;
var
  stmt: string;
begin
  stmt := 'SELECT * FROM ' + TBL_User
        + ' WHERE 1 = 1 '
        + '   AND no = ' + QuotedStr(No)
    ;

  TEntity.Load(stmt, self);
end;

procedure TUser.Insert;
var
  stmt: string;
begin
  stmt := 'INSERT INTO ' + TBL_User + ' ('

        + 'no'

        + ', name'

        + ', name_eng'

        + ', email'

        + ', recv_cal_notify'

        + ', recv_cal_overdue'

        + ', is_admin'

        + ') VALUES ('

        + QuotedStr(No)

        + ', ' + QuotedStr(Name)

        + ', ' + QuotedStr(NameEng)

        + ', ' + QuotedStr(Email)

        + ', ' + IntToStr(RecvCalNotify)

        + ', ' + IntToStr(RecvCalOverdue)

        + ', ' + IntToStr(IsAdmin)
        + ') '
    ;

  ExecUpdate(stmt);
end;

procedure TUser.Update;
var
  stmt: string;
begin
  stmt := 'UPDATE ' + TBL_User + ' SET '

        + 'no = ' + QuotedStr(No)

        + ', name = ' + QuotedStr(Name)

        + ', name_eng = ' + QuotedStr(NameEng)

        + ', email = ' + QuotedStr(Email)

        + ', recv_cal_notify = ' + IntToStr(RecvCalNotify)

        + ', recv_cal_overdue = ' + IntToStr(RecvCalOverdue)

        + ', is_admin = ' + IntToStr(IsAdmin)

        + ' WHERE 1 = 1 '
        + '   AND no = ' + QuotedStr(No)
    ;

  ExecUpdate(stmt);
end;

procedure TUser.Delete;
var
  stmt: string;
begin
  stmt := 'DELETE FROM ' + TBL_User
      + ' WHERE 1 = 1 '
      + '   AND no = ' + QuotedStr(No)
    ;
  ExecUpdate(stmt);
end;

function TUser.ToString: string;
begin
  TToStringBuilder.toString(self);
end;

function TUser.GetNo: string;
begin
  result := FNo;
end;

procedure TUser.SetNo(value: string);
begin
  if FNo <> value then begin
    FNo := value;
  end;
end;

function TUser.GetName: string;
begin
  result := FName;
end;

procedure TUser.SetName(value: string);
begin
  if FName <> value then begin
    FName := value;
  end;
end;

function TUser.GetNameEng: string;
begin
  result := FNameEng;
end;

procedure TUser.SetNameEng(value: string);
begin
  if FNameEng <> value then begin
    FNameEng := value;
  end;
end;

function TUser.GetEmail: string;
begin
  result := FEmail;
end;

procedure TUser.SetEmail(value: string);
begin
  if FEmail <> value then begin
    FEmail := value;
  end;
end;

function TUser.GetRecvCalNotify: integer;
begin
  result := FRecvCalNotify;
end;

procedure TUser.SetRecvCalNotify(value: integer);
begin
  if FRecvCalNotify <> value then begin
    FRecvCalNotify := value;
  end;
end;

function TUser.GetRecvCalOverdue: integer;
begin
  result := FRecvCalOverdue;
end;

procedure TUser.SetRecvCalOverdue(value: integer);
begin
  if FRecvCalOverdue <> value then begin
    FRecvCalOverdue := value;
  end;
end;

function TUser.GetIsAdmin: integer;
begin
  result := FIsAdmin;
end;

procedure TUser.SetIsAdmin(value: integer);
begin
  if FIsAdmin <> value then begin
    FIsAdmin := value;
  end;
end;


initialization
  // Register this entity class for dynamic loading.
  RegisterClass(TUser);

finalization
  UnRegisterClass(TUser);


end.