/ BasicKnowledge

Event-Driven Architecture

Event-driven architecture หรือ EDA นั้นเป็นรูปแบบหนึ่งของการเขียนโปรแกรมสมัยใหม่ จริงๆ มันก็ไม่ได้ใหม่อะไรหรอก เราได้ใช้กันอยู่บ่อยๆ โดยที่ไม่รู้ตัวมากกว่า


Event-driven architecture แปลเป็นไทยตรงๆ ว่า "สถาปัตยกรรมแบบขับเคลื่อนโดยเหตุการณ์" โดย EDA นี้ นำเสนอเกี่ยวกับการสร้าง การตรวจสอบ การใช้งาน และการทำปฏิกิริยากับเหตุการณ์ที่เกิดขึ้น

ในอดีตการเขียนโปรแกรมนั้นจะเขียนแบบตรงไปตรงมาจากบนลงล่าง ทำไปทีละบรรทัดๆ ในเวลาต่อมาก็เริ่มมีโครงสร้างแบบต่างๆ ให้ใช้ จนถึงในยุคของ OOP การเขียนโปรแกรมก็มีท่าประหลาดๆ พิศดารมากขึ้น เราไม่ต้องรอให้โปรแกรมทำคำสั่งบรรทัดนี้จนเสร็จแล้วค่อยทำบรรทัดต่อไป แต่เราสามารถข้ามไปทำอย่างอื่นต่อ โดยไม่ต้องสนใจบางคำสั่งที่ยังรันไม่เสร็จก็ได้ หลายๆ ท่านอาจจะเคยได้ยินศัพท์คำสว่า Blocking I/O หรือ Synchronous I/O ซึ่งมันหมายถึงการหยุดรอระหว่างทำ I/O อยู่ ซึ่งแปลว่าเราปล่อยหน่วยประมวลผลให้ว่างจนกว่า I/O นั้นๆ จะทำสำเร็จ ต่อมาจึงได้มีคอนเซ็ปต์ของ Non-Blocking I/O คือเมื่อมีการทำ I/O ที่ใช้ระยะเวลานาน เช่น การอ่านเขียนข้อมูล การสื่อสารกับเน็ตเวิร์ค ก็ปล่อยให้มันทำไป แต่ในขณะเดียวกัน เราเลือกที่จะให้หน่วยประมวลผลไม่ต้องหยุดรอ แต่ข้ามไปทำสิ่งต่อไปได้เลย นั่นหมายความว่า Flow หลักของเราจะไม่รู้เลยว่า I/O ที่ปล่อยไปนั้นจะเสร็จเมื่อไหร่ ดังนั้น Callback หรือ Event จึงถูกนำมาใช้เพื่อแก้ปัญหาเหล่านี้

อีกเรื่องที่ทุกท่านอาจจะคุ้นเคยก็คือเรื่องของ GUI การใช้งาน UI พื้นฐาน เช่น Button นั้นก็ใช้หลักการของ Event/Callback ทั้งสิ้น เมื่อเราพยายามเอาเม้าส์ไปชี้ที่ Button ก็จะเกิดเหตุการณ์ที่เรียกว่า Hover ออกมาจาก Button นั้น เมื่อเราคลิกลงไป ก็จะเกิด MouseDown เมื่อปล่อยคลิกก็เกิด MouseUp และ Click ทั้งหมดเป็น Event ทั้งสิ้น เพียงแต่เราอาจจะใช้มันโดยไม่รู้ตัวนั่นเอง

ที่กล่าวมาข้างต้นนั่นก็เป็นบางรูปแบบของการนำ Event-Driven มาใช้งานในโปรแกรม แต่เคยรู้หรือไม่ว่าในภาษาส่วนใหญ่เราสามารถสร้าง Event เองได้ ผมอยากให้คุณจินตนาการว่าคุณเป็นคนสร้างระบบเกมออนไลน์ขนาดใหญ่ คุณลองคิดดูครับขณะที่ User เข้าระบบมา จะทำอย่างไรให้ User อื่นรู้ ให้เวลาคิด 5 4 3 2 1 0 หมดเวลาครับ คำตอบง่ายๆ ก็คือ Broadcast Event ออกไปครับ

เรามาดูตัวอย่างง่ายๆ ของการส้ราง Event กันดีกว่าครับ โดยครั้งนี้ราจะมาลองพิมพ์ข้อมูลออกบน Console หลายๆ อย่าง พร้อมๆ กัน อย่างละ 10,000 ครั้ง และถ้าอันไหนเสร็จก็ให้ส่ง Evnt เตือนคลาสหลักได้เลย ผมขอใช้ Java ในการอธิบาย

สร้าง Event Listener กันก่อน เพื่อใช้ในการดักฟัง Event และประกาศ Event ตามแบบของ Java

public interface TenThousandTimesPrinterEventListener {
	public void onCompleted(String string);
}

ต่อมาสร้างคลาสสำหรับพิมพ์ข้อความหนึ่งหมื่นครั้ง

public class TenThousandTimesPrinter 
{
	List<TenThousandTimesPrinterEventListener> listeners;
	public TenThousandTimesPrinter()
	{
		listeners = new ArrayList<TenThousandTimesPrinterEventListener>();
	}

	public void print(String string)
	{
		new Thread(new TenThousandTimesPrinterRunnable(string)).start();
	}

	public void addListener(TenThousandTimesPrinterEventListener listener)
	{
		listeners.add(listener);
	}

	class TenThousandTimesPrinterRunnable implements Runnable
	{
		private String stringForPrint;
		public TenThousandTimesPrinterRunnable(String string)
		{
			stringForPrint = string;
		}
	
		public void run() 
		{
		
			for(int i=0;i<10000;i++)
			{
				System.out.println(stringForPrint);
			}
		
			for(TenThousandTimesPrinterEventListener listener : listeners)
			{
				listener.onCompleted(stringForPrint);
			}
		}
	}
}

ในคลาส TenThousandTimesPrinter จะมี List ของ Listener ที่สร้างไว้ข้างต้น เพื่อใช้ส่ง Event ออกไป อีก method ที่สำคัญคือ print ซึ่งรับตัวแปรเป็นค่าที่ต้องการพิมพ์ หลังจากนั้นมันจะสร้าง Thread ขึ้นมาเพื่อพิมพ์คำนั้น 10,000 ครั้ง ก่อนที่จะเรียกใช้ Listener ทีละตัวเพื่อบอกว่าเสร็จแล้วนะ TenThousandTimesPrinterRunnable เป็นส่วนกลไกของ Thread ที่ผมอธิบายไปเมื่อกี้

หลังจากนั้นเราจะลองเอาคลาสนี้ไปใช้งานกันเลย

public class MainClass 
{
	public static void main(String [] args)
	{
		TenThousandTimesPrinter printer = new TenThousandTimesPrinter();
	
		TenThousandTimesPrinterEventListener listener = 
        new TenThousandTimesPrinterEventListener() {
			public void onCompleted(String string) {
				System.out.println("print "+string+" completed");
			}
		};
	
		printer.addListener(listener);
		printer.print("Hello");
		printer.print("World");
		printer.print("This");
		printer.print("Is");
		printer.print("Event");
		printer.print("Example");
	}
}

MainClass นี้จะทำการส้รางเครื่องพิมพ์ TenThousandTimesPrinter ขึ้นมา แล้วก็ใส่ Listener ไว้ ถ้าพิมพ์ อะไรก็ตาม ครบ 10,000 ครั้ง ให้พิมพ์ว่า "print ข้อความ completed" หลังจากนั้นเราก็ลงพิมพ์ค่าต่างๆ หลายๆ ค่า คือ Hello, World, This, Is, Event, Example

หลังจากรันคลาสนี้จะพบว่ามันไม่รอให้พิมพ์ Hello เสร็จแล้วค่อยพิมพ์ World แต่พิมพ์ไปพร้อมๆ กันทุกอันเลย นี่คือคุณสมบัติของ Thread ซึ่งเราไม่ได้สนใจ สิ่งที่เราสนใจคือ เมื่อเวลาผ่านไปสักพักก็เริ่มมี print ... completed มาเป็นระยะๆ นั่นแหละครับ Event onCompleted ของพวกเราทำงานถูกต้อง

ในภาษาใหม่ๆ เริ่มมีการเอา Pattern นี้ไปใช้เป็น Feature ของภาษาเลย เช่น C# ซึ่งมีคำสั่งสำหรับสร้าง Event โดยเฉพาะ

ในสเกลที่ใหญ่ขึ้นเราจะต้องคิดว่าใครควรจะได้รับ Event ไหนอย่างไร เริ่มมีการ Implement เรื่อง Channel และ Subscribe/emit เพื่อที่จะเลือกว่าต้องส่ง Event ให้ใครบ้าง