Lietuvių

Sužinokite, kaip įvykių valdymas (Event Sourcing) gali pakeisti audito sekų įgyvendinimą, suteikdamas neprilygstamą atsekamumą, duomenų vientisumą ir sistemos atsparumą. Išsamiau apie praktinius pavyzdžius ir strategijas.

Įvykių valdymas (Event Sourcing): audito sekų įgyvendinimas patikimoms ir atsekamoms sistemoms

Šiandieniniame sudėtingame ir tarpusavyje susijusiame skaitmeniniame kraštovaizdyje itin svarbu palaikyti patikimą ir išsamią audito seką. Tai ne tik dažnai yra reguliavimo reikalavimas, bet ir nepaprastai svarbu derinant, atliekant saugumo analizę ir suprantant sistemos evoliuciją. Įvykių valdymas (Event Sourcing) – architektūrinis modelis, fiksuojantis visus programos būsenos pokyčius kaip įvykių seką, siūlo elegantišką ir galingą sprendimą, skirtą patikimoms, audituojamoms ir išplečiamoms audito sekoms įgyvendinti.

Kas yra įvykių valdymas (Event Sourcing)?

Tradicinės programos paprastai saugo tik dabartinę duomenų būseną duomenų bazėje. Dėl tokio požiūrio sunku atkurti praėjusias būsenas arba suprasti įvykių, nulėmusių dabartinę būseną, seką. Įvykių valdymas (Event Sourcing), priešingai, sutelkia dėmesį į kiekvieno reikšmingo programos būsenos pokyčio fiksavimą kaip nekintamo įvykio. Šie įvykiai saugomi tik papildomoje įvykių saugykloje, sudarančioje išsamų ir chronologinį visų sistemos veiksmų įrašą.

Pagalvokite apie tai kaip apie banko sąskaitos knygą. Užuot tiesiog užfiksavus dabartinį likutį, kiekvienas indėlis, išėmimas ir pervedimas įrašomas kaip atskiras įvykis. Atkurdami šiuos įvykius, galite atkurti sąskaitos būseną bet kuriuo laiko momentu.

Kodėl audito sekoms naudoti įvykių valdymą (Event Sourcing)?

Įvykių valdymas (Event Sourcing) siūlo keletą svarių privalumų įgyvendinant audito sekas:

Įvykių valdymo (Event Sourcing) įgyvendinimas audito sekoms: išsamus vadovas

Štai praktinis vadovas, kaip įgyvendinti įvykių valdymą (Event Sourcing) audito sekoms:

1. Nustatykite pagrindinius įvykius

Pirmas žingsnis – nustatyti pagrindinius įvykius, kuriuos norite užfiksuoti savo audito sekoje. Šie įvykiai turėtų atspindėti reikšmingus programos būsenos pokyčius. Apsvarstykite tokius veiksmus kaip:

Pavyzdys: El. prekybos platformoje pagrindiniai įvykiai gali būti „Užsakymas sukurtas“ (OrderCreated), „Mokėjimas gautas“ (PaymentReceived), „Užsakymas išsiųstas“ (OrderShipped), „Prekė pridėta į krepšelį“ (ProductAddedToCart) ir „Vartotojo profilis atnaujintas“ (UserProfileUpdated).

2. Apibrėžkite įvykio struktūrą

Kiekvienas įvykis turėtų turėti gerai apibrėžtą struktūrą, apimančią šią informaciją:

Pavyzdys: „OrderCreated“ įvykis gali turėti šią struktūrą:

{
  "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. Pasirinkite įvykių saugyklą

Įvykių saugykla yra centrinė vieta įvykiams saugoti. Tai turėtų būti tik papildoma duomenų bazė, optimizuota įvykių sekoms rašyti ir skaityti. Yra keletas variantų:

Rinkdamiesi įvykių saugyklą, atsižvelkite į šiuos veiksnius:

4. Įdiekite įvykių publikavimą

Įvykus įvykiui, jūsų programa turi jį publikuoti įvykių saugykloje. Tai paprastai apima šiuos veiksmus:

Pavyzdys (naudojant hipotetinę „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. Kurkite skaitymo modelius (projekcijas)

Nors įvykių saugykla suteikia išsamią visų pakeitimų istoriją, dažnai nėra efektyvu tiesiogiai teikti užklausas skaitymo operacijoms. Vietoj to, galite kurti skaitymo modelius, taip pat žinomus kaip projekcijos, kurie yra optimizuoti konkretiems užklausų šablonams. Šie skaitymo modeliai yra gauti iš įvykių srauto ir atnaujinami asinchroniškai, kai publikuojami nauji įvykiai.

Pavyzdys: Galite sukurti skaitymo modelį, kuriame yra visų konkretaus kliento užsakymų sąrašas, arba skaitymo modelį, apibendrinantį tam tikro produkto pardavimo duomenis.

Norėdami sukurti skaitymo modelį, jūs prenumeruojate įvykių srautą ir apdorojate kiekvieną įvykį. Kiekvienam įvykiui atitinkamai atnaujinate skaitymo modelį.

Pavyzdys:

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. Apsaugokite įvykių saugyklą

Įvykių saugykla turi jautrius duomenis, todėl itin svarbu ją tinkamai apsaugoti. Apsvarstykite šias saugumo priemones:

7. Įdiekite auditą ir ataskaitas

Įdiegę įvykių valdymą (Event Sourcing), galite naudoti įvykių srautą audito ataskaitoms generuoti ir saugumo analizei atlikti. Galite teikti užklausas įvykių saugykloje, kad rastumėte visus įvykius, susijusius su konkrečiu vartotoju, operacija ar objektu. Taip pat galite naudoti įvykių srautą, kad atkurti sistemos būseną bet kuriuo laiko momentu.

Pavyzdys: Galite sugeneruoti ataskaitą, kurioje rodomi visi pakeitimai, atlikti konkrečiame vartotojo profilyje per tam tikrą laikotarpį, arba ataskaitą, kurioje rodomos visos konkretaus vartotojo inicijuotos operacijos.

Apsvarstykite šias ataskaitų teikimo galimybes:

Įvykių valdymo (Event Sourcing) iššūkiai

Nors įvykių valdymas (Event Sourcing) turi daug privalumų, jis taip pat kelia keletą iššūkių:

Geriausia įvykių valdymo (Event Sourcing) praktika

Norėdami sumažinti įvykių valdymo (Event Sourcing) iššūkius, laikykitės šių geriausių praktikų:

Realaus pasaulio įvykių valdymo (Event Sourcing) pavyzdžiai

Įvykių valdymas (Event Sourcing) naudojamas įvairiose pramonės šakose ir programose, įskaitant:

Išvada

Įvykių valdymas (Event Sourcing) yra galingas architektūrinis modelis, galintis iš esmės pakeisti jūsų audito sekos įgyvendinimą. Jis suteikia neprilygstamą atsekamumą, duomenų vientisumą ir sistemos atsparumą. Nors jis kelia tam tikrų iššūkių, įvykių valdymo (Event Sourcing) privalumai dažnai nusveria išlaidas, ypač sudėtingoms ir kritinėms sistemoms. Vadovaudamiesi šiame vadove aprašytomis geriausiomis praktikomis, galite sėkmingai įdiegti įvykių valdymą (Event Sourcing) ir sukurti patikimas bei audituojamas sistemas.

Tolesnis skaitymas