สมการดุลยภาพ ของ Domain-Driven Design (DDD)

หลักในการปฏบัติแบบ Domain-Driven Design มีอยู่ 3 ข้อ ได้แก่

  1. เรียนรู้ความหมายของปัญหาจากผู้เชี่ยวชาญ โดยใช้ภาษาร่วมกัน (Ubiquitous Language) หรือเรียกได้ว่า เป็นพื้นที่ความรู้ของปัญหา (The Problem Space)
  2. นำความรู้จากผู้เชี่ยวชาญที่ได้จากข้อ 1 มาออกแบบ (Modeling) เพื่อเขียน Code (Model-Driven Design) หรือเรียกได้ว่า พื้นที่ของความรู้ในการแก้ปัญหา (The Solution Space)
  3. พยายามป้องกันไม่ให้ความรู้จากปัญหา หรือ เทคนิคการออกแบบอื่นๆ มาทำให้ code จากข้อ 2 เปลี่ยนความหมายจากขอบเขตของปัญหาจากข้อ 1 ไป (Preserving Model Integrity)

ดังนั้น ผมสามารถเขียน หลักการ DDD ออกมาเป็นสมการ ได้เป็น

Ubiquitous Language = Model-Driven Design

ความหมายของสมการแสดงถึงคงไว้ซึ่งดุลยภาพของ
ความรู้ของปัญหา (ด้านซ้าย Ubiquitous Language) กับ Code (ด้านขวา Model-Driven Design) ให้ เท่ากัน อยู่เสมอไป (เครื่องหมายเท่ากับ ก็คืองานส่วน Preserving Model Integrity)

เครื่องมือสำหรับทำความเข้าใจปัญหาเพื่อใช้สร้างภาษาสำหรับสื่อสารและออกแบบ Model ร่วมกัน (Ubiquitous Language) โดย Eric Evan แนะนำไว้เรียกว่า Model Exploration Whirlpool

เครื่องมือสำหรับงาน Model-Driven Design จากหนังสือ Domain-Driven Design: Tackling Complexity in the Heart of Software โดย Eric Evan ที่นำเสนอไว้ ดังภาพนี้ครับ

งานในส่วนออกแบบนี้ สามารถใช้ Patterns ที่เรารู้จักเข้ามาช่วยออกแบบได้ครับ ผมแนะนำหนังสือเบื้อต้น ดังนี้

เครื่องมือสำหรับงาน Preserving Model Integrity จากหนังสือ เล่มเดียวกัน โดย Eric Evan ที่นำเสนอไว้ ดังภาพนี้ครับ

 

และ สำหรับวงสีน้ำเงิน  Ubiquitous Language (สมการทางด้านขวา) ก็คือ เครื่องมือที่ใช้สร้างภาษาร่วมกันระหว่างโปรแกรมเมอร์ และผู้ใช้ระบบ ซึ่งก็คือผู้เชี่ยวชาญเจ้าของปัญหา โดย Eric Evan แนำนำในหนังสือ นำเข้ามาใช้เป็นเครื่องมือสำหรับงานส่วนนี้ ก็เป็นหลักการปฏิบัติ ในกระบวนการพัฒนาซอฟแวร์ Extreme Programming ที่เรียกว่า System Metaphor

Model-Driven Design คือ การออกแบบเชิงแบบจำลอง ทำไมจึงเป็นแบบจำลอง (Model) เพราะว่าเราต้องการแสดงความรู้ของปัญหาออกมาเป็นภาษาที่สื่อสารได้ง่าย, มีโครงสร้าง และ
สามารถบันทึกเก็บไว้ได้

ตัวอย่าง แบบจำลอง ก็คือ diagram ที่ใช้แสดงความหมายของความรู้ เช่น UML, Flow Chart, CAD หรือ Chart ในรูปแบบอื่นๆ เพื่อใช้สื่อความหมายของความรู้ที่ต้องการ รวมถึง syntax หรือ code ต่างๆด้วย เช่น Mathematical syntax, Python, C#, Go ก็ถือเป็น model ชนิดหนึ่งเช่นกัน

ในความสมดุลจากสมการ จึงมีความหมายได้ว่า ความรู้จากปัญหา เท่ากับ Code ที่ออกแบบมาอยู่เสมอ เราจึงอ่าน Code ได้เหมือนได้อ่านเรื่องราวของปัญหาโดยไม่เสียความหมายจากเจ้าของปัญหาไปเลย

นั่นก็คือหลักการสำคัญของ Domain-Driven Design

ขอบคุณครับ

#:P

Advertisements

.NET พัฒนาซอฟแวร์แบบ Resilient ด้วย package Polly

สวัสดีครับ

บล็อคนี้เป็นเรื่องยากสักหน่อย และผมจะใช้ภาษาของตัวเองเพื่ออธิบายการทำให้ระบบงานซอฟแวร์ที่เราสร้างขึ้นมีคุณสมบัติ resilient ก็คือ การรับประกันระบบซอฟแวร์ว่าจะทำงานชดเชย หรือ ฟื้นฟูให้ระบบกลับมาทำงานให้บริการเป็นปกติเหมือนเดิม หากเกิดความผิดพลาด (failures) และส่งผลให้เกิดความสูญเสีย (loss) ขึ้นแก่ธุรกิจ หรือ สิ่งอื่นๆที่เกี่ยวข้องกับระบบงานซอฟแวร์ของเราตามมา

ผมคิดว่าคุณสมบัติข้อนี้เป็นหัวใจหลักของระบบงานซอฟแวร์ ที่ทำให้เกิดคุณสมบัติ Responsive และ Elastic เกิดได้ ผมจึงเขียนบล็อคนี้ขึ้นมา

Resilient คือ การรับประกันความผิดพลาดใดๆจากการทำงานของระบบที่เกิดขึ้นในระหว่างการทำงานปกติ เมื่อระบบเกิดข้อผิดพลาด และเข้าแผนความคุ้มครองความผิดพลาด (policy) ที่กำหนดไว้แล้ว ก็จะมีการทำงานชดเชยตามแผน เพื่อให้กลับมาทำงานได้ตามปกติ และเกิดความสูญเสียน้อยที่สุด

Resilient ไม่ได้รับประกันว่าจะไม่เกิดข้อผิดพลาด แต่รับประกันว่า หากเกิดข้อผิดพลาดแล้วระบบต้องสามารถชดเชย หรือฟื้นฟูตนเอง ให้กลับมาทำงานได้ตามปกติ

การทำงานได้ตามปกติ หมายความว่า ระบบต้องทำงานโดยอยู่ในสถานะ ไม่มีข้อผิดพลาด

ข้อผิดพลาดนี้ ไม่ใช่แค่ error เท่านั้น แต่มีความหมายรวมถึง metric ที่วัดค่า health, availability, performance, KPI, SLA และอื่นๆที่ใช้วัดคุณภาพของระบบซอฟแวร์ที่เรากำหนด ด้วย

การทำงานได้ตามปกติ ระบบต้องทำงานให้บริการ อยู่ในคุณภาพที่เรากำหนดกันตั้งแต่เริ่มต้นสร้างระบบ โดยดูจาก metric ที่วัดค่าได้ เช่น จำนวนข้อผิดพลาดต้องเป็นศูนย์, การให้บริการโดยเฉลี่ยต้องไม่เกิน 16 วินาที, ทุก containers ต้องพร้อมบริการอยู่เสมอ เป็นต้น

กระบวนการหลักของ Resilient ประกอบด้วย

หนึ่ง แผนความคุ้มครองความผิดพลาด (Policy) และ สอง กระบวนการชดเชยความผิดพลาด (Compensate)

Policy ก็คือ แผนความคุ้มครองความผิดพลาด ที่มีการระบุเงื่อนไขไว้เพื่อจับข้อผิดพลาด ที่อาจจะเกิดขึ้นจากกระบวนการทำงานปกติ

ตัวอย่างที่จะทำเป็นตัวอย่างนี้ใช้ package Polly ติดตั้งได้โดยเขาไปที่ visual studio 2017 แล้วพิมพ์คำสั่ง ข้างล่างนี้ลงไปที่ package console manager

PM> install-package Polly

Polly จัดเตรียมเครื่องมือสำหรับแผนความคุ้มครองให้เลือกใช้ ดังนี้

Retry: แผนนี้จะ จับข้อผิดพลาดแล้วนับสะสมไว้ จากนั้นจะชดเชยข้อผิดพลาดในแต่ละครั้งไป ไม่เกินจำนวนครั้งที่กำหนด

ตัวอย่าง code Retry

public void SimpleRetry()
{
int countFoo = 0;
Action foo = () =>
{
++countFoo;
Console.WriteLine($”Act foo: {countFoo}”);
throw new NullReferenceException();
};

Action<int> bar = (retryCount) => Console.WriteLine($”Compensate: {retryCount}”);

Policy
.Handle<NullReferenceException>()
.Retry(3, (exception, retryCount) =>
{
bar(retryCount);
})
.ExecuteAndCapture(foo);
}

Output SimpleRetry

Act foo: 1
Compensate: 1
Act foo: 2
Compensate: 2
Act foo: 3
Compensate: 3
Act foo: 4

ดูจาก Output SimpleRetry Act foo ครั้ง 4 ไม่มีการ Compensate ให้ ตามแผนประกันข้อผิดพลาด retry ให้ 3 ครั้ง

Circuit-breaker: แผนประกันข้อผิดพลาดนี้จะคุ้มครองระบบไม่ให้ทำงานที่ผิดพลาดซ้ำๆหลายครั้ง พร้อมกับแก้ไขระบบให้กลับมาทำงานได้ตามปกติ มี 3 state คือ

  • Closed: เป็นสถานะเริ่มต้น กรณีการทำงานเป็นปกติ
  • Open: จะเป็นสถานะนี้ ก็ต่อเมื่อเกิดข้อผิดพลาดเกินจำนวนที่กำหนดไว้ หลักจากนั้น Circuit-breaker จะตั้งเวลาหยุดใหม่
  • (durationOfBreak) พร้อมกับ หยุดทุกกระบวนการตามระยะเวลาที่กำหนดนี้ จนกระทั่งหมดเวลา ก็จะไปอยู่ในสถานะ Half-Open
  • Half-Open: เมื่ออยู่ในสถานะนี้ Circuit-breaker จะเปิดให้ทำงานตามปกติได้ และเมื่อเรียกแล้วเกิดข้อผิดพลาดอีก ก็จะไปอยู่ในสถานะ Open อีกครั้ง วนไป แต่ถ้าเรียกกระบวนการแล้วผ่านได้ Circuit-breaker จะไปอยู่ที่สถานะ Closed ตามเดิม

ตัวอย่าง code Circuit breaker

public void SimpleCircuitBreaker()
{
int countFoo = 0;

Action foo = () =>
{
++countFoo;
Console.WriteLine($”Act foo: {countFoo} NullReferenceException”);
throw new NullReferenceException();
};
Action foo2 = () => Console.WriteLine($”Act foo2 completed”);

var breaker = Policy
.Handle<NullReferenceException>()
.CircuitBreaker(
exceptionsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(2),
onBreak: (exception, durationOfBreak) =>
{
Console.WriteLine($”onBreak”);
},
onHalfOpen: () =>
{
Console.WriteLine($”onHalfOpen”);
},
onReset: () =>
{
Console.WriteLine($”onReset”);
}
);

breaker.ExecuteAndCapture(foo); // 1
breaker.ExecuteAndCapture(foo); // 2
breaker.ExecuteAndCapture(foo); // 3 เปลี่ยนสถานนะ Open
// ในช่วง 2 วินาทีรี้ ทุกกระบวนการที่เรียกหลังจากนี้จะถูกหยุด
breaker.ExecuteAndCapture(foo); // 4 ถูกหยุด
breaker.ExecuteAndCapture(foo2); // 5 ถูกหยุด
// จำลองโดยการรอให้เวลาหมด 3 วินาที
Task.Delay(TimeSpan.FromSeconds(3)).Wait();

breaker.ExecuteAndCapture(foo); // ถูกเรียกอีกครั้ง แต่เกิดข้อผิดพลาด

// จำลองโดยการรอให้เวลาหมด 3 วินาที
Task.Delay(TimeSpan.FromSeconds(3)).Wait();
breaker.ExecuteAndCapture(foo2); // เรียก function ที่ถูกต้อง
}

Output SimpleCircuitBreaker

Act foo: 1 NullReferenceException
Act foo: 2 NullReferenceException
Act foo: 3 NullReferenceException
onBreak
onHalfOpen
Act foo: 4 NullReferenceException
onBreak
onHalfOpen
Act foo2 completed
onReset

เมื่อพิจารณาดูจะเห็นว่า Act foo เกิดข้อผิดพลาด NullReferenceException ทำงาน 3 ครั้ง แล้ว เข้าสถานะ Open คือ onBreak หลังจากนั้นแม้จะเรียก function foo ที่มี NullReferenceException หรือ foo2 ที่ไม่มี exception ก็ถูก break ไว้ในสถานะ Open

ต่อมาจึงจำลอง โดย delay เวลาไว้ 3 วินาที มากกว่าเวลาที่ CircuitBreaker มัน break คือ 2 วินาที จนมันไปอยู่ที่สถานะ Half-Open สังเกต output onHalfOpen แล้วก็เรียก foo ให้เกิดข้อผิดพลาด จะเห็นว่าเรียกเพียงครั้งเดียวก็เข้าไปสู่สถานะ Open อีกครั้ง การนับข้อผิดผลาดจะสะสมต่ออีก จนกระทั้ง หมดเวาล break กลับไปสู่ HalfOpen อีกครั้ง จำลองให้ delay เวลาไว้ 3 วินาทีเหมือนเดิม

ทีนี้เรียก foo2 เป็น function ที่ทำงานไม่มีข้อผิดพลาด CircuitBreaker จะกลับไปอยู่สถานะ Closed อีกครั้งพร้อมกับเริ่มนับข้อผิดพลาดใหม่

สำหรับ กระบวนการชดเชยข้อผิดพลาด สำหรับแผนแบบ CircuitBreaker สามารถทำได้ที่ onBreak และ onHalfOpen คือเมื่อ เข้า onBreak ให้พยายาม แก้ไขข้อผิดพลาดในช่วงเวลานั้น และเมื่อเวลา break หมดลง จะเข้ากระบวนการ onHalfOpen อีกครั้งให้ตรวจสอบว่าการแก้ไขนั้นผ่านหรือยัง ก่อนที่งานในกระบวนการปกติจะกลับเข้ามาทำงานได้อีกครั้ง

Timeout: จะประกันว่าการทำงานส่วนนี้ ต้องไม่รอนานเกินกว่าเวลา (timeout) ที่ระบุไว้แน่นอน

แผนแบบ Timeout มี 2 ผลประโยชน์ให้เลือกคือ Pessimistic และ Optimistic

  • Optimistic Timeout: จะชดเชยก็ต่อเมื่อ เกิดข้อผิดพลาดในระหว่างการทำงานปกติ ที่เกินเวลา Timeout ไปแล้วเท่านั้น
  • Pessimistic Timeout: จะชดเชยทันทีไม่ว่ากรณีใดๆเมื่อการทำงานปกติเกิด Timeout

ตัวอย่าง code Optimistic Timeout

public void SimpleOptimisticTimeout()
{
Action foo = () =>
{
Task.Delay(TimeSpan.FromSeconds(3)).Wait();
Console.WriteLine($”Throw ApplicationException”);
throw new ApplicationException();
Console.WriteLine($”Act foo completed”);
};

Action bar = () => Console.WriteLine(“Compensate”);

var timeoutPolicy = Policy.Timeout(
timeout: TimeSpan.FromSeconds(2),
timeoutStrategy: TimeoutStrategy.Optimistic,
onTimeout: (context, timespan, task, exception) =>
{
bar();
}
);

timeoutPolicy.ExecuteAndCapture(foo);

}

output ของ Optimistic Timeout

Throw ApplicationException
Compensate

จาก function foo ผมให้ delay งานไว้ 3 วินาที ให้มากกว่า Timeout Policy คือ 2 วินาที เมื่อ throw exception จากการทำงานปกติ หลังจากนี้จะเกิน timeout แผนความคุ้มครองก็จะชดเชยให้โดยเรียก function bar ลองปรับให้ delay งานไว้ต่ำกว่า 2 วินาทีดูครับ

ตัวอย่าง code Pessimistic Timeout

public void SimplePessimisticTimeout()
{
Action foo = () =>
{
Task.Delay(TimeSpan.FromSeconds(3)).Wait();
Console.WriteLine($”Act foo completed”);
};

Action bar = () => Console.WriteLine(“Compensate”);

var timeoutPolicy = Policy.Timeout(
timeout: TimeSpan.FromSeconds(2),
timeoutStrategy: TimeoutStrategy.Pessimistic,
onTimeout: (context, timespan, task, exception) =>
{
bar();
}
);

timeoutPolicy.ExecuteAndCapture(foo);

}

output ของ Pessimistic Timeout

Compensate

จาก function foo ผมให้ delay งานไว้ 3 วินาที ให้มากกว่า Timeout Policy คือ 2 วินาที จะเห็นว่า แผนนี้จะชดเชยให้ทันที โดยไม่รอให้การทำงานปกติจบลง ไม่ว่าในการทำงานจะเกิดข้อผิดพลาดใดๆก็ตาม แผนแบบ Pessimistic Timeout ก็จะชดเชยให้ครับ

Bulkhead Isolation: แผนนี้รับประกันว่าการใช้ทรัพยกรเพื่อกระบวนการทำงานปกติต้องไม่เกินขีดจำกัดที่กำหนดไว้

ตัวอย่าง code Bulkhead Isolation

public void SimpleBulkheadIsolation()
{
Action<Context> foo = (n) =>
{
Task.Delay(TimeSpan.FromSeconds(1)).Wait();
Console.WriteLine($”Act foo {n.OperationKey} completed”);
};

Action<string> bar = (corId) => Console.WriteLine($”Compensate for {corId}”);

var bulkhead = Policy
.Bulkhead(
maxParallelization: 1,
maxQueuingActions: 1,
onBulkheadRejected: (ct) =>
{
bar(ct.OperationKey.ToString());
});

var taksList = new List<Task>();
for (int i = 0; i < 5; i++)
{
var t = Task.Factory.StartNew(() =>
{
var corAppId = new Context(Guid.NewGuid().ToString());
bulkhead.ExecuteAndCapture(foo, corAppId);
});

taksList.Add(t);
}

Task.WhenAll(taksList).Wait();
}

output ของ Bulkhead Isolation

Compensate for 7b9b6042-0d36-49e3-ae1a-d6b365f6c9b8
Compensate for d73bd757-a645-4a91-b1c7-b78cec4f64e5
Compensate for c5b071d6-27b6-48d0-97ab-b8599ef1ec13
Act foo 84a336ae-64c9-4cf6-a94f-6a69757cca9c completed
Act foo c347f29f-c091-474c-a45c-5cb5cdc103a2 completed

อธิบายตัวอย่าง code แผนแบบ Bulkhead Isolation ตัวอย่างกำหนดแผนแบบ Bulkhead กำหนดค่าดังนี้

maxParallelization คือ ค่าจำกัดให้ทำงานพร้อมๆกัน ตัวอย่างทำได้ทีละ 1 งาน

maxQueuingActions คือ ค่าจำกัดให้งานรอในคิวได้ไม่เกินกำหนด ตัวอย่างรอทำได้ทีละ 1 งาน

เหตุการณ์ onBulkheadRejected จะเกิดขึ้นก็ต่อเมื่อ งานที่เข้ามาเกินค่า maxQueuingActions แล้ว ในตัวอย่างจะให้ชดเชยด้วย function bar ตัวอย่างให้เพิ่มงานวนไป 5 งาน เมื่อดู output จะเห็นว่า งานถูกชดเชยไป 3 งานและอีก 2 งานทำงานได้ตามปกติ

Cache: แผนนี้จะคืนค่าที่รู้อยู่แล้วกลับไปทันที ถ้าไม่รู้จะเก็บค่าไว้ระยะเวลาหนึ่ง ตามที่กำหนด คือเป็นแผนที่ช่วยชดเชยผลลัพย์ได้ชั่วคราว ถูกใช้ประกอบกับแผนความคุ้มครองอื่นๆ

Fallback: แผนนี้จะประกันว่ากระบวนการปกติต้องไม่เกิดข้อผิดพลาดใดๆ โดยจะชดเชยผลลัพย์ หรือ ทำกระบวนการให้ใหม่ แทนกระบวนการปกติ เมื่อเกิดข้อผิดพลาดขึ้นตามที่ระบุไว้ในแผน

ตัวอย่าง code Fallback

public void SimpleFallback()
{
Func<string, Product> foo = (name) =>
{
return new Product(name);
};

var fallback = Policy<Product>
.Handle<ArgumentNullException>()
.Fallback(() =>
{
return new Product(“Compensate product”);
});

var result = fallback.ExecuteAndCapture( () => foo(“”) );

Console.WriteLine($”Outcome: {result.Outcome}, Product name: {result.Result.Name}”);
}

public class Product
{
public Product(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException();
Name = name;
}
public string Name { get; set; }
}

output ของ Fallback

Outcome: Successful, Product name: Compensate product

อธิบายจาก code ตัวอย่าง

ผมสร้าง class Product และให้เกิดกข้อผิดพลาดถ้าใส่ชื่อค่าว่างเข้าไป กับ กำหนด function foo ให้เป็น กระบวนการทำงานปกติ คือทำหน้าที่แค่สร้าง Product ให้ และ กำหนด fallback policy กำหนดความคุ้มครองที่ข้อผิดพลาด ArgumentNullException ซึ่งจะสร้าง Product ตั้งคือใหม่เป็น Compensate product ให้ใหม่แทนข้อผิดพลาดจาก foo

ดู output จะเห็นว่า กระบวนการปกติตั้งใจใส่ชื่อว่าง เพื่อให้เกิดข้อผิดพลาด จะเห็นว่า Outcome นี้มีค่า Successful และ มีการชดเชย product ให้ใหม่เป็นชื่อ Compensate product

PolicyWrap: การรับประกันข้อผิดพลาดให้ระบบ หรือ Resilient จะต้องใช้ทุกแผนความคุ้มครองที่กล่าวมาทั้งหมดนั้น เพื่อให้ครอบคุมข้อผิดพลาดที่อาจจะเกิดขึ้น และ ชดเชย หรือ ทดแทน เพื่อฟื้นฟูระบบให้กลับมาทำงานเป็นปกติเหมือนเดิมได้

ตัวอย่าง code PolicyWrap

public void SimplePizzaResilient()
{
Func<string, Product> makePizza = (name) => new Product(name);

Action<Product> normalShipPizza = (product) =>
{
Task.Delay(TimeSpan.FromSeconds(3)).Wait();
Console.WriteLine($”Normal ship {product.Name} completed”);
};

Action<Product> specialShipPizza = (product) =>
{
Console.WriteLine($”Special ship {product.Name} completed”);
};

var makePizzaFallback = Policy<Product>
.Handle<TimeoutException>()
.Fallback(
fallbackAction: (ctx) =>
{
// make Compensate Pizza and special ship service
var bigPizza = makePizza(“Big Pizza by Compensate”);
specialShipPizza(bigPizza);
return bigPizza;
});

var serviceTimeout = Policy.Timeout<Product>(
timeout: TimeSpan.FromSeconds(2),
timeoutStrategy: TimeoutStrategy.Pessimistic,
onTimeout: (context, timespan, task, exception) =>
{
// send signal for produce compensate pizza
throw new TimeoutException();
}
);

var myPizzaPolicy = Policy.Wrap<Product>(makePizzaFallback, serviceTimeout);

myPizzaPolicy.ExecuteAndCapture(() =>
{
var mySinaturePizza = “Simple Pizza by #:P”;
var thePizza = makePizza(mySinaturePizza);
normalShipPizza(thePizza);
return thePizza;
});
}

อธิบาย code ตัวอย่างนี้เป็นการบริการพิซซ่า โดยการให้บริการครั้งหนึ่ง ประกอบด้วยกระบวนการปกติ คือ makePizza, normalShipPizza และ specialShipPizza โดย normalShipPizza ทำให้เกิด delay 3 วิทาที

class Product ดูที่ code ตัวอย่าง Fallback

ที่แผนความคุ้มครองการบริการพิซซ่า myPizzaPolicy คือการ wrap 2 แผนไว้ด้วยกัน ได้แก่ makePizzaFallback และ serviceTimeout โดยแผน makePizzaFallback จะทำการทำชดเชย โดยทำ pizza ขนาดใหญ่ makePizza พร้อมกับส่ง pizza แบบพิเศษ specialShipPizza

serviceTimeout ในตัวอย่างนี้ให้ง่าย จะส่ง signal โดย throw exception TimeoutException ออกไปเฉย เพื่อให้ makePizzaFallback ทำกระบวนการชดเชยให้

พอลอง run ดูก็จะเห็น output เป็นแบบนี้ครับ

Special ship Big Pizza by Compensate completed

แล้วลองแก้ไข delay ใน function normalShipPizza เป็น 1 วินาทีดู ก็จะ output ปกติออกมา ลอง run ดูครับ

ผมขอจบบล็อคไว้เพียวเท่านี้ก่อนครับ หวังว่าจะเป็นประโยชน์

ขอบคุณครับ

#:P

API Gateway ด้วย ASP.NET Core Web API ภายใน 15 นาที

สวัสดีครับ บล็อคนี้ผมขอเอาแบบเร็วๆนะครับ

API Gateway เป็น services ตัวหนึ่งที่ทำหน้าที่รับ request จากผู้ใช้ แล้ว ส่งต่อ request ไปยัง service providers พอได้ response กลับมา ก็ส่ง response กลับไปยังผู้ใช้

ประโยชน์

  1. ควบคุมจัดการการเข้าใช้ WEB API ที่จุดเดียว
  2. ทำ load-balance, fail-over WEB API ได้
  3. ควบคุมจัดการ version ของ WEB API โดยไม่ต้องเปลี่ยนแปลงที่ client

ข้อเสีย

API Gateway server เป็นด่านแรกเพื่อเข้าถึงทุกบริการ ดังนั้นถ้ามันตายไป ก็คือทั้งระบบเสีย การแก้ก็คือ ให้เราสร้าง cluster สำหรับ API Gateway server อีกที

สิ่งที่ต้องการ Visual Studio 2017 Community

ตัวอย่างนี้ สร้าง 2 components คือ

  1. APIGateway ชนิด project เป็น ASP.NET Core Web Application เป็น Empty project
  2. SimpleValuesServices ชนิด project เป็น ASP.NET Core Web Application เป็น API

1. สร้าง APIGateway

  • เปิด Visual studio สร้าง project APIGateway เลือก ASP.NET Core Web Appliation เลือกเป็น Empaty

  • ติดตั้ง Ocelot ไปที่ Package Manager Console พิมพ์คำสั่ง
    • PM> install-package Ocelot
  • เพิ่มไฟล์ routes.json (เลือก properties เป็น Copy always) ให้กำหนด Downstream, Upstream ของ HOST ตามนี้

 

  • เปิด class file Startup.cs เขียน code แบบข้างล่างนี้ลงไป

  • เปิด class file Program.cs เขียน code แบบข้างล่างนี้ลงไป

  • code เสร็จแล้วครับ build แล้ว run โดยเข้า command cd ไปที่ folder APIGateway\bin\Debug\netcoreapp2.1 จะมี APIGateway.dll อยู่ให้ พิมพ์ command ตามข้างล่างนี้

2. สร้าง SimpleValuesServices

  • เพิ่ม project เหมือน APIGateway แต่ให้เลือกเป็น API ตามรูป

  • ไปที่ class file Program.cs เพิ่ม code ตามข้างล่างนี้ลงไป

  • เสร้จแล้วครับ ให้ run 2 service เป็น cluster กัน โดยเปิด command แล้ว cd ไปที่ folder SimpleValuesServices\bin\Debug\netcoreapp2.1 จะมี file SimpleValuesServices.dll
    • run SimpleValuesServices port 5001
    •  run SimpleValuesServices port 5002

เอาละทุกอย่างเสร็จหมดแล้ว ทีนี้มาทดสอบกันครับ เปิด chrome ขึ้นมา พิมพ์ไปที่ http://localhost:9000/values

มันจะ ส่ง request สลับวนไปตาม algorithm ที่เรากำหนดไว้ (ตัวอย่างนี้กำหนดแบบ RoundRobin) ไป ยัง url simple values services สองตัวที่เปิดอยู่คือ http://localhost:5001/api/values กับ http://localhost:5002/api/values ครับ

Software Architecture ตัวอย่างเป็นแบบภาพนี้

 

แหล่งข้อมูล

  1. https://ocelot.readthedocs.io/en/latest/features/loadbalancer.html#id6
  2. API Gateway pattern

 

อธิบายการพัฒนาซอฟแวร์แบบส่งมอบของก่อน ด้วยตัวอย่าง (Delivery-first by Example)

สวัสดีครับ

บล็อคนี้ผมเขียนต่อจากบล็อคที่แล้ว การพัฒนาซอฟแวร์แบบส่งมอบของก่อน (Delivery-first)

การพัฒนาซอฟแวร์แบบส่งมอบของก่อน แบ่งช่วงขั้นตอน (Phase) การทำงานออกเป็น 3 ช่วง คือ Delivery, Detect และ Defect หรือ 3D

ช่วง Delivery เป้าหมายเพื่อส่งมอบของ (Deploy) โดยการส่งมอบนี้ไม่จำกัด อาจจะเป็นเพื่อส่งให้ลูกค้าใช้เลย (Production) หรือ ส่งเพื่อให้ลูกค้าทดสอบ (Acceptance / Functional test) หรือ ส่งมอบเพื่อทดสอบร่วมกันกับทีมพัฒนา (Integration test) หรือ ทดสอบเฉพาะส่วนเดี่ยวๆก็ได้ (Unit-test) ผมรวมเรียกว่าการส่งมอบ คือ Delivery หมายถึง ส่งมอบของที่ใช้ประโยชน์ได้ ทำงานได้จริง มีผลลัพธ์ออกมา ไม่ใช่แค่ code หรือ ไอเดีย แนวคิด การออกแบบ หรือ เอกสาร ต่างๆ

ช่วง Detect เป้าหมายเพื่อ ตรวจจับ ให้ได้ค่าที่ประเมิณผล (Measure) ได้จริง หรือก็คือการใช้ Fitness function เพื่อตรวจสอบว่าผลลัพธ์ทั้งหมดเราที่ประเมิณผลได้จากซอฟแวร์ที่ส่งมอบไปนั้นบรรลุเป้าหมายของเราหรือไม่ ผลสรุปจาก detect นี้ทำให้ทีมพัฒนา หรือ ผู้ใช้ และผู้ที่เกี่ยวข้อง เห็นชัดเจนว่าผลลัพธ์นี้ถูกต้อง ถูกใจเป็นไปตามความต้องการหรือไม่ ซึ่งก็คือรายการ defect เพื่อใช้ดำเนินการในขั้นตอน Defect ต่อไป

ช่วง Defect เป้าหมายเพื่อพิจารณาตัดสินใจ defect ต่างๆจากที่เรา detect ได้ว่า จะแก้ไข ปรับปรุงของ หรือ ซอฟแวร์ที่เราส่งมอบไปแล้วนั้นต่อไป หรือไม่ แล้ววนกลับไปสู่ช่วง Delivery ต่อไป ขั้นนี้ก็คือ  Continuous improvement ซึ่งเป็นกระบวนการบังคับในรูปแบบการพัฒนาซอฟแวร์เชิงวิวัฒนาการนั่นเอง

ตัวอย่าง ผมจะเป็นงานบัญชีสร้างรายงานสมุดรายวันทั่วไป ผลลัพย์แสดงเป็น html ง่ายๆครับ ขั้นตอนพัฒนาตามนี้เลยครับ

ต้องการ Visual studio 2017 community edit ครับ Download และติดตั้งฟรีได้ที่ VS2017

Delivery 

  1. ผลลัพธ์เป็นรายงาน HTML ครับ ชื่อ file report.html หน้าตาตามที่ออกมาเป็นแบบนี้
  2. สร้าง projects ภาษา C# ชื่อว่า Accounting.Core เป็น library สำหรับงานบันทึกธุรกรรมทางบัญชี 
  3. ใน projects ผมได้สร้าง class 3 class และ 2 enumeration type เพื่อใช้แทนงานทางบัญชี ซึ่งตอนแรก ก็ไม่ใช่ 3 class นี้ แต่ไม่เป็นไร ให้ทำไปก่อนครับ ตามที่เราเข้าใจความต้องการในตอนนั้น ไปได้เลย 3 class มีดังนี้
    1. class Account : สมุดบัญชีบันทึกรายวันทั่วไป
    2. class AccountEntry : รายการบัญชีแบบสองด้าน
    3. class AccountTransaction : ธุรกรรมธุรกิจ เพื่อใช้ระบุได้ว่า แต่ละรายการที่เปลี่ยนแปลงไปนั้นเป็นชุดธุรกรรมเดียวกัน
    4. enum AccountEntryType : ชนิดของการลงบันทึกบัญชีสองด้าน มีสองค่าคือ Debit และ Credit
    5. enum AccountType : ประเภทของรายการบัญชี แต่ละประเภทแบ่งเป็น 3 กลุ่มได้แก่ สินทรัพย์ (Assets), หนีสิน (Liabilities) และ ส่วนของเจ้าของ (Shareholders’ Equity)
  4. code ส่วนของของที่พร้อมส่งมอบแล้วอยู่ที่ Accounting.Core
  5. หลังจาก build เสร็จจะได้ของเป็น library file Accounting.Core.dll พร้อมส่งมอบให้ผู้ใช้ใช้งานได้ โดยผมจะสร้าง projects เพื่อแทนการใช้งาน library นี้อีกหนึ่ง project ชื่อว่า Accounting.Core.Client โดยเป็นแบบ MSTest ครับ คือมันเป็น xUnit สำหรับ run ใช้ของ Accounting.Core.dll ของผม แทนผู้ใช้เป็นตัวอย่างไปก่อน
  6. ใน project Accounting.Core.Client เพิ่ม Test class Delivery เพื่อใช้แสดงการใช้ของ คือ Accounting.Core.dll ครับ โดยการ add reference เข้ามา แล้วเพิ่ม Test method เพื่อดำเนินการเพิ่มธุรกรรมทางธุรกิจเพื่อให้มีรายการบันทึกบัญชี ไว้ออก report ได้ มีรายการธุรกกรรมทั้งหมด 4 รายการ คือ
    1. ธุรกรรมการค้า รหัส 001 – ขายสินค้าให้นาย ก. 1 ชิ้น รับเงินสด 1000 บาท
    2. ธุรกรรมการค้า รหัส 002 – จ่ายสินค้าคงเหลือเพื่อขาย นาย ก. 1 ชิ้น 800 บาท
    3. ธุรกรรมการค้า รหัส 003 – ขายสินค้าให้นาย ข. 2 ชิ้น รับเงินโอน PromptPay 2000 บาท
    4. ธุรกรรมการค้า รหัส 004 – จ่ายสินค้าขายนาย ข. 2 ชิ้น 1600 บาท
    5. หลังจากบันทึกรายการธุรกรรมการค้าเสร็จ ผมสั่ง build ได้ report.html ออกมาไปวางที่ path results ครับ
  7. Code client ทั้งหมด ดูได้ที่ Accounting.Core.Client.Delivery

หลังจากที่มีการใช้งานของแล้ว ขั้นตอนต่อมาก็คือการ Detect ครับ

Detect

  1. เพื่อไม่ให้เสียเวลา ผมใช้ Project client เพื่อเขียน script ในการ detect ผลลัพธ์จากการใช้งานแล้วครับ เพิ่ม Test class Detect เขาไป
  2. เนื่องจาก file ที่เราต้อง Detect นั้นเป็น html ผมขอติดตั้ง lib ตัวช่วยเข้าไปหน่อยคือ HtmlAgilityPack ครับลงผ่าน nuget ได้เลยครับ
    1. PM> install-package HtmlAgilityPack
  3. เพิ่ม Test method watch_sell_goods_and_build_generalJournal_report  เข้าไปเพื่อช่วย run script Detect ผลลัพย์จาก Client ครับ ชือว่า ดู code ได้ที่ Accounting.Core.Client.Detect
  4. หลังจากนั้นให้ run Test method โดยการ click mouse ขวา แล้วเลือก Run test ครับจะเห็นผลลัพธ์ที่ detect ได้เลย เพราะผมใช้ MSTest มันจะแสดง report ออกมาว่าผ่าน ไม่ผ่านตามที่เราเขียนไว้ 
  5. ดูผลลัพย์ detect จากหน้าต่าง Test Explorer ด้านซ้ายได้เลยครับ 

จะเห็นว่าทดสอบผ่านหมดครับ เพราะขึ้นอยู่กับเราจะเขียนให้ code detect ผลลัพย์ให้เป็นอย่างที่คาดหวังขนาดไหน ตัวอย่างนี้ก็เขียนให้ง่ายไว้ก่อน แค่เช็คว่ามี file report.html หรือไม่ และ ตรวจสอบยอดบันทึกด้าน Debit และ Credit ว่าเท่ากันรึเปล่า แค่นั้น

สำหรับขั้นตอน Defect ก็รวมอยู่ใน Detect แล้วครับ มันก็คือเราดูผลที่ Detect ได้จากนั้นเราก็ตัดสินใจว่าจะ แก้ไขซอฟแวร์ของเราหรือไม่ ขั้นตอนนี้เรียกว่า Defect

หวังว่านักพัฒนาจะเข้าใจรูปแบบการแก้ปัญหาแบบ การพัฒนาซอฟแวร์แบบส่งมอบของก่อน หรือ Delivery-first ได้พอเข้าใจบ้าง จริงๆแล้วมันมาจากการเรียนรู้ที่เราไม่เข้าใจว่าจะทดสอบอย่างไร แต่เรารู้แล้วว่าจะแก้ปัญหาอย่างไรก่อน แล้วจึงมาตรวจสอบให้ได้ว่า ผลลัพย์จากการแก้ปัญหาของเรานั้นมันถูกต้อง เป็นที่น่าพอใจรึยัง แล้วก็กลับไปแก้ไขวิธีการแก้ปัญหาของเราให้ได้ผลลัพย์ออกมาจนกระทั่งเราพอแล้ว แค่นั้นครับ

ปัญหาที่ผมเจอคือ บางครั้งเราก็หาคำตอบได้มาทดสอบ ใช้เขียนทดสอบได้ตั้งแต่ต้น เรียกว่า Test-Driven Development (TDD) แต่บางงานเรายังไม่รู้เลยว่าคำตอบควรจะเป็นอย่างไร แต่เรารู้แล้วว่าผลลัพธ์มันจะออกมาแบบนี้ แล้วผมก็จึงสืบค้นหาคำตอบว่าผลลัพย์มันต้องออกมาเป็นแบบไหน จึงได้เกิด Delivery-first แล้วผมก็เรียกมันใหม่ว่า Delivery-driven Development (DdD) ต่อจากนี้ไปครับ

ขอบคุณครับ

#:P

การพัฒนาซอฟแวร์แบบส่งมอบของก่อน (Delivery-first)

สวัสดีครับ

บล็อคนี้ผมจะมาแชร์ เทคนิคการพัฒนาซอฟแวร์ ที่ผมทำแล้วง่ายกว่า เร็วกว่า และได้คุณภาพเหมือนกับ Test-Driven-Development (TDD) ที่เราๆนิยมใช้กัน

เทคนิคนี้ผมได้ใช้มาตลอดนานแล้ว หลังจากที่ผมเรียนรู้เทคนิค TDD ว่ามันแก้ปัญหาการพัฒนาซอฟแวร์ได้ดี ทุกปัญหาผมแก้ได้โดยง่ายด้วยเทคนิค TDD และผมก็เขียนไว้ในบล็อคผมเองก็หลายที่

จนมาวันหนึ่งผมไม่ได้เขียน Test ก่อนเลย พร้อมทั้งได้งานที่ Refactor code ของระบบเก่าหลายโปรเจ็ค ผมก็ได้ใช้เทคนิคนี้มาตลอด แต่ผมก็ไม่ได้เรียกมันสักที แต่มันไม่ใช่ TDD แน่นอน แต่ก็แนะนำให้นักพัฒนาใช้ TDD ก่อน ก่อนที่จะเข้าใจว่าไม่ต้องเขียน test ก่อนพัฒนาเป็นอย่างไร จนได้เกิดเทคนิคนี้ขึ้น

เทคนิคนี้คือว่าไม่ได้เขียน test ก่อน แต่งานที่ผมทำก็ออกมามีคุณภาพ และรวดเร็ว เหมือนกับ TDD ครับ ทั้งงานสร้างใหม่ และงาน refactor ของเดิม

ผมจึงตั้งชื่อเทคนิคนี้เองว่า Delivery-first หรือ ส่งมอบของก่อน ของในที่นี้ เป็นซอฟแวร์

เทคนิค ส่งมอบของก่อน ขั้นตอนก็แสนง่ายครับ มี แค่ 3 ขั้นตอน คือ Delivery-Detect-Defect หรือเรียกสั้นๆว่า 3D ครับ ไม่ว่าคุณจะมีของอยู่แล้ว หรือ สร้างของขึ้นมาใหม่ ใช้เทคนิคนี้ได้หมดครับ ต่างจาก TDD คือ ไม่ต้องเขียน test ก่อน

ขั้นตอนแรก Delivery คือ ลงมือสร้างของนั้นขึ้นมาเลยครับ ถ้าพร้อมส่งมอบก็ส่งได้เลย ถ้าไม่พร้อม ก็ deploy ขึ้นทดสอบก่อน ไม่ต้องเขียน test เขียน code ขึ้นมาก่อน build แล้ว delivery เลย หรือถ้ามีของอยู่แล้วก็ข้ามไปขั้นที่สองครับ

ขั้นตอนที่สอง Detect คือ ให้เขียน code  เพื่อตรวจจับผลลัพธ์ของสำเร็จที่เราส่งมอบไปจากขั้นแรก แล้วเอาแสดงใหเได้ หรือถ้ามีข้อมูลมาเปรียบเทียบผลลัพธ์ที่เราคาดว่ามันต้องเป็น ก็เอามาสรุปรวมกันไปครับ

และ

ขั้นตอนที่สาม Defect คือ ดูจากผลสรุปที่ได้จาก ขั้นตอน Detect แล้วผลลัพธ์ได้ตามคาดหวัง หรือไม่ได้ หรือว่าเราพอใจ ไม่พอใจแล้ว ก็ให้เรากลับไปแก้ไขการสร้างของ ของเราให้ถูกต้องตรงใจได้เลยครับคือ ย้อนกลับไปที่ขั้นตอน Delivery

ตัวอย่าง-1: ซอฟแวร์ feature ง่ายๆอย่างการ login

  1. Delivery – ผมมีหน้า login อยู่แล้ว ผมก็เข้าไปเล่นได้เลยโดย login username/password ปกติ ถ้าไม่มีก็สร้างมันขึ้นมา แล้ว deploy ให้มีหน้าจอสำหรับ login ใส่ username/password ขึ้นมาเลย ผลลัพธ์คือ ผม login ผ่านเข้าไปเจอหน้าแรกได้
  2. Detect – ผมเขียน automate script สำหรับอ่านข้อมูลการ login ของผมขึ้นมา แล้วดูว่า login ได้หรือไม่ แล้วแสดงเป็นรายงานออกมาให้เห็นได้
  3. Defect- ผมก็ตรวจดูผลลัพธ์จากรายงาน Detect ถ้าได้ผลว่าผม login อยู่ ก็แสดงว่าการส่งมอบของนั้นผ่านครับ

ตัวอย่าง-2: สร้างโครงการใหม่เลยครับคือ บันทึกข้อมูลสินค้าสักอย่างหนึ่ง

  1. Delivery – ผมสร้างหน้าจอบันทึกสินค้าที่สามารถบันทึกสินค้าได้จริงๆ แล้ว deploy สำหรับทดสอบก่อนเลยครับ อย่าเขียน test นะครับ
  2. Detect – ผมเขียน automate script เพื่ออ่านข้อมูลที่บันทึกลงไป แล้วสรุปแสดงผลออกมา ให้เห็นอ่านเข้าใจว่ามีข้อมูลที่ บันทึกลงระบบได้จริงๆ
  3. Defect – ก็ตรวจดูผลลัพธ์จาก Detect ครับว่าถูกต้องมั้ย ถ้าเราอยู่ในขั้นตอน test เราก็มีผลที่คาดหวังสรุปออกมาที่ Detect ได้เลยครับ แล้วก็ดำเนินการแก้ไขกลับไปขั้นตอน Delivery ต่อไป

ขอนำเสนอแค่นี้ก่อนครับ ผมจะเขียน code ให้ดูต่อไป ผมใช้เทคนิคนี้แล้วมันง่ายมาก ไม่ต้องมากังวลว่าของจะไม่เสร็จ และไม่มีคุณภาพครับ เพราะมันเสร็จตั้งแต่ต้น รวมถึงมี Detect เพื่อตรวจดูแล้วแจ้ง Defect กลับมาให้เรากลับไปแก้ไขของที่ Delivery ไปแล้วอยู่ตลอดเวลาเลยครับ

* เพิ่มเติมอ่านคำอธิบาย Delivery-first ด้วยตัวอย่าง ได้ที่ อธิบายการพัฒนาซอฟแวร์แบบส่งมอบของก่อน ด้วยตัวอย่าง (Delivery-first by Example)

ขอบคุณครับ

#:P

 

#agile, #tdd

งาน, กำลัง, ความซับซ้อน และ Story points เพื่อการประมาณ และ วางแผนงาน แบบ Agile

สวัสดีครับ

วันนี้ผมจะมาอธิบายการประมาณค่า เพื่อ วางแผนงานแบบ Agile ที่เรานิยมใช้กัน ก็คือ Story points (SP)

SP ก็คือ ขนาดของงาน หรือ ความต้องการ มันก็คือขนาดของซอฟแวร์ที่เรากำลังพัฒนา

เราใช้ SP เพื่อไปวางแผนปล่อยซอฟแวร์, คำนวนต้นทุนค่าแรงพัฒนาซอฟแวร์ และ ติดตามงานเพื่อให้อยู่ในแผนงานที่เรากำลังทำอยู่

เพราะว่า SP คือ ขนาดของงาน เราจึงสามารถนำงานต่างๆ มาเปลียบเทียบกันได้ โดยไม่ขึ้นกับจำนวนคน หรือความสามารถของคนที่จะมาทำ หรือ ความซับซ้อน ก็คือ ความไม่รู้ของคนที่จะทำงานชิ้นนี้ให้สำเร็จ

ส่วนความสามารถของคน หรือ ทีมที่จะทำงานนี้ให้เสร็จนั้น เรานิยมวัดด้วยขนาดของเวลาที่ใช้ทำงานนั้นจนเสร็จ เรียกว่า ความพยายาม (effort)

ในการอธิบายเรื่อง SP นี้ที่เราพบเจอกันบ่อย เขามักจะอธิบายว่า มันคือค่า ความพยายาม กับ ความซับซ้อนของงาน ซึ่งกำหนดแบบนี้ก็ไม่ผิด เดี๋ยวผมจะอธิบายต่อไปว่า SP, ความพยายาม และ ความซับซ้อนของงาน มันแตกต่างกันอย่างไร แล้วความแตกต่างกันตรงนี้ทำให้ SP นั้นเกิดความผิดพลาด (error) ในการวางแผนงานกันในทีมได้อย่างไร

ทำไม SP ถึงถูกอธิบายเป็นแบบนั้น ผมขออธิบายด้วย สมการงาน ครับ สมการงาน กำหนดได้แบบนี้

Work(W) = Force(F) x Distance(d)

Work ก็คือ งาน ซึ่งเทียบได้กับ ความต้องการ หรือ คุณสมบัติของซอฟแวร์ที่ต้องการ (feature) หรือ issue

Force ก็คือ กำลัง ซึ่งเทียบได้กับ ความพยายาม ที่คนในทีมใช้ทำงานนั้นจนสำเร็จ และ

Distance ก็คือ ระยะทาง ซึ่งเทียบได้กับขนาดของงาน หรือ SP

ตัวอย่าง เมื่อเราประมาณงานหนึ่งชิ้น (Work item) สมมิว่า คือ w1

เรามีทีมพัฒนาที่มีสมาชิกอยู่ 2 คน คือ p1 และ p2 ให้ทั้งสองคนประมาณงาน w1 นี้

p1 ประมาณ ใช้เวลา 1 วัน และ p2 ประมาณ ใช้เวลา 2 วัน

ถ้าเราใช้ กำลังเป็นหน่วยวัดของงานแล้ว เราจะพบว่า งานแบบเดียวกันขนาดมันไม่มีทางเท่ากันได้เลย เป็นเพราะอะไร

แทนค่าประมาณงานของทั้งสองคนลงไป ในสมการงาน เราจะได้

w1 = F * 1 — สำหรับ p1

w1 = F * 2 — สำหรับ p2

เราจะพบว่า การทำให้ w1 เหมือนกันให้ได้นั้น p1 ต้องออกแรง (F) มากกว่า p2 เป็น 2 เท่า แต่การที่เราเอาเวลา มาเป็นตัววัดขนาดงาน ให้แต่ละคนในทีมเห็นเหมือนกันนั้นเป็นไปไม่ได้เลย ดังนั้น จึงไม่แปลกว่าทำไมการจัดการวางแผนมีความผิดพลาด (error) อยู่เสมอ เช่น ปัญหาในการวางแผนงานโดยประมาณแบบผู้เชี่ยวชาญ ว่างานชิ้นนี้ คนนี้ใช้ 1 วันทำเสร็จ ดังนั้น งานชิ้นนี้ให้ใครทำก็จะต้อง 1 วันเสร็จเหมือนกัน ซึ่งเราจะเห็นว่า มันเป็นไปไม่ได้ในทุกกรณี หรือ ความเข้าใจที่ว่า งานชิ้นนี้คนเดียวทำ 1 วัน ถ้าเราใช้ 2 คนก็ต้องเสร็จได้ภายในครึ่งวันแน่นอน ความเข้าใจทั้งหมดนี้เกิดมากจาก การใช้ Effort กับ Complexity ประมาณขนาดของงาน ซึ่งทั้งสองค่านี้มีความสัมพันธ์กับทีมพัฒนาแต่ละคน ผมก็จะอธิบายในลำดับต่อไป

ดังนั้การจัดการแบบ Agile จึงเลือกใช้ SP มาเป็นตัวประมาณค่าขนาดของงาน เพื่อนำมาวางแผนงานทำงานต่อไป นั่นเอง

เทคนิคที่นิยมใช้ประมาณขนาดของงาน เช่น Planning poker, Swimlane Sizing

พอเราได้ขนาดมาแล้ว เราก็ให้ทีม มาประมาณกำลัง หรือเวลาทำงานชิ้นนั้นอีกที

ทีนี้มาถึงความซับซ้อนของงาน ทำไมมีหลายๆคน อธิบายว่า SP คือ ความพยายาม กับ ความซับซ้อน ได้

ผมจะยกตัวอย่าง งานเหมือนเดิม ในทีมพัฒนา มีสมาชิก 2 คนคือ p1 และ p2 ให้ประมาณงาน w1 เหมือนกัน ได้ดังนี้

p1 ประมาณงาน w1 ได้ 1 วัน และ p2 ก็ประมาณงาน w1 ได้ 2 วัน

w1 = F * 1 — สำหรับ p1

w1 = F * 2 — สำหรับ p2

จากสมการงาน เรารู้แล้วว่า F ของ p1 ไม่เท่ากับ p2 แต่ทำไมถึงไม่เท่ากัน และทำอย่างไรถึงจะให้เท่ากันได้ เพื่อให้เห็นขนาดของงาน w1 นี้ตรงกัน

ทีนี้ผมจะพยายามทำให้ ทั้งสองคนนี้ใช้กำลังเท่ากัน เพื่อใช้อธิบายเรื่องความซับซ้อนของงาน โดยเลือกเอาค่าน้อยที่สุด เพื่อให้งานเสร็จไวครับ ก็คือ 1 วัน

ถ้า ผมให้ F เท่ากับ Effort (e) คือ ความพยายาม คูณกับ Knowledge (k) ความรู้ในงาน จะได้สมการงานใหม่ เป็น

F = k x e

นำไปแทนค่าใน สมการงาน ได้เป็น

W = k x e x d

โดยที่ k มีค่าระหว่าง 0 ถึง 1 คือ

ถ้าค่า k เท่ากับ 0 ไม่รู้เรื่องเกี่ยวกับงานนั้นเลย, ถ้าค่า k เท่ากับ 1 คือมีความรู้ในงานนั้นอย่างสมบูรณ์ ถ้าค่า k อยู่ระหว่าง 0 กับ 1 ก็คือ มีความรู้ในงานตามส่วนไป เช่น 0.5 หรือ รู้ 50% เป็นต้น (ซึ่ง 1-k มีความสัมพันธ์ทางบวกกับ technical debt )

ถ้าผมให้ Complexity (c) หรือ ความซับซ้อน คือความไม่รู้เกี่ยวกับงาน ดังนั้น c จะเท่ากับ 1 – k นั่นเอง

ดังนั้น ถ้าผมให้ p1 มี k เท่ากับ 1 คือ มีความรู้สมบูรณ์ และ p2 มี k เท่ากับ 0.5 หรือรู้ในงานนี้ 50% แทนค่าในสมการงาน ได้เป็น

w1 = e * 1 * 1 — สำหรับ p1

w1 = e * 0.5 * 2 — สำหรับ p2

เราจะพบว่าสมการงาน มีค่าเท่ากันได้ โดย p1 และ p2 ในความพยายาม (e) เท่ากัน แต่ความรู้ของทั้ง p1 และ p2 ไม่เท่ากัน

และนี่ก็คือที่มาว่า ทำไมถึงมีคนอธิบายว่า Story points ที่ใช้เป็นหน่วยวัดงาน นั้น มันก็คือ ความพยายาม (e) กับ ความซับซ้อน (c) เพราะว่า พวกมันมีความสัมพันธ์กัน แต่ไม่เกี่ยวข้องกัน แต่ส่งผลทำให้เกิดงาน นั้นเอง

ก่อนไป ผมมีอีกสามาการหนึ่งที่สามารถนำมาใช้อธิบายความสัมพันธ์ระหว่าง งาน, กำลัง, ความซับซ้อน และ Story points ก็คือ สมการมูลค่า ที่ว่า

Value = Price x Quantity

ผมเทียบกับงานซอฟแวร์ แล้วอธิบายได้ว่า

Price เท่ากับ Effort x Knowledge และ Quantity ก็คือ Story points ก็ได้เช่นเดียวกับ สมการงาน ครับ

ผมก็ขออธิบายไว้เพียงเท่านี้ หวังว่าจะช่วยให้เข้าใจ Story points เพื่อใช้ในประมาณค่า เพื่อวางแผนงาน ที่เข้าใจได้ชัดเจนขึ้นต่อไปครับ

ขอบคุณครับ

#:P

หนังสือแนะนำอ่านเพิ่มเติม

 

 

สร้าง chart ด้วย d3js กับข้อมูลธนาคารแห่งประเทศไทย ผ่าน Web API อย่างง่าย

สวัสดีครับ

บล็อคนี้ ผมมานำเสนอ การสร้าง chart เพื่อแสดงข้อมูลทางเศรษฐกิจ ที่ได้จาก Web API ของ ธนาคารแห่งประเทศไทย (BOT) ที่เปิดให้ใช้ครับ

ตอนนี้บริการ Web API ที่เปิดให้ มีข้อมูลเกี่ยวกับ อัตราแลกเปลี่ยน, อัตราดอกเบี้ย และ ผลการประมูลตราสารหนี้ อ่านรายละเีอยด ได้ที่นี่ครับ https://iapi.bot.or.th/Developer?lang=th

ตัวอย่าง ที่ผมจะแสดง คือ เป็นแอพ Web แสดง line chart หรือ กราฟเส้น ของข้อมูล อัตราแลกเปลี่ยนเฉลี่ย (รายวัน) แบบนี้

แกนนอน แสดงเวลา แกนตั้งแสดง อัตราแลกเปลี่ยน บาท THB ต่อ ดอลล่าสหรัส USD และ ที่ text box ให้ผู้ใช้ใส่ ปี และ เดือน ลงไป พอกด select ก็จะส่ง ปี และ เดือน ไปเอาข้อมูลจาก BOT Web API มาแล้วแสดงผล เป็น line chart ตามรูปครับ

เริ่มสร้างกันเลย

1. สร้าง html file ขึ้นมาครับ ตั้งชื่อว่า DAILY_AVG_EXG_RATE.html เติม code html เริ่มต้นเข้าไปก่อน แบบนี้ครับ

อธิบาย ดูกล่อง สีเขียว ครับ คือผมเพิ่ม library jQuery สำหรับช่วย query element DOM html ให้ สะดวกดี และ d3js  ใช้สำหรับสร้าง chart แสดงผลข้อมูล

2. เพิ่ม style sheet สำหรับ แสดงเส้น grid บน chart เพื่อให้อ่าน chart ได้ง่ายขึ้น เติม code CSS นี้ต่อจาก script ครับ

3. เพิ่ม javascript file ครับตั้งชื่อ BotChartPlayer.js เพิ่ม code สำหรับ สร้าง chart และ เรียกข้อมูลผ่าน Web API ของแบงค์ชาติครับ แบบนี้

// BotChartPlayer.js
function BotChartPlayer(opts) {

opts = opts || {};
opts.elementID = opts.elementID || ‘chart’;
opts.width = opts.width || 1000;
opts.height = opts.height || 450;
opts.dateFormat = opts.dateFormat || ‘%Y-%m-%d’;
opts.x_column = opts.x_column || ‘date’;
opts.y_column = opts.y_column || ‘value’;
opts.uri = opts.uri || ‘https://iapi.bot.or.th/Stat/Stat-ExchangeRate/DAILY_AVG_EXG_RATE_V1’;

var dataSet = {};

var svg = d3.select(‘#’ + opts.elementID)
.append(“svg”)
.attr(“width”, opts.width)
.attr(“height”, opts.height),
margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = +svg.attr(“width”) – margin.left – margin.right,
height = +svg.attr(“height”) – margin.top – margin.bottom,
g = svg.append(“g”).attr(“transform”, “translate(” + margin.left + “,” + margin.top + “)”);

var parseTime = d3.timeParse(opts.dateFormat),

x = d3.scaleTime().rangeRound([0, width]),

y = d3.scaleLinear().rangeRound([height, 0]);

var line = d3.line()
.x(function (d) { return x(parseTime(d[opts.x_column])); })
.y(function (d) { return y(+d[opts.y_column]); });

g.append(“g”)
.attr(“class”, “axis-bottom grid”);

g.append(“g”)
.attr(“class”, “axis-left grid”)
.append(“text”)
.attr(“fill”, “#000”)
.attr(“transform”, “rotate(-90)”)
.attr(“y”, 6)
.attr(“dy”, “0.71em”)
.attr(“text-anchor”, “end”)
.text(“THB / USD”);

g.append(“path”)
.attr(“class”, “line”)
.attr(“fill”, “none”)
.attr(“stroke”, “steelblue”)
.attr(“stroke-linejoin”, “round”)
.attr(“stroke-linecap”, “round”)
.attr(“stroke-width”, 1.5);

var display = function (data) {

x.domain(d3.extent(data, function (d) {

return parseTime(d[opts.x_column]);

}

));
y.domain(d3.extent(data, function (d) {

return +d[opts.y_column];

}));

g.select(“.axis-bottom”)
.attr(“transform”, “translate(0,” + height + “)”)
.transition()
.call(d3.axisBottom(x).ticks(10).tickSize(-height))
.select(“.domain”)
.remove();

g.select(“.axis-left”)
.transition()
.call(d3.axisLeft(y).ticks(10).tickSize(-width));

g.select(“.line”)
.datum(data)
.transition()
.attr(“d”, line);

}

var pad = function (data, size) {

var s = String(data);
while (s.length < (size || 2)) { s = “0” + s; }
return s;

}

return {

play: function (year, month) {
var d = new Date(year, month, 0),

period = year + ‘-‘ + pad(parseInt(month), 2),

criteria = “start_period=” + period + “-01&end_period=” + period + “-” + d.getDate() + “&currency=USD”;

if (dataSet[period] == undefined) {
d3.json(opts.uri + “/?” + criteria)
.header(‘api-key’, ‘U9G1L457H6DCugT7VmBaEacbHV9RX0PySO05cYaGsm’)
.get(function (error, root) {
if (!error) {
var result = root.result;
if (result.success) {

var data = result.data.data_detail;
dataSet[period] = data;
display(dataSet[period]);

}
}
})
} else {

display(dataSet[period]);

}

}
};

}

// end of file BotChartPlayer.js

4. สุดท้ายครับ เติม code ให้สมบูรณ์ไปที่ file DAILY_AVG_EXG_RATE.html เพื่อแสดงผลของเรา ตามนี้ครับ

 

ขออธิบายหน่อยนึง

กล่องสีเขียว คือ form สำหรับผู้ใช้ ใส่ input ปี, เดือน และ คำสั่ง Select เพื่อแสดง chart

กล่องสีส้ม คือ ส่วนพื้นที่เพื่อใช้แสดง chart โดยเมื่อผู้ใช้กดคำสั่ง Select แล้วแอพจะ ไปเรียก Web API เพื่อเอาข้อมูลมา สร้าง chart โดย class file BotChartPlayer.js ที่ load เข้ามาในบรรทัดที่สอง

กล่องสีน้ำเงิน คือ ขั้นตอนสร้าง BotChartPlayer ใช้สร้าง chart และ เรียก Web API กับขั้นตอน นำค่า ปี และ เดือน เพื่อส่งไปให้ Web API สำหรับช่วงเวลาข้อมูลอัตราแลกเปลี่ยน

เสร็จแล้วครับผม ทีนี้ก็ลองเล่นดูครับ โดยเปิดด้วย browser ปกติครับผม ถ้ามีเวลาผมก็จะค่อยๆเพิ่มการแสดงผล chart สำหรับ BOT Web API อื่นๆที่เหมาะสมต่อไปครับ ตาม source code ได้ที่ ChartBOT

ตัวอย่าง Bar chart ข้อมูลจาก BOT Web API อัตราดอกเบี้ยเงินให้สินเชื่อของธนาคารพาณิชย์ (อัตราร้อยละต่อปี)

ขอบคุณครับ

#:P