ไทย

เรียนรู้วิธีที่ Event Sourcing สามารถปฏิวัติการนำ Audit Trail ของคุณมาใช้ โดยมอบความสามารถในการตรวจสอบ ความสมบูรณ์ของข้อมูล และความยืดหยุ่นของระบบที่เหนือชั้น สำรวจตัวอย่างและกลยุทธ์การนำไปใช้จริง

Event Sourcing: การนำ Audit Trails มาใช้เพื่อระบบที่แข็งแกร่งและตรวจสอบได้

ในภูมิทัศน์ดิจิทัลที่ซับซ้อนและเชื่อมโยงถึงกันในปัจจุบัน การรักษา Audit Trail ที่แข็งแกร่งและครอบคลุมเป็นสิ่งสำคัญอย่างยิ่ง ไม่เพียงแต่เป็นข้อกำหนดด้านกฎระเบียบบ่อยครั้ง แต่ยังมีความสำคัญต่อการดีบั๊ก การวิเคราะห์ความปลอดภัย และการทำความเข้าใจวิวัฒนาการของระบบของคุณ Event Sourcing ซึ่งเป็นรูปแบบสถาปัตยกรรมที่จับการเปลี่ยนแปลงทั้งหมดในสถานะของแอปพลิเคชันในฐานะลำดับของเหตุการณ์ นำเสนอโซลูชันที่สง่างามและทรงพลังสำหรับการนำ Audit Trails ที่เชื่อถือได้ สามารถตรวจสอบได้ และขยายได้มาใช้

Event Sourcing คืออะไร?

แอปพลิเคชันแบบดั้งเดิมมักจะจัดเก็บเฉพาะสถานะปัจจุบันของข้อมูลไว้ในฐานข้อมูลเท่านั้น วิธีการนี้ทำให้ยากต่อการสร้างสถานะในอดีต หรือทำความเข้าใจลำดับของเหตุการณ์ที่นำไปสู่สถานะปัจจุบัน ในทางตรงกันข้าม Event Sourcing มุ่งเน้นไปที่การจับการเปลี่ยนแปลงที่สำคัญทุกอย่างในสถานะของแอปพลิเคชันในฐานะเหตุการณ์ที่ไม่เปลี่ยนแปลง เหตุการณ์เหล่านี้จะถูกจัดเก็บใน Event Store แบบ append-only ซึ่งสร้างบันทึกที่สมบูรณ์และตามลำดับเวลาของการดำเนินการทั้งหมดภายในระบบ

ลองนึกถึงสมุดบัญชีธนาคาร แทนที่จะบันทึกยอดคงเหลือปัจจุบันเท่านั้น ทุกการฝาก การถอน และการโอนจะถูกบันทึกเป็นเหตุการณ์แยกต่างหาก ด้วยการเล่นเหตุการณ์เหล่านี้ซ้ำ คุณสามารถสร้างสถานะของบัญชีได้ทุกเมื่อ

ทำไมต้องใช้ Event Sourcing สำหรับ Audit Trails?

Event Sourcing นำเสนอข้อได้เปรียบที่น่าสนใจหลายประการสำหรับการนำ Audit Trails มาใช้:

การนำ Event Sourcing มาใช้สำหรับ Audit Trails: คู่มือทีละขั้นตอน

นี่คือคู่มือภาคปฏิบัติสำหรับการนำ Event Sourcing มาใช้สำหรับ Audit Trails:

1. ระบุเหตุการณ์สำคัญ

ขั้นตอนแรกคือการระบุเหตุการณ์สำคัญที่คุณต้องการบันทึกใน Audit Trail ของคุณ เหตุการณ์เหล่านี้ควรแสดงถึงการเปลี่ยนแปลงที่สำคัญในสถานะของแอปพลิเคชัน พิจารณาการดำเนินการต่างๆ เช่น:

ตัวอย่าง: สำหรับแพลตฟอร์มอีคอมเมิร์ซ เหตุการณ์สำคัญอาจรวมถึง "OrderCreated", "PaymentReceived", "OrderShipped", "ProductAddedToCart", และ "UserProfileUpdated"

2. กำหนดโครงสร้างเหตุการณ์

แต่ละเหตุการณ์ควรมีโครงสร้างที่กำหนดไว้อย่างดีซึ่งรวมถึงข้อมูลต่อไปนี้:

ตัวอย่าง: เหตุการณ์ "OrderCreated" อาจมีโครงสร้างดังนี้:

{
  "eventType": "OrderCreated",
  "eventData": {
    "orderId": "12345",
    "customerId": "67890",
    "orderDate": "2023-10-27T10:00:00Z",
    "totalAmount": 100.00,
    "currency": "USD",
    "shippingAddress": {
      "street": "123 Main St",
      "city": "Anytown",
      "state": "CA",
      "zipCode": "91234",
      "country": "USA"
    }
  },
  "timestamp": "2023-10-27T10:00:00Z",
  "userId": "user123",
  "transactionId": "tx12345",
  "correlationId": "corr123",
  "metadata": {
    "ipAddress": "192.168.1.1",
    "browser": "Chrome",
    "location": {
       "latitude": 34.0522,
       "longitude": -118.2437
    }
  }
}

3. เลือก Event Store

Event Store เป็นที่เก็บกลางสำหรับจัดเก็บเหตุการณ์ ควรเป็นฐานข้อมูล append-only ที่ปรับให้เหมาะสมสำหรับการเขียนและอ่านลำดับของเหตุการณ์ มีตัวเลือกหลายอย่าง:

เมื่อเลือก Event Store ให้พิจารณาปัจจัยต่างๆ เช่น:

4. ใช้การเผยแพร่เหตุการณ์

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

ตัวอย่าง (โดยใช้ EventStoreService สมมติ):

public class OrderService {

  private final EventStoreService eventStoreService;

  public OrderService(EventStoreService eventStoreService) {
    this.eventStoreService = eventStoreService;
  }

  public void createOrder(Order order, String userId) {
    // ... business logic to create the order ...

    OrderCreatedEvent event = new OrderCreatedEvent(
        order.getOrderId(),
        order.getCustomerId(),
        order.getOrderDate(),
        order.getTotalAmount(),
        order.getCurrency(),
        order.getShippingAddress()
    );

    eventStoreService.appendEvent("order", order.getOrderId(), event, userId);
  }
}

public class EventStoreService {

  public void appendEvent(String streamName, String entityId, Object event, String userId) {
    // Create an event object
    EventRecord eventRecord = new EventRecord(
        UUID.randomUUID(), // eventId
        streamName,  // streamName
        entityId,   // entityId
        event.getClass().getName(), // eventType
        toJson(event),  // eventData
        Instant.now().toString(), // timestamp
        userId  // userId
    );

    // Serialize the event
    String serializedEvent = toJson(eventRecord);

    // Append the event to the event store (implementation specific to the chosen event store)
    storeEventInDatabase(serializedEvent);

    // Publish the event to subscribers (optional)
    publishEventToMessageQueue(serializedEvent);
  }

  // Placeholder methods for database and message queue interaction
  private void storeEventInDatabase(String serializedEvent) {
    // Implementation to store the event in the database
    System.out.println("Storing event in database: " + serializedEvent);
  }

  private void publishEventToMessageQueue(String serializedEvent) {
    // Implementation to publish the event to a message queue
    System.out.println("Publishing event to message queue: " + serializedEvent);
  }

  private String toJson(Object obj) {
    // Implementation to serialize the event to JSON
    try {
      ObjectMapper mapper = new ObjectMapper();
      return mapper.writeValueAsString(obj);
    } catch (Exception e) {
      throw new RuntimeException("Error serializing event to JSON", e);
    }
  }
}


class EventRecord {
  private final UUID eventId;
  private final String streamName;
  private final String entityId;
  private final String eventType;
  private final String eventData;
  private final String timestamp;
  private final String userId;

  public EventRecord(UUID eventId, String streamName, String entityId, String eventType, String eventData, String timestamp, String userId) {
    this.eventId = eventId;
    this.streamName = streamName;
    this.entityId = entityId;
    this.eventType = eventType;
    this.eventData = eventData;
    this.timestamp = timestamp;
    this.userId = userId;
  }

  // Getters

  @Override
  public String toString() {
    return "EventRecord{" +
        "eventId=" + eventId +
        ", streamName='" + streamName + '\'' +
        ", entityId='" + entityId + '\'' +
        ", eventType='" + eventType + '\'' +
        ", eventData='" + eventData + '\'' +
        ", timestamp='" + timestamp + '\'' +
        ", userId='" + userId + '\'' +
        '}';
  }
}

class OrderCreatedEvent {
    private final String orderId;
    private final String customerId;
    private final String orderDate;
    private final double totalAmount;
    private final String currency;
    private final String shippingAddress;

    public OrderCreatedEvent(String orderId, String customerId, String orderDate, double totalAmount, String currency, String shippingAddress) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.orderDate = orderDate;
        this.totalAmount = totalAmount;
        this.currency = currency;
        this.shippingAddress = shippingAddress;
    }

    // Getters for all fields

    public String getOrderId() { return orderId; }
    public String getCustomerId() { return customerId; }
    public String getOrderDate() { return orderDate; }
    public double getTotalAmount() { return totalAmount; }
    public String getCurrency() { return currency; }
    public String getShippingAddress() { return shippingAddress; }

    @Override
    public String toString() {
        return "OrderCreatedEvent{" +
                "orderId='" + orderId + '\'' +
                ", customerId='" + customerId + '\'' +
                ", orderDate='" + orderDate + '\'' +
                ", totalAmount=" + totalAmount +
                ", currency='" + currency + '\'' +
                ", shippingAddress='" + shippingAddress + '\'' +
                '}';
    }
}

class Order {
  private final String orderId;
  private final String customerId;
  private final String orderDate;
  private final double totalAmount;
  private final String currency;
  private final String shippingAddress;

  public Order(String orderId, String customerId, String orderDate, double totalAmount, String currency, String shippingAddress) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.orderDate = orderDate;
        this.totalAmount = totalAmount;
        this.currency = currency;
        this.shippingAddress = shippingAddress;
    }

    // Getters for all fields

    public String getOrderId() { return orderId; }
    public String getCustomerId() { return customerId; }
    public String getOrderDate() { return orderDate; }
    public double getTotalAmount() { return totalAmount; }
    public String getCurrency() { return currency; }
    public String getShippingAddress() { return shippingAddress; }

    @Override
    public String toString() {
        return "Order{" +
                "orderId='" + orderId + '\'' +
                ", customerId='" + customerId + '\'' +
                ", orderDate='" + orderDate + '\'' +
                ", totalAmount=" + totalAmount +
                ", currency='" + currency + '\'' +
                ", shippingAddress='" + shippingAddress + '\'' +
                '}';
    }
}

5. สร้าง Read Models (Projections)

แม้ว่า Event Store จะให้ประวัติที่สมบูรณ์ของการเปลี่ยนแปลงทั้งหมด แต่โดยทั่วไปแล้วการสอบถามโดยตรงสำหรับการดำเนินการอ่านจะไม่คุ้มค่า แทนที่จะเป็นเช่นนั้น คุณสามารถสร้าง Read Models ซึ่งรู้จักกันในชื่อ Projections ซึ่งปรับให้เหมาะสมสำหรับรูปแบบการสอบถามเฉพาะ Read Models เหล่านี้ได้มาจาก Event Stream และได้รับการอัปเดตแบบอะซิงโครนัสเมื่อมีการเผยแพร่เหตุการณ์ใหม่

ตัวอย่าง: คุณอาจสร้าง Read Model ที่มีรายการคำสั่งซื้อทั้งหมดสำหรับลูกค้าที่ระบุ หรือ Read Model ที่สรุปข้อมูลการขายสำหรับผลิตภัณฑ์ที่เฉพาะเจาะจง

ในการสร้าง Read Model คุณสมัครรับ Event Stream และประมวลผลแต่ละเหตุการณ์ สำหรับแต่ละเหตุการณ์ คุณจะอัปเดต Read Model ตามนั้น

ตัวอย่าง:

public class OrderSummaryReadModelUpdater {

    private final OrderSummaryRepository orderSummaryRepository;

    public OrderSummaryReadModelUpdater(OrderSummaryRepository orderSummaryRepository) {
        this.orderSummaryRepository = orderSummaryRepository;
    }

    public void handle(OrderCreatedEvent event) {
        OrderSummary orderSummary = new OrderSummary(
                event.getOrderId(),
                event.getCustomerId(),
                event.getOrderDate(),
                event.getTotalAmount(),
                event.getCurrency()
        );

        orderSummaryRepository.save(orderSummary);
    }

    // Other event handlers for PaymentReceivedEvent, OrderShippedEvent, etc.
}

interface OrderSummaryRepository {
    void save(OrderSummary orderSummary);
}

class OrderSummary {
    private final String orderId;
    private final String customerId;
    private final String orderDate;
    private final double totalAmount;
    private final String currency;

    public OrderSummary(String orderId, String customerId, String orderDate, double totalAmount, String currency) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.orderDate = orderDate;
        this.totalAmount = totalAmount;
        this.currency = currency;
    }
    //Getters
}

6. รักษาความปลอดภัย Event Store

Event Store มีข้อมูลที่ละเอียดอ่อน ดังนั้นจึงจำเป็นอย่างยิ่งที่จะต้องรักษาความปลอดภัยอย่างเหมาะสม พิจารณามาตรการรักษาความปลอดภัยดังต่อไปนี้:

7. นำการตรวจสอบและรายงานมาใช้

เมื่อคุณได้นำ Event Sourcing มาใช้แล้ว คุณสามารถใช้ Event Stream เพื่อสร้างรายงานการตรวจสอบและดำเนินการวิเคราะห์ความปลอดภัย คุณสามารถสอบถาม Event Store เพื่อค้นหาเหตุการณ์ทั้งหมดที่เกี่ยวข้องกับผู้ใช้ ธุรกรรม หรือเอนทิตีที่เฉพาะเจาะจง นอกจากนี้คุณยังสามารถใช้ Event Stream เพื่อสร้างสถานะของระบบขึ้นใหม่ได้ทุกเมื่อ

ตัวอย่าง: คุณอาจสร้างรายงานที่แสดงการเปลี่ยนแปลงทั้งหมดที่ทำกับโปรไฟล์ผู้ใช้ที่เฉพาะเจาะจงในช่วงระยะเวลาหนึ่ง หรือรายงานที่แสดงธุรกรรมทั้งหมดที่เริ่มต้นโดยผู้ใช้รายใดรายหนึ่ง

พิจารณาความสามารถในการรายงานดังต่อไปนี้:

ความท้าทายของ Event Sourcing

แม้ว่า Event Sourcing จะมอบประโยชน์มากมาย แต่ก็มีความท้าทายบางประการเช่นกัน:

แนวทางปฏิบัติที่ดีที่สุดสำหรับ Event Sourcing

เพื่อบรรเทาความท้าทายของ Event Sourcing ให้ปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเหล่านี้:

ตัวอย่างการใช้งาน Event Sourcing ในโลกแห่งความเป็นจริง

Event Sourcing ถูกนำไปใช้ในอุตสาหกรรมและการใช้งานที่หลากหลาย รวมถึง:

บทสรุป

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

อ่านเพิ่มเติม