x Contact us ask us for an offer
Thank you for your message. We will contact you as soon as we understand your needs!
  • Witaj Hello!
  • Masz pytania? Any questions?
  • Skontaktuj się contact us
    we can help you with your business!
Blog
  • 05
    Feb
    Author: matix February 5, 2018 20:27
    How to implement Event Sourcing in Java

    If you are interested in Software architecture, you probably should take a look at Event Sourcing.
    In this post we will show you, how Event Sourcing implementation can help you with.

    The basic idea of Event Sourcing is the fact, that every domain (DDD - Domain Driven Design) event which has happened in the past, is stored in the database.

    Let's take a look at the quick example and compare two different entities.


    For example: We are preparing some Order System, where logged customer can order some products.

    How should the database look like?

    Product (productId, productTitle, productPrice)
    Customer (customerId, firstName, lastName)
    Order (orderId, customerId, List products)

    Will this solution work? YES
    Is this solution scalable? NO - why?
    a) You can't split the database into two small databases (one for Orders, one for Customers), because of the relations in the database. - it's possible to do with REST API, but it's hard and you brake the relations,
    b) There is a problem with database integration when we want to change the Order entity. For example, we don't want to have relations between Product and Order anymore, because the prices changes during the time. What then ? The table Order should change the schema to:

    Order (orderId, customerId, orderDetails)

    It would be great... but what about the old orders? They are not compatible.



    Event Sourcing


    Event Store

    Event Sourcing is the solution.
    It gives you high-level of security and you a guarantee that you will never loose data. It can also allow you to change the database schema and type in the future.

    How does it work?
    What if you save all the user Order actions to a database. Let's create a MySQL database with one table called: EventStore. That table will store all the events in your application. The table schema is shown below:

    package org.codingisthinking.eventstore.hibernate;

    import javax.persistence.*;
    import java.sql.Timestamp;
    import java.time.Instant;
    import java.util.UUID;

    @Entity
    public class EventStore {
    @Id
    protected UUID id;
    protected UUID aggregateId;
    protected String aggregateType;
    protected String payLoad;
    protected int version;

    @Basic
    protected Timestamp createdAt;

    public EventStore() {
    this.id = UUID.randomUUID();
    this.version = 1;
    }

    public EventStore(UUID id, UUID aggregateId, String aggregateType, String payLoad, int version) {
    this.id = id;
    this.aggregateId = aggregateId;
    this.aggregateType = aggregateType;
    this.payLoad = payLoad;
    this.version = version;
    }

    public UUID getId() {
    return id;
    }

    public UUID getAggregateId() {
    return aggregateId;
    }

    public String getAggregateType() {
    return aggregateType;
    }

    public String getPayLoad() {
    return payLoad;
    }

    public String getCreatedAt() {
    if (createdAt != null) {
    return createdAt.toString();
    }

    return createdAt.toString();
    }

    public int getVersion() {
    return version;
    }

    public void setVersion(int version) {
    this.version = version;
    }

    @PrePersist
    public void prePersist() {
    this.createdAt = Timestamp.from(Instant.now());
    }
    }




    It's very simple implementation of Event Store. It stores:

    Event ID,
    AggregateId (Id of the Order, usually UUID generated ID),
    Aggregate Type (OrderCreated, OrderPlaced, OrderCanceled, OrderChangedStatus),
    PayLoad (JSON data connected to type of event), version of change.
    Date - when the event has happended;

    Below the example of Events for single Order:

    31, 063bde1e-0aaf-11e8-ba89-0ed5f89f718b, OrderCreated, {"id": "063bde1e-0aaf-11e8-ba89-0ed5f89f718b", "userDetails": {"firstname": "Mateusz"}, "status": "NEW"}, 3.01.2019
    32, 063bde1e-0aaf-11e8-ba89-0ed5f89f718b, OrderPaid, 4.01.2019
    33, 063bde1e-0aaf-11e8-ba89-0ed5f89f718b, OrderChangedStatus, {"status" : "DELIVERED"}, 8.01.2019


    After this 3 lines we can see that a customer firstName: Mateusz has Ordered new Items. Later the Order was paid and after that, the Order Changed Status to DELIVERED.

    Okay, so we have a list of events stored in the database. The EventStore can also have any event types, such as: CustomerRegistered, CustomerLoggedIn, CustomerChangedProfileData, ProductAddedByAdministrator, PaymentSuccessEmailSent, etc.

    Why is this safe? Because you can make a horizontal replications of your database. One database can be a MASTER, the rest can be SLAVES which will only synchronize.
    The EventStore is a table where you can only INSERT data. DON'T select, DON'T update. Just insert. If you want to remove Order, just add new Event OrderRemoved. If you want to update OrderDetails, just create new event OrderDetailsChanged, etc.


    I need a list of Orders with status DELIVERED


    Let's say, we need a list of orders from this month in the Backend (for administrator). As I mentioned before, we can't select from the EventStore database.

    Projection/ View
    Have you ever heard of views in (my)SQL? Yes.. that's how projections works. We need to create a view for an Administrator. You can act table/entity as.. view or projection. If you want to see all the orders from this month, just call the table LastMonthOrders with fields:

    OrderId, OrderDate, OrderDetails, UserId

    I am using H2 in-memory database for that kind of tables / projections.


    Putting it all together - the Order entity view / projection with data from EventStore


    All we need to do now is take the Events we are interested in and build a temporary in-memory database (LastMonthOrders).
    It's very simple to do it in Java Spring:

    package org.codingisthinking.conversation.runners;

    import com.github.kevinsawicki.http.HttpRequest;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codingisthinking.conversation.services.EventStoreMessageBusService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.core.env.Environment;
    import org.springframework.stereotype.Component;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPubSub;

    import java.util.ArrayList;
    import java.util.List;

    @Component
    public class EventSubscriberRunner implements CommandLineRunner {
    @Autowired
    EventStoreMessageBusService eventStoreMessageBusService;

    @Autowired
    Environment environment;

    @Override
    public void run(String... args) throws Exception {
    Jedis jedis = new Jedis(environment.getProperty("redis.host"));

    List eventInterestedList = new ArrayList<>();
    eventInterestedList.add("OrderCreated");
    eventInterestedList.add("OrderStatusChanged");
    eventInterestedList.add("OrderCanceled");

    for (String aggregateType : eventInterestedList) {
    // The URI for the event Store API (REST) for fetching all the Order Event data.
    String uri = environment.getProperty("event_store.uri") + "/eventstore/aggregateType/" + aggregateType;

    System.out.println("Fetching data from: " + uri);

    HttpRequest httpRequest = HttpRequest.get(uri);

    if (httpRequest.ok()) {
    try {
    ObjectMapper objectMapper = new ObjectMapper();

    Event[] eventStore = objectMapper.readValue(httpRequest.body(), Event[].class);

    for (Event event : eventStore) {
    // CHECK THE EVENT TYPE AND save / update the read-only H2 database (such as LastMonthOrders).
    }

    } catch (Exception e) {
    System.out.println(e.getMessage());
    }
    }
    }

    }
    }




    Then you can just fetch all the data from the temporary database like it's been a normal table.
    Now, depending on the Event, we create new Entity, update or removing the existing row from the H2 database.



    Why Event Sourcing?


    1. No data are lost - all the application history is stored in the EventStore table,
    2. When you need to change an business logic, you simply work with temporary in-memory entities/tables, so... you are building new table (with new columns) when you restart the application.
    3. It's a introduction to CQRS (Command Query Responsibility Segregation) ideology and a great way to building a scalable Microservices.
    4. EventStore is a big database and stores all the informations about your customers, from which you can create new Projections (tables) an get a great business tips for a future.
    5. You don't have to use one technology - every single Event is stored in a EventStore and you can fetch it via Rest API, so.... Build a microservices crafted exactly for a business needs. Every single microservice can be done in different language: Python, Java, C#, PHP, whatever.

    If you are interested more about Event Sourcing, feel free to contact us via e-Mail: biuro@wiseweb.pl.

    Next articles will explain the CQRS technology and how the Events should be published.

    Best Regards,
    Mateusz