Latviešu

Uzziniet, kā Pasākumu avota izmantošana var mainīt jūsu audita pārskatu izveidi, nodrošinot nepārspējamu izsekojamību, datu integritāti un sistēmas noturību. Iepazīstieties ar praktiskiem piemēriem un ieviešanas stratēģijām.

Pasākumu avota izmantošana: audita pārskatu izveide robustām un izsekojamām sistēmām

Mūsdienu sarežģītajā un savstarpēji savienotajā digitālajā vidē robusta un visaptveroša audita pārskata uzturēšana ir ārkārtīgi svarīga. Tā ne tikai bieži vien ir regulatīva prasība, bet arī būtiska atkļūdošanai, drošības analīzei un sistēmas attīstības izpratnei. Pasākumu avots (Event Sourcing) — arhitektūras modelis, kas uztver visas lietojumprogrammas stāvokļa izmaiņas kā pasākumu secību — piedāvā elegantu un jaudīgu risinājumu uzticamu, auditējamu un paplašināmu audita pārskatu izveidei.

Kas ir Pasākumu avots?

Tradicionālās lietojumprogrammas parasti saglabā tikai datu pašreizējo stāvokli datubāzē. Šī pieeja apgrūtino to rekonstruēt pagātnes stāvokļus vai izprast notikumu secību, kas noveda pie pašreizējā stāvokļa. Pasākumu avots, gluži pretēji, koncentrējas uz katras nozīmīgās lietojumprogrammas stāvokļa izmaiņas uztveršanu kā nemaināmu pasākumu. Šie pasākumi tiek saglabāti tikai pievienojamu (append-only) pasākumu krātuvē, veidojot pilnīgu un hronoloģisku visu sistēmas darbību reģistru.

Padomājiet par to kā par bankas konta reģistru. Tā vietā, lai vienkārši ierakstītu pašreizējo atlikumu, katrs depozīts, izņemšana un pārskaitījums tiek ierakstīts kā atsevišķs pasākums. Atskaņojot šos pasākumus, jūs varat rekonstruēt konta stāvokli jebkurā laikā.

Kāpēc izmantot Pasākumu avotu audita pārskatiem?

Pasākumu avots piedāvā vairākas pārliecinošas priekšrocības audita pārskatu izveidei:

Pasākumu avota izveide audita pārskatiem: pakāpenisks ceļvedis

Šeit ir praktisks ceļvedis Pasākumu avota izveidei audita pārskatiem:

1. Galveno pasākumu identificēšana

Pirmais solis ir identificēt galvenos pasākumus, kurus vēlaties uztvert savā audita pārskatā. Šiem pasākumiem ir jāatspoguļo nozīmīgas izmaiņas lietojumprogrammas stāvoklī. Apsveriet darbības, piemēram:

Piemērs: E-komercijas platformai galvenie pasākumi varētu būt "OrderCreated" (Pasūtījums izveidots), "PaymentReceived" (Maksājums saņemts), "OrderShipped" (Pasūtījums nosūtīts), "ProductAddedToCart" (Prece pievienota grozam) un "UserProfileUpdated" (Lietotāja profils atjaunināts).

2. Pasākumu struktūras definēšana

Katram pasākumam ir jābūt skaidri definētai struktūrai, kas ietver šādu informāciju:

Piemērs: "OrderCreated" pasākumam varētu būt šāda struktūra:

{
  "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. Pasākumu krātuves izvēle

Pasākumu krātuve ir centrālais pasākumu saglabāšanas repozitorijs. Tai jābūt tikai pievienojamai (append-only) datubāzei, kas optimizēta pasākumu secību rakstīšanai un lasīšanai. Ir pieejamas vairākas iespējas:

Izvēloties pasākumu krātuvi, apsveriet tādus faktorus kā:

4. Pasākumu publicēšanas īstenošana

Kad notiek pasākums, jūsu lietojumprogrammai tas ir jāpublicē pasākumu krātuvē. Tas parasti ietver šādus soļus:

Piemērs (izmantojot hipotētisko 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. Nolasīšanas modeļu (projekciju) izveide

Lai gan pasākumu krātuve nodrošina pilnīgu visu izmaiņu vēsturi, to bieži vien nav efektīvi tieši vaicāt lasīšanas operācijām. Tā vietā jūs varat izveidot nolasīšanas modeļus, kas pazīstami arī kā projekcijas, kas ir optimizēti konkrētiem vaicājumu modeļiem. Šie nolasīšanas modeļi tiek iegūti no pasākumu plūsmas un tiek asinhroni atjaunināti, kad tiek publicēti jauni pasākumi.

Piemērs: Jūs varētu izveidot nolasīšanas modeli, kas satur visu pasūtījumu sarakstu konkrētam klientam, vai nolasīšanas modeli, kas apkopo pārdošanas datus par noteiktu preci.

Lai izveidotu nolasīšanas modeli, jūs abonējat pasākumu plūsmu un apstrādājat katru pasākumu. Katram pasākumam attiecīgi atjaunojiet nolasīšanas modeli.

Piemērs:

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);
    }

    // Citas pasākumu apstrādes funkcijas priekš PaymentReceivedEvent, OrderShippedEvent utt.
}

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. Pasākumu krātuves drošināšana

Pasākumu krātuvē ir sensitīvi dati, tāpēc ir ļoti svarīgi to pienācīgi nodrošināt. Apsveriet šādus drošības pasākumus:

7. Audita un pārskatu izveide

Pēc Pasākumu avota ieviešanas jūs varat izmantot pasākumu plūsmu, lai ģenerētu audita pārskatus un veiktu drošības analīzi. Jūs varat vaicāt pasākumu krātuvei, lai atrastu visus pasākumus, kas saistīti ar konkrētu lietotāju, darījumu vai entītiju. Jūs varat arī izmantot pasākumu plūsmu, lai jebkurā laikā rekonstruētu sistēmas stāvokli.

Piemērs: Jūs varētu ģenerēt pārskatu, kas parāda visas izmaiņas, kas veiktas konkrētā lietotāja profilā noteiktā laika posmā, vai pārskatu, kas parāda visus darījumus, ko veicis konkrēts lietotājs.

Apsveriet šādas pārskatu iespējas:

Pasākumu avota izaicinājumi

Lai gan Pasākumu avots piedāvā daudzas priekšrocības, tas rada arī dažus izaicinājumus:

Pasākumu avota labākās prakses

Lai mazinātu Pasākumu avota izaicinājumus, ievērojiet šīs labākās prakses:

Pasākumu avota reālās pasaules piemēri

Pasākumu avots tiek izmantots dažādās nozarēs un lietojumprogrammās, tostarp:

Secinājums

Pasākumu avots ir spēcīgs arhitektūras modelis, kas var mainīt jūsu audita pārskatu izveidi. Tas nodrošina nepārspējamu izsekojamību, datu integritāti un sistēmas noturību. Lai gan tas rada dažus izaicinājumus, Pasākumu avota priekšrocības bieži vien pārsniedz izmaksas, īpaši sarežģītām un kritiskām sistēmām. Ievērojot šajā ceļvedī izklāstītās labākās prakses, jūs varat veiksmīgi ieviest Pasākumu avotu un veidot robustas un auditējamas sistēmas.

Tālākā lasīšana