Dependency Inversion

Dependency คืออะไร

ไม่รู้จะเขียนอะไรจะเขียน Angular4 ก็ยังไม่เคยลอง จะ Webpack ก็เขียนไปแค่ Project เดียว แถมยังใช้ angularjs อยู่ด้วยเลยยังไม่เอามาเขียน ตอนนี้เลยขอกินบุญเก่าเอาเรื่องที่เรียนตอนปี 4 วิชา OOAD (อีกแล้ว) มาเขียนดีกว่า

Dependency คือ เอ่อ… อธิบายยากเหมือนกันนะ ไม่รู้จะอธิบายยังไงให้กลางและเข้าใจง่าย ขอเป็นอธิบายในมุมมองของผมละกัน Dependency เหมือนกับว่าถ้าเราจะใช้ Module นี้เราจะต้องทำอะไรบ้าง เช่น ต้องส่งตัวแปรเข้าไปยังไง ต้องมี Environment แบบไหน ต้องมีเปิด Permission อะไร และอื่นๆที่ทำให้เราสามารถใช้ Module นั้น อ่าถ้าจะให้ยกตัวอย่างก็ประมาณ LEGO อะ ถ้าเราอยากต่อกับ LEGO 4 หัวทิ่มและก็ต้องมี LEGO ที่มีรูให้เสียบ 4 รูตรงกับ LEGO ที่เราจะไปต่อ หรือ ถ้าเป็นนักเล่นเกมส์อยากเล่นเกมส์ก็ต้องไปตรวจสอบ Spec คอมว่ามันต้องลงอะไรบ้าง บางทีต้องไปโหลดส่วน Lib C++, DirectX8, 9, 10 มาลงบ้างเพื่อให้สามารถเล่นเกมส์ได้

เวลาเปลี่ยนอะไรก็เปลี่ยน

วันหนึ่งกำลังเขียน Code แล้วมีคนเดินเข้ามาแล้วบอกว่า “ต้องเปลี่ยนตรงนี้นะ” , “เปลี่ยน DBMS” , “ลูกค้าอยากได้…เลยต้องเปลี่ยนไปใช้…” , “เข้าไม่อยากใช้ Rest เขาอยากใช้ FTP” ในชีวิตคนสายนี้จะต้องได้ยินคำพูดพวกนี้มาบ้างไม่มากก็น้อย มันก็ตลกในหลายๆครั้งที่อาชีพที่เราทำมัน อยากจะเปลียนอะไรก็เปลี่ยนกันง่ายอะไรขนาดนัน อาจเป็นเพราะอาชีพที่เราทำมันเพิ่งมีขึ้นมายังไม่ถึง 100 ปี อะไรๆมันก็เลยไม่นิ่งไม่มีอะไรตายตัวเมื่อเทียบกับสายงานสร้างอื่นๆไม่ว่าจะโยธา เครื่องกล หรืออย่างอื่นที่ศาสตร์ของเขามีมานานมีการลองผิดลองถูกจนได้วิธีการที่ดีที่ถูกต้องแล้ว หรือจริงๆแล้วงานของพวกเราต้องยอมรับกับความเปลี่ยนแปลงได้เสมอ

เมื่อเปลี่ยนก็ต้องแก้

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

Dependency Inversion

เป็นตัวย่อ D จากกฏ SOLID ตัว Dependency Inversion ว่าง่ายๆคือ …. เอออธิบายยากเหมือนกันว่ะ เอาเป็นยกตัวอย่างละกัน ผมเคยเขียน Web appliction ตัวนึงตอนนั้นพัฒนาโดยใช้ Database เป็น NoSQL รูปแบบหนึ่ง คราวนี้ด้วยความอ่อนด้อยด้านประสบการณ์ เวลาเข้าถึงข้อมูลผมก็เขียนแบบให้มันต่อ Database ตรงๆเลย ทำอย่างรวดเร็วว่องไว Project นี้ก็จบไป ผ่านไปไม่นาน คนที่รับ Project นี้ไปทำต่อมาพูดคุยว่าต้องเปลี่ยนจาก NoSQL เป็น SQL เนี่ยจะต้องแก้อะไรบ้าง ตอนนั้นนี่บอกเลยว่าแก้เยอะมากๆ เยอะแบบเยอะที่สุด แถบจะทุกที่เลยเพราะว่าเราเขียนต่อ Database โดยตรงไปทุกส่วน ใจผมตอนนั้นนี่บอกเลยว่า เขียนใหม่อาจจะง่ายกว่าเสียด้วยซ้ำ พออ่านถึงตรงนี้พอจะเริ่มเข้าใจเล็กน้อยแล้วใช่ไหมครับ

ภาพแสดงการ Design ที่ dependency กับ DBMS

ปัญหาของ Code ที่ผมเขียนคือผม Design ออกแบบข้อมูลโดยอ้างอิงจาก DBMS เป็นหลัก พอได้ปุ๊ปก็ออกแบบ Data model ตาม DBMS connection lib return ออกมาให้แบบเพียวๆไปจากนั้น
ก็ให้ส่วน Business logic มีวิธีเรียกใช้ DB และเห็นข้อมูลตาม Data Model จะเห็นว่าอะไรก็ไปขึ้นอยู่กับ DBMS connection lib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ตัวอย่าง Code ที่เขียนด้วย NodeJs

/* RpgUserObject จะประมาณนี้
{
_id : String
name : String
username : String
password : String
item : [Object]
}
*/

// ใน Controller สักที่หนึ่งจากหลายล้านที่

function handleFindRpgUser(req, res) {
var _id = req.headeer("_id");
db.rpgUser.findOne({_id: _id }, function(err, result) {
if (err) {
res.status(500).send("Internal server error");
} else {
res.json(result);
}
});
}

พอมันเกิดอะไรเปลี่ยนแปลงกับ DBMS ไม่ว่าจะเป็นเปลี่ยน (อันนี้ยากแต่ถ้าเปลี่ยนล่ะก็) แต่ที่ผมโดนมาคือ DBMS connection lib มันเปลี่ยน version แล้วเขาก็เปลี่ยนการรูปแบบการ Return แปลว่ามันจะเกิดอะไรขึ้น ทุกส่วนที่ยุ่งเกียวกับ DBMS connection lib จะต้องมีการแก้ไขแน่นอน ดูเส้นได้เลย ผมเลยบอกว่าถ้ามานั่งไล่แก้ซึ่งไม่รู้กี่ที่ กระทบกี่ตำแหน่ง ต้อง Test ใหม่อาจจะ Test ทั้งหมด ถ้าใช้ Automate ก็โชคดีไปแต่ถ้ามีอเปล่าก็…. ไปนึกภาพเอาเองละกัน

กลับหัว

ภาพแสดงการ Design ที่ dependency กับ DBMS

หลังจากเกิดปัญหานี้ผมก็ลองไปค้นหนังสือสมุดที่เคยเรียน หาใน internet ว่ามีวิธีการดีๆอะไรบ้าง จนเจอเรื่อง SOLID แล้วก็เห็นว่าคนส่วนใหญ่เจอปัญหาแบบเดียวกันหมด เขาเลยมาตั้งเป็นหลักการ จากการอ่านคือ ผมไปเขียน Code ไปยึดติดกับส่วนที่เป็น low level ในส่วนที่อธิบายให้เราแก้ Design ทำการสร้างส่วนที่เป็น Abstract หรือ Layer ขึ้นให้มันบอกแค่มันทำอะไรได้ return อะไร อย่างของผมสร้างเป็น Data layer ผมกำหนดไปเลยว่า Data model หน้าตาเป็นยังไง มันสามารถค้นหาด้วยอะไรบ้าง ส่วนวิธีค้นหาเป็นยังไงผมไม่บอกรู้แค่ว่าไปหามาให้ได้ก็พอ หรือง่ายๆเวลา Design ให้พยายาม Design ให้เป็นอะไรแบบ หน้าตาเป็นยังไง เรียกแล้วออกมาได้อะไร ส่วนวิธีการเป็นหน้าที่ของ low level เป็นคนพยายามทำให้ได้ตามรูปแบบที่ hight level กำหนดไว้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ตัวอย่าง Code ที่เขียนด้วย NodeJs

///ไฟล์ RpgUserDataLayer.js
exports.findById(_id, callback) {
// implement how to get data from database
db.rpgUser.findOne({_id: _id }, callback );
};

/// ใน Controller จากหลายล้านที่
function handleFindRpgUser(req, res) {
var RpgUserDataLayer = require("path/to/RpgUserDataLayer.js");

RpgUserDataLayer.findById(req.header("id"), function(req, res) {
if (err) {
res.status(500).send("Internal server error");
} else {
res.json(result);
}
});
}

ไม่เห็นมีไรเปลี่ยนแปลงแถมซับซ้อนขึ้นอีก “หลอกกันนี่นา”

ครับซับซ้อนขึ้นแน่นอนครับ แต่เมื่อเกิดการเปลี่ยนแปลงที่ชั้น DBMS เช่น เปลี่ยนยี่ห้อ เปลี่ยนการ Design model อันนี้ผมยกตัวอย่างเปลียนเป็น SQL จะเกิดอะไรขึ้น สิ่งที่ผมต้องไปแก้ก็แค่ที่เดียวคือ RpgUserDataLayer.js ส่วนที่ Controller อีกหลายล้านที่ผมก็ไม่ต้องแก้เพราะ Data layer ผมไม่ได้เปลี่ยนรูปแบบการรับส่งข้อมูล

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ตัวอย่าง Code ที่เขียนด้วย NodeJs

///ไฟล์ RpgUserDataLayer.js
exports.findById(_id, callback) {
// implement how to get data from database
connection.query("SELECT * FROM RpgUserTable WHERE _id = '" + _id + "'", function (err, resultUser) {
if (err) {
callback(err, null);
} else {
connection.query("SELECT * FROM RpgUserItemTable WHERE _id = '" + _id + "'", function (err, resultUserItem) {
resultUser.item = resultUserItem;
callback(err, resultUser);
});
}
}
};

สรุปเลยดีไหม น่าจะยาวเกินไปละ

ส่วนสรุปเป็นส่วนที่เขียนยากมากๆเลยนะไม่รู้ว่าจะสรุปยังไง เอาเป็นว่า Dependency Inversion คือการกลับความสัมพันธ์ Dependency จากที่เราไปยึดติดขึ้นตรงต่อกับชั้น Low level เราควรกลับหัวความสัมพันธ์ เราควรให้ส่วน Low level ขึ้นตรงกับ Hight level เหมือน code ที่ผมใช้ตัว Low level คือ DBMS connection lib ที่ต้องพยายามเขียน พยายามหาวิธีดึงข้อมูลออกมาให้ตรงกับรูปแบบ Data layer ที่ผม Design ไว้

แหล่งอ้างอิงและเรื่องที่น่าไปอ่านต่อ :
https://en.wikipedia.org/wiki/Dependency_inversion_principle
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
https://en.wikipedia.org/wiki/Coupling_(computer_programming)
https://en.wikipedia.org/wiki/Software_design_pattern
https://sourcemaking.com/

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