การออกแบบ Software Architecture Desing นั้นมีความสำคัญอย่างมากในการออกแบบ งานหนึ่งใน SWA สามารถสร้างวิธีการที่ทำให้คนเข้าใจกับระบบได้ง่ายและเร็ว ซึ่งเร็วกว่าการอ่านทั้งหมดด้วยตัวเอง

การจัดกลุ่มต้อง

  • Make sense ทำให้เข้าใจง่ายขึ้น
  • มีประโยชน์ และอธิบายได้

Make Sense กับใคร?

เราสามารถจัดกลุ่มของ Architecture ได้ออกเป็น 3 กลุ่มใหญ่ คือ

  • Speciallist Collaboration
  • Business Collaboration
  • Organization Collaboration

Specialist Collaboration

N-Tier Architecture

เป็นวิธีการจัดกลุ่ม class ต่างๆ เข้าด้วยกัน แบบหนึ่ง ซึ่งเราจะต้องแบ่ง App ออกมาเป็น Leyer โดยจะแบ่งออกเป็นกี่ Leyer ก็ได้ ไม่ได้มีการกำหนดไว้ตายตัว

N-Tier ที่นิยม เช่น MVC, Clean Architecture, Java EE เป็นต้น

ดังนั้น ถ้าหากเราใช้ N-Tier เราจะแบ่ง class ออกมาเป็นกลุ่มๆ

ถึงแม้จะบอกว่า N-Tier ไม่มีการกำหนดวิธีใช้งานไว้ แต่ตัวมันเองก็มี กฎอยู่หนึ่งข้อ ที่ปฏิบัติกัน คือ Leyer ภายใน ห้ามใช้ Leyer ภายนอก คือ Tier 1 สามารถเรียกใช้งาน Tier 2 ได้ แต่ Tier 2 ห้ามไปเรียกใช้งาน Tier 1 ดังภาพ

ตัวอย่าง
การใช้งาน Architecture แบบนี้ ที่เราน่าจะคุ้นเคยกัน คือ Web Application Structure ที่มีการแบ่ง Handler, Service, Repository ออกเป็นชั้นๆ โดยแต่ละชั้นจะเรียกใช้งาน Tier ที่ต่อๆ ไป แต่จะไม่กลับไปเรียกใช้ Tier ข้างบนตัวเอง

ถ้าเก่าๆ หน่อย คือ 3 Tier Architecture ที่แบ่งออกเป็น Presentation, Logic, Data

ประโยชน์ในการใช้ N-Tier
การแบ่ง dependency tracking เรารู้ว่าใคร (Tier/Layer) สารมารถเรียกใช้ใครได้ และใครเรียกใช้ใครไม่ได้ การที่เรารู้ dependency tracking นั้น ช่วยให้เรารู้ผลกระทบ ที่จะตามมา เมื่อมีการแก้ไข code ในระบบ ซึ่งสิ่งนี้เป็นสิ่งที่สำคัญมากๆ ใน codebase ขนาดใหญ่ ที่เราไม่สามารถทดสอบทั้งหมดได้

เช่น สมมติเราต้องการเพิ่ม rate limit ที่อยู่ในระดับของ handler เราก็จะรู้ได้ในทันทีว่า ถ้าเราแก้ไขใน handler มันจะไม่กระทบใน tier หรือ layer อื่นๆ (Services, Repositories) เพราะทั้งสอง layer นี้ ไม่มีการกลับไปเรียกใช่งาน handler อยู่แล้ว ทำให้เราสามารถรู้ได้ว่า เราจะตรวจสอบความถูกต้องของการทำงานแค่ไหน

ประโยชน์อื่นๆ ก็ยังมี เช่น

  • Secure
  • Easy to Manage
  • Scalable

โดย Benefit ของ ทั้ง 3 นี้ จะขึ้นอยู่กับว่า เราต้องการเปรียบเทียบกับอะไร ถ้าเราเปรียบเทียบกับการแก้ปัญหา God Class ตัว N-Tier ก็จะมีประโยชน์หลายอย่าง แต่ถ้าหากเราไปเปรียบเทียบการการแบ่งการทำงานออกเป็น Domain Base แล้วละก็ N-Tier ก็จะไม่มีประโยชน์อย่างที่ว่ามา อีกต่อไป

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


MVC Architecture

การออกแบบมาเพื่อแก้ปัญหาเรื่อง UI Interaction โดยที่โจทย์ในช่วงแรกๆ ของการสร้าง MVC คือ เรามีโปรแกรมหนึ่งที่มี Input/Output จำนวนมาก (เช่น Keyboard, Network, Terminal Input, Mouse) นึกถึง Core Bank ที่ต้องต่อกับ UI Interaction ที่ต่างๆ กัน เช่น Application, Web, Terminal, ATM เป็นต้น ซึ่งแต่ละตัวจะมี Spec และ Layout ที่แตกต่างกัน ซึ่งในสมัยก่อนมันทำกันยากกว่าสมัยนี้

Concept จะเป็นแบบนี้

เมื่อแบ่งออกเป็นแบบนี้แล้ว ก็ตั้งชื่อกลุ่มให้มัน ก็จะได้แบบนี้

โดยที่ Model คือ ระบบภายใน แต่ในระบบ Application ทั่วๆ ไป ระบบภายใน ก็คือ Database เลย แต่ถ้าเรามองภาพใหญ่ออกมาอีก เราก็สามารถมอง model เป็น third-party หรือ External System ที่เราไปใช้มัน (dependency on) ก็ได้เช่นเดียวกัน

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


Clean Architecture

ไอเดียของ Clean Architecture เกิดจากการต่อยอดจาก Hexagonal Architecture ที่อยากให้ architecture หรือ code base ของเรานั้น เป็นอิสระจาก framework หรือ third party ทั้งหมดได้

ซึ่งในการทำระบบจริงๆ มันจะมี module หลายๆ ตัว มาต่อกัน ซึ่งการทำงานแบบ MVC อาจจะไม่เพียงพอ เนื่องจากมันเริ่มแยกออกยาก ว่าส่วนไหนเป็นของตัวเองที่ต้องดูแล ส่วนไหนเป็น Business Logic เราเขียน หรือเป็นของ third party เมื่อเกิดข้อผิดพลาดขึ้นมาแล้วมันตอบไม่ได้ว่า แก้ที่ไหน (ทำให้เกิดการโบ้ยกันไปมา)

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
  • Entities: Layer ที่มี Business rule ต่างๆ ที่เราใช้ เช่น สามารถโอนเงินได้สูงสุดกี่บาท ก่อนที่จะมีของ scan หน้าก่อนการโอนเงิน
  • Use Case: เป็น Action ที่จะเกิดขึ้น เช่น การทำการถอนเงิน เป็นต้น ซึ่งจะทำงานรวมกันกับ Entities ที่เป็นทำหน้าที่เป็นตัวตอบ Business Logic บางอย่าง
  • Interface Adapter: เป็น Adapter ที่บอกว่าจะต้องไปต่อกับ third party ทั้งหมดยังไงได้บ้าง ซึ่งจะใช้ use-case กับ entity เข้ามา แล้วแปลงเป็น format ที่ third party สามารถเรียกใช้งานได้ เช่น Lagacy System, Database, Web เป็นต้น
  • Framework & Driver: เอาข้อมูลจาก Adapter ส่งไปให้ third party เป็นการ handle input/output กันระหว่างระบบของเรากับ third party

ซึ่งเวลาเรานำไปใช้งานจริงๆ มันไม่จำเป็นต้องแบ่งออกมาแค่ 4 ตัวนี้ก็ได้ สามารถเพิ่มลดได้ เพราะส่วนที่สำคัญที่สุดของ Clean Architecture คือ Dependency Rules

Dependency Rules
คือ ตัว code ของเราจะไม่ต้องขึ้นอยู่กับ third party ใดๆ ทั้งสิ้น ซึ่ง dependency จะต้องชี้เข้าไปข้างในเท่านั้น ห้ามให้ entity กับ use case ที่ทำขึ้นมานั้น ไปขึ้นอยู่กับ third party แต่ที่ตัว third party จะเป็นตัวเข้ามาเรียกใช้งานส่วนที่เรารับผิดชอบเสมอ

Core codebase (Business Logic) should not have to know about all the third party

Dependency Inversion
มาดูวิธีการเขียนแบบทั่วไปกันก่อน

import Entities.Course;
import Entities.Student;
import Entities.Payment;
import Exceptions.*;

public class CoursesUseCases {
  private PaymentAdapter paymentAdapter;
  private ELearningAdapter eLearningAdapter;

  public CoursesUseCases() {
    this.paymentAdapter = new ConcretePaymentAdapter();
    this.eLearningAdapter = new ConcreteELearningAdapter();
  }

  public void BuyACourse(Course course, Student student, Payment payment) {
    if (!course.IsActive()) {
      throw new CourseNotActiveException();
    }
    if (!student.Blacklisted()) {
      throw new CannotBuyException();
    }
    if (this.paymentAdapter.Pay(payment) === PaymentResult.Success) {
      this.eLearningAdapter.RegisterCourse(course, student);
    }
  }
}

class ConcretePaymentAdapter {
  public PaymentResult Pay(Payment payment) {
    // Implementation
    throw new UnknowError();
  }
}

class ConcreteELearningAdapter {
  public ConcreteELearningAdapter(String url) {
    //
  }
}

CourseUseCases.java

หากเขียนด้วยวิธีการนี้ มันจะขัดกับกฎเรื่องของ dependency กับ third party ที่ตัว third party จะต้องขึ้นอยู่กับ use case แต่วิธีการเขียนนี้กลายเป็นว่า use case ไปขึ้นกับ Adapter ของ third party แทน หาก adapter แก้ไขอะไร ก็จะไปกระทบ use case ของเราทันนี้ (depend on adapter)

ดังนั้น เพื่อแก้ปัญหานี้เราจะการกำหนด Interface ของ adapter ที่เราจะใช้ของ third party ไว้ใน use case ก่อนเลย แล้วให้ตัว adapter เป็นคน implements ตัว Interface ตัว adapter นั้นไปใช้แทน

เมื่อมีการแก้ไขในฝั่งของ third party ก็จะมีการแสดง error ขึ้นมา ตามภาพ

import Entities.Course;
import Entities.Student;
import Entities.Payment;
import Exceptions.*;

interface PaymentAdapter {
  public PaymentResult Pay(Payment payment);
  // Others ...
}

interface ELearningAdapter {
  public void ViewCourse();
  
  void RegisterCourse(Course course, Student student);
  // Others ...
}


enum PaymentReult {
  Success
}

public class CoursesUseCases {
  private PaymentAdapter paymentAdapter;
  private ELearningAdapter eLearningAdapter;

  public CoursesUseCases(PaymentAdapter paymentAdapter, ELearningAdapter eLearningAdapter /* Dependency Injection */) {
    this.paymentAdapter = paymentAdapter;
    this.eLearningAdapter = eLearningAdapter
  }

  public void BuyACourse(Course course, Student student, Payment payment) {
    if (!course.IsActive()) {
      throw new CourseNotActiveException();
    }
    if (!student.Blacklisted()) {
      throw new CannotBuyException();
    }
    if (this.paymentAdapter.Pay(payment) === PaymentResult.Success) {
      this.eLearningAdapter.RegisterCourse(course, student);
    }
  }
}

class ConcretePaymentAdapter implements PaymentAdapter {
  public PaymentResult Pay(Payment payment) {
    throw new UnknowError();
  }
}

CourseUseCases.java

วิธีการนี้จะทำให้ตัว adapter นั้น depend on use case เสมอ หากมีการแก้ไขตัว adapter ฝั่งของ use case มันจึงไม่กระทบกับ core ส่วน adapter จะยุ่งยากขึ้นมาหน่อย ก็ไม่เป็นไร (เพราะ core สำคัญกว่า)

การใช้งาน Clean Architecture กับคนในทีม เป็นการแยก dependency ออกจากกันแล้วให้แต่ละคนไปรับผิดชอบในแต่ส่วน แต่ทุกคนจะต้องเข้าใจ Domain กลาง

ใน Clean Architecture นั้นสิ่งที่จะเกิดขึ้น คือ ทุกๆ layer จะต้องเข้าใน domain entities และ use case ในระดับหนึ่งแล้ว เพราะว่าจะต้อง implement adapter ตามสิ่งที่ use case และ entities ระบุไว้

สิ่งที่ตามมาอีกอย่าง คือ dev ที่ทำงานใน clean architecture จะต้องเข้าใจ domain กับ use case ที่ทำอยู่ แต่ไม่จำเป็นต้องเข้าใจ third party ที่ไม่เกี่ยวกับงานในส่วนที่ dev คนนั้นทำอยู่ก็ได้

ซึ่งเราจะพบว่า idea จริงๆ อีกอย่างหนึ่ง คือ พยายามการขยายความรู้ในตัว domain knowledge ให้ developer ภายในทีม ให้เข้าใจว่า ระบบของเราพยายามสร้าง business value อะไรอยู่

Clean Architecture emphasize domain knowledge by enforcing dependency inwards

Business Collaboration

พูดถึงทีมเวลาที่ต้องประสานงานกับทาง business แล้ว เกิดความ smooth เรียบร้อย และต่อเนื่อง เข้าจังหวะกันได้ดี

ปัญหาที่เจอในการทำงานร่วมกับระหว่างทีมพัฒนา กับ ทีม business คือ

BA: "โปรแกรมเมอร์พูดอะไรไม่รู้เรื่องเลย"

Dev: "BA พูดอะไรมาไม่เข้าใจ ไม่รู้เรื่องเลย"

ซึ่งคำบ่นนี้ เป็นปัญหาคลาสสิคที่เราจะเจอกัน จนกระทั่งมีคนพบเจอว่า ทำยังไงให้เราสามารถเขียนระบบล้อตาม business domain ได้ ก็น่าจะคุยกันง่ายขึ้น

นั่นคือ Eric Evans ผู้ที่เขียนหนังสือ Domain Driven Design นั่นเอง

Domain Driven Design

มัน คือ เทคนิคในการออกแบบ software ตาม business model ที่ช่วยทำให้ Dev และ BA เห็นภาพที่ตรงกัน ทำให้การแก้ไข หรือ การทำ requirement change ทำได้ง่ายขึ้น

ยกตัวอย่างเช่น

BA จะมีภาพหรือกระบวนการทำงานอยู่ในหัวของตัวเองอยู่แล้วแบบหนึ่ง

แต่เมื่อเราเอามาเขียนโค๊ด และวาดมันออกมา ก็จะได้ระบบหน้าตาประมาณนี้

เห็นว่าตัวคำศัพท์ หรือ relationship ไม่เหมือนกันเลย ซึ่งจะมีผลหลายอย่าง

เช่น

ถ้าเขาถามว่า checkout process ทำงานยังไง Dev ก็อาจจะงงว่ามันอยู่ตรงไหน ในโค๊ดที่เขียนขึ้นมา เนื่องจากตัว checkout process มันอาจจะถูกเอาไปเขียนไว้ในส่วนของ Pay (With discount)

หรือ

ถ้าเราอยากเพิ่ม promotion เข้าไป dev ก็จะงงว่า มันอยู่ตรงไหน ซึ่งจริงๆ มันอาจจเป็นส่วนเล็กๆ ภายใน Payment ก็ได้

กลับกันในฝั่งของ business นั้น จะเห็น promotion เป็นก้อนใหญ่ๆ เลย ถ้าหาก dev ไปถาม business ว่า discount ทำงานยังไง business ก็อาจจะงงได้ เพราะในฝั่ง business ไม่ได้มีคำนี้ (มีแค่คำว่า promotion)

สุดท้ายก็เกิดอาการเข้าใจไม่ตรงกัน และเริ่มตีกันได้

ดังนั้นทาง DDD เลยเสนอว่า เรามา model ภาพตามที่ Business Expert เห็นเลยสิ แล้วทางทีม dev ก็เขียนตามที่เห็นเลย เวลาที่คุยกันจะได้รู้เลยว่ามันอยู่ตรงไหน

ทำให้เราคุยกันง่ายขึ้น เพราะคุยภาษาเดียวกันแล้ว

Three Level of Domain Driven Design

Domain Discovery (Exploratory DDD)
เป็นการเก็บ requirements จากทางฝั่งลูกค้า โดยมีเทคนิคในการเก็บข้อมูล เพื่อให้สามารถดึงข้อมูลเหล่านั้นจาก user ให้ได้

  • ระบบปัจจุบันเป็นอย่างไร จะไปต่อยังไง (Model as-is, to-be and cloud-be states of the domain)
  • ตัวอย่างเทคนิคที่ใช้ เช่น Big Picture, Event Stroming, Business Flow with User Scenarios เป็นต้น

Software Architecture (Strategic DDD)
เป็นการพูดถึงเรื่องของการแบ่งระบบที่มีขนาดใหญ่ออกมาให้เป็น domain เล็กๆ ย่อยๆ ได้อย่างไร โดยจะมีการพูดในเรื่องของ...

  • Bounded Contexts
  • Ubiquitous language
  • Domain Events

Software Design (Tactical DDD)
เป็นการพูดถึงในระดับ code แล้ว ว่า เราจะเขียน code ยังไงให้ ตรงกับ model ของ domain export ซึ่งจะมีเทคนิคที่ใช้ เช่น Entities, Value Object, Aggregates, Event Sourcing เป็นต้น

DDD Doctrine

  1. พยายามสื่อสารอย่างตรงไปตรงมา (อย่าคิดว่าเรื่องแค่นี้น่าจะรู้กันแล้ว ให้พูดออกมาเลย)
  2. ควรใช้ภาษาเดียวในการคุย
  3. อย่าหยุดเรียนรู้ (พยายามเรียนรู้ตาม domain ที่โตขึ้น)
  4. explore model หลายๆ แบบ
  5. โฟกัสที่ concrete scenarios (เริ่มจากฝั่ง requirements เสมอ)
  6. ออกแบบให้สามารถปรับ design ของเรา

เรามาดูเรื่องของ...

Bounded Context & Ubiquitous language

Bounded Context คือ conceptual boundary ที่บอกว่าเรา Domain แต่ละตัวมีขอบเขตอยู่ตรงไหน เท่าไหร่ cover อะไรบ้าง โดยจะตกลงกันว่าต้องใช้ภาษาเดียวกัน ซึ่งเราจะเรียกว่า Ubiquitous language

เงื่อนไขในการวาด bounded context

  • ต้องมีชื่อที่ valid และ make sense เสมอ
  • bounded context จะต้องมี ubiquitous language กำกับมาด้วยเสมอ (ถ้าไปวาดอะไรมา แต่มีไม่ business expert กำกับ ก็ไม่ควรทำ)
  • หลีกเลี่ยง bounded context ที่ซับซ้อน (มันจะทำให้การแก้ไขยุ่งยากมากยิ่งขึ้น และใช้ business export เยอะเกินความจำเป็น) ถ้า communication pattern เริ่มซับซ้อน ต้องลองหาว่ามี model แบบไหนที่สามารถทำให้ bounded context มันดู simple ขึ้น

Ubiquitous language คือ การสร้างภาษาหรือข้อตกลงที่สามารถใช้ร่วมกันได้ใน domain นั้น มันจำช่วยลดความสับสนในการคุยได้

เทคนิคในการแบ่ง Bounded Context เช่น

  • Namespace
  • Package
  • Library
  • Multi-service

Context Mapping

เวลาที่เราวาด Bounded Context นั้น มันจะมีบางอย่างที่เหมือนกันอยู่ ที่อาจจะต้องอยู่ใน bounded context ที่มากกว่า 1 ที่ ทำให้จำเป็นต้องมีข้อมูลบางส่วนที่ต้องแชร์กัน จึงต้องมีสิ่งที่ใช้เชื่อมข้อมูลกัน

เทคนิคที่ใช้ เช่น

  • ใช้ ID เดียวกัน เพื่อบอกว่าข้อมูลนี้เป็นของใคร
  • ใช้วิธีการส่ง Event หากัน
  • ใช้วิธีการ REST API ไปหากัน

Tactical Domain Driven Design

เป็นส่วนที่เราพยายามจะเขียนโค๊ดในล้อตาม Business Domain ซึ่งมีเทคนิคหลายอย่าง ซึ่งในบทความนี้เราขอพูดถึงตัว 2 concept นี้กันก่อน

Entity

เป็น object ที่ถูกกำหนดไว้ด้วย identity ต่างๆ พูดง่ายๆ คือ object lifecycle ที่สามารถเปลี่ยนไปได้เรื่อยๆ ตามเวลา เช่น User, Course, Shopping Cart, Invoice เป็นต้น

Users ตอนเริ่มต้นอาจจะมีชื่อแบบหนึ่ง และต่อมาก็อาจจะเปลี่ยนชื่อไปเป็นอีกชื่อหนึ่ง ไม่ว่าจะเปลี่ยนข้อมูลไปมากน้อยแค่ไหน มันก็ยังเป็น user คนเดิมอยู่ดี แบบนี้เราจะเรียกมันว่า Entity

Value object

เป็นกลุ่มของข้อมูลที่ไม่มีการเปลี่ยนแปลงตามกาลเวลา เช่น Date Time, Price with Currency, Color เป็นต้น

การแยกระหว่าง Entity กับ Value object เป็นอะไรที่สำคัญมาก เพราะว่ามันจะมีผลกระทบกับ Performance หรือ Storage ของระบบ

ประโยชน์ในการแยก Entity เช่น

  • เราสามารถแยกข้อมูลของแต่ละคนได้ เช่น ชื่อเหมือนกัน แต่อาจจะเป็นคนละคนกันได้
  • ข้อมูลจะ up-to-date
  • ทำให้ข้อมูลไม่เกิดการ conflict กับ context อื่นๆ
  • ช่วยใช้คิดถึงและตัดสินใจในการจัดการกับเรื่องของ Transaction และ Multi-Thread

ซึ่งทั้งหมดที่ยกตัวอย่างมา มันจะตรงข้ามกับ Value Object เลย

ตัวอย่างการใช้งาน

สมมติว่า เรามี requirements ประมาณนี้

"User must be able to put courses into a shopping cart"

"Each course have a description and info, which is a complex text with styling"

"Then, user will checkout the cart and make a payment"

1.แยกระหว่าง Noun กับ Verb ออกจากัน ก็จะได้

พอเราแยกมันออกมาแล้ว เราก็สามารถบอกได้ว่า

Noun = Entity & Value Object

Verb = Method

(ซึ่งถ้าไปลึกกว่านี้ บาง Verb บางอย่างจะเป็น Event ได้แต่ตอนนี้เราพูดถึงแค่นี้ก่อน)

เราสามารถระบุ Entity กับ Value Object ได้แล้ว

ถ้าเราเอามาวาดเป็น diagram ก็จะได้ภาพที่ใกล้เคียงกับ context


Aggregate

aggregate นั้น เป็น consistent boundary ที่มี object หลายๆ ที่สามารถมองว่าเป็นตัวเดียว ซึ่งตัว aggregate นั้นจะแตกต่างจาก entity ทั่วไป คือ เราต้องทำงานกับหัวบนสุดเท่านั้น ซึ่งเราจะเรียกมันว่า aggregate root เพื่อรักษา consistency ของข้อมูลนั้นไว้

ดังนั้นเราสามารถพูดได้ว่า ทุกๆ aggregate root จะมี entity แต่ไม่จำเป็นว่า ทุกๆ entity จะต้องเป็น aggregate root


Service

เป็น stateless object ที่ทำการ operation บางอย่างที่เกี่ยวข้องกับ entity หลายๆ ตัว โดยการตั้งให้เป็น stateless coordination แล้ว เราไม่ต้องกังวลเรื่องของการทำงาน เพราะการทำงานหลายๆ อย่างจะไปทำที่ Aggregate และตัวมันเองต้องกังวลเรื่อง Lifecycle เพราะ Lifecycle จะอยู่ที่ Entity และ equality อยู่ใน value object

ข้อควรระวังในการทำ Service ที่เป็น stateless object ขึ้นมา มันจะไม่สามารถ tacking สิ่งที่เกิดขึ้นไม่ได้ (เช่น เกิดขึ้นวันไหน) ถ้าอยากให้สามารถ tracking ส่วนนี้ได้ เราจะเป็นต้องสร้าง entity ตัวใหม่ขึ้นมาเพื่อจัดการเรื่องนี้โดยเฉพาะ

สิ่งที่ต้องระวัง คือ เวลาที่เราไปคุยกับ business แล้วเขาพูดถึง operation ของเขา อาจจะมาบอกมาเป็น verb แต่มันอาจจะไม่ใช้ verb จริงๆ ก็ได้ เพราะถ้าหากต้องมีการ track ด้วยแล้ว มันอาจจะเป็นแค่ noun

เช่น

เขาอาจจะมาบอกเราว่า ต้องมีการ Approve ได้ แต่เขาอาจจะไม่ได้บอกเราว่าจริงๆ แล้วมีการเก็บตัวใบ Approval เกิดขึ้นที่ไหนด้วย ว่า เก็บที่ไหน เมื่อไหร่ และ ใครเซ็น เป็นต้น

ดังนั้นให้ระวังเรื่องนี้ในตอนคุย requirement ด้วย


Domain Event

เวลาที่เกิดอะไรขึ้น แทนที่เราจะบอกให้ domain อื่นทำอะไร ก็จะเป็นการ public ขึ้นไปตรงกลาง แล้ว domain อื่น ก็สามารถเข้ามา Subscribe ได้ เพื่อดูว่าเกิดอะไรขึ้นบ้าง

ทำให้เกิดการถ่ายโอนหน้าที่ไปยัง domain ที่รับผิดชอบเรื่องนั้นจริงๆ แทนที่จะกระจายไปทั่วระบบ

เวลาที่เราทำ Domain Event มันจะมี property ที่สำคัญอยู่ คื

  • Event ต้องเป็น Immutable
    • อะไรที่เกิดแล้ว ก็จะเกิดเลย ไม่มีการลบทิ้ง
    • ถ้าต้องการแก้ไข จะต้องสร้าง event ใหม่เข้าไป เพื่อ Reference ไปยังตัวเก่า เหมือนกับการทำระบบบัญชี
  • Event จะมี Timestamp เพื่อให้รู้ว่าเกิดอะไรก่อนเกิดหลัง

Organization Collaboration

ต่อไปเป็นการทำงานร่วมกันกับทีมต่างๆ ภายในองค์กร ซึ่งเมื่อต้องทำงานร่วมกันหลายๆ ทีม มันก็จะมีปัญหาต่างๆ เกิดขึ้น เช่น

  • Technology Stack ไม่ตรงกัน
  • การ Integrate
  • โยนความรับผิดชอบกันไปมา

ซึ่ง Team Collaboration Pattern ในหัวข้อนี้ เราจะพูดถึงการวาง Architecture ยังไงเพื่อให้สามารถทำงานร่วมกันได้อย่างราบรื่น


Conway's Law

เป็นกฏสำคัญในการออกแบบองค์กร

Organizations which design systems (in the broad sense used here) are constrained to produce designs which are copies of the communication structures of these organizations.
— Melvin E. Conway

ถ้าองค์กรคุณมีโครงสร้างแบบไหน ระบบของคุณ Architecture ของคุณ หรือโครงสร้าง ก็จะล้อตามโครงสร้างองค์กรของคุณ

ทำไมถึงเป็นอย่างนั้น

สมมติว่าเรามี dev อยู่ 2 คน ที่ไม่อยากคุยกัน เนื่องจากสาเหตุต่างๆ เช่น

  • ทำงานกันคนละทีม คนละที่
  • มีหัวหน้าคนละคน ซึ่งอาจจะโฟกัสต่างกัน
  • ถือ KPI คนละตัว
  • ลูกค้าที่ต่างกัน คนละกลุ่ม ที่มีความต้องการต่างกัน

ทำให้ dev ทั้ง 2 คน จำเป็นต้องมีงานที่ต่างกันเป็นของตัวเอง และเมื่อ dev สองคนนี้ต้องทำงานร่วมกัน integrate ระบบเข้าหากัน ก็ไม่อยากคุยกันเยอะ เลยหาวิธีที่ให้คุยกันน้อยๆ เช่น

คุย requirement ที่อีกฝั่งต้องการ > ไป develop > โยน doc ให้ > มีปัญหาค่อยมาถาม

แค่นี้ก็คุยน้อยลงแล้ว มันเหมือนจะดีมั้ง?

แต่สังเกตดูว่า แต่ละคนมี Goal ที่ต่างกันแล้ว ทำให้ dev สนใจแค่งานของตัวเอง คือ การเขียนโค๊ด มันก็เริ่มคุยกันไม่รู้เรื่อง แต่ขาดการพูดคุยกันว่า ทำอย่างไรให้ระบบออกมาดี ทำงานง่าย เป็นต้น สุดท้ายแต่ละทีมก็จะมี style เป็นของตัวเอง

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


Cross-Functional Team

หมายถึง team ที่รวมกันหลายๆ ตำแหน่ง ให้สามารถทำงานตั้งแต่ต้นจนจบ จนทำให้ทีมคุยกันง่ายขึ้น


Monolith

เป็นรูปแบบการทำงานร่วมกัน โดยอยู่บน code base เดียวกัน แต่การทำงานอยู่บน code base เดียวกัน แต่มีหลายทีมจะมีการแยก Folder, Module, Library ออกไปแต่ละทีม แต่ยังอยู่บน code base เดียวกัน

เวลาที่เราใช้ Monolith จะต้องเป็น modular ซึ่งต้องมีการใช้งานข้ามกันให้น้อยที่สุด เพื่อไม่ให้เกิดการกระทบกันได้ และเมื่อมีการกระทบก็เรียกทีมที่กระทบมาคุยกันก่อน

การร่วมมือกันแบบ monolith นั้น จะต้องกำหนด Boundary และ จุดเชื่อมต่อกันของทีมอย่างชัดเจน

ข้อดี

  • Infra ดูแลง่าย ไม่ต้อง Deploy หลายๆ App
  • ไม่ต้องรู้ทริกเกี่ยวกับ distributed system
  • สามารถบังคับใช้ coding standard หรือ practice เดียวกันได้

ข้อเสีย

  • ใช้ technology เดียวกัน ถ้าบางทีมต้องการใช้งาน technology อื่นที่เหมาะสมกว่าก็จะทำได้ยาก
  • ใช้ style เดียวกัน แต่ละทีมจะปรับ style ให้เหมาะกับงานที่ตัวเองรับผิดชอบยาก
  • การจัดการการ release

ใช้งานตอนไหน

  • ใช้ตอนเริ่มต้น และค่อยๆ ย้าย เมื่อสังเกตเห็นแล้วว่า เริ่มเกิด conflict กันระหว่างทีม เช่น release cycle ไม่ตรงกัน, เกิด code conflict หรือ จำเป็นต้องเปลี่ยน technology เป็นต้น

Multi-Service architecture

เป็นการออกแบบ architecture ให้มีการคุยกันระหว่าง services หลายๆ ตัว โดยการแบ่ง Service ออกเป็นหลายๆ ตัว แล้วคุยกันผ่าน network ทำให้เราสามารถใช้ technology, Release Cycle และ Standard ที่เป็นของตัวเอง

เมื่อเราคุยกันผ่าน Server หลายๆ ตัว ผ่าน network มันก็จะกลายเป็น Distributed System

แต่เราก็จะพบกับปัญหาอื่นๆ ตามมาให้ต้องคิดต้องแก้อยู่เหมือนกัน เช่น

  • Retry Strategy เป็นแบบไหนเมื่อ call แล้ว network ล่ม
  • จะเกิด data loss ไหม
  • Available Model เป็นอย่างไร ถ้าระบบอื่นล้ม หรือระบบเราล่มจะทำยังไง
  • กระทบกับ SLA ของ Service
  • Monitoring
  • Consistent Boundaries ข้อมูลไม่ตรงกัน
  • CAP Theorem
  • การทดสอบยากขึ้น
    • Mock Data
    • Integrated environment
  • Versioning & Backword compatibility

เห็นไหมว่า มันตามมาด้วยปัญหามากมายเลย ที่ต้องจัดการ แต่การทำเหล่านี้ทำเพื่อให้เราสามารถแก้ปัญหาเรื่อง release cycle ให้สามารถทำได้ง่ายและเร็วขึ้น

ดังนั้น เวลาที่เราจะเลือกใช้ Multi-Service ต้องถามองค์กรดูก่อนว่าพร้อมที่จะแก้ปัญหามากมายเหล่านี้ไหม

Multi-service setup meant to solve organization problem

SOA & Microservices

สรุปความแตกต่างระหว่าง SoA กับ Microservice สั้นๆ ได้ ว่า

  • SoA: จะให้ encourage single source of truth
  • Microservices: จะ allow data duplication

Design Service Boundary

การออกแบบ service ที่ควรล้อไปกับมัน คือ

  • Bounded Context
  • Conway's Law

แค่กฏ 2 ข้อนี้ทำให้เราสามารถออกแบบ boundary แล้ว

ถ้าเราออกแบบได้ดี ให้ลองสังเกตุว่า เมื่อมี requirement change เกิดขึ้น 80% ของงานหรือการ coordination มันจะเกิดขึ้นแค่ไม่กี่ service เท่านั้น อาจจะแค่ 1-2 service และจะคุยกันภายในทีม อีก 20% อาจจะมีบางงานที่จำเป็นต้องคุยกับทีมอื่นๆ

ในทางกลับกัน5hk 80% ต้องมีการคุยกันมากกว่า 6 Services ว่าจะต้องมี release cycle กันยังไง หรือ backward compatibility หรือต้องมาจัดประชุมใหญ่ๆ ร่วมกัน