Java Event Sourcing umsetzen

SchlossPilsener

Cadet 1st Year
Registriert
Dez. 2020
Beiträge
10
Guten Tag,

in einer Übungsaufgabe soll ich Event Sourcing umsetzen. Ich kenne mich damit nicht aus und habe bisher nur von einem Beispiel abgekupfert.

Es geht um eine Klasse Teilnehmer.
Für diese Teilnehmer sollen nun Zustände angelegt werden:

Jeder Teilnehmer soll einen bestimmten Status hinsichtlich seiner Anmeldung haben. Es können dadurch drei verschiedene Zustände erreicht werden: ANGEMELDET, ANGEMELDETBEZAHLT und ABGEMELDET.

Jeder Wechsel des Zustandes führt zu einem Ereignis. Zu Beginn befindet sich der Teilnehmer im Zustand ANGEMELDET. Wenn das Ereignis "Zahlung" auftritt, dann wird der Teilnehmer in den Zustand ANGEMELDETBEZAHLT gesetzt. Wenn danach das Ereignis "Abmeldung" auftritt, dann wird der Teilnehmer in den Zustand abgemeldet überführt.

Bisher habe ich folgendes:
Ist das bisher in Ordnung? Habe mich vorerst nur am payedEvent versucht.

Java:
    package veranstaltung;

    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;

    @Entity
    public class Teilnehmer {

    
    static int idnumber=0;
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String vorname;
    private String wohnort;
    public enum Status {ANGEMELDET, ANGEMELDETBEZAHLT, ABGEMELDET}
    private Status st;

    protected Teilnehmer() {}
    
    public Teilnehmer(Long id, String name, String vorname,String wohnort)
    {
        this.id=id;
        this.name=name;
        this.vorname=vorname;
        this.wohnort=wohnort;
        st=Status.ANGEMELDET;
    }
    
    
    public static String erzeugeID ()
    {
        String id= "JAVALAND-";
        id=id+idnumber;
        idnumber++;
        return id;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVorname() {
        return vorname;
    }

    public void setVorname(String vorname) {
        this.vorname = vorname;
    }

    public String getWohnort() {
        return wohnort;
    }

    public void setWohnort(String wohnort) {
        this.wohnort = wohnort;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    
    
    public Status getSt() {
        return st;
    }

    public void setSt(Status st) {
        this.st = st;
    }

    public void handlePayedEvent(PayedEvent payedEvent) {
        payedEvent.getTeilnehmer().st=Status.ANGEMELDETBEZAHLT;
    }
    
    public String toString()
    {
        return getVorname()+getName()+getWohnort()+getSt();
        
    }
    }

Java:
package veranstaltung;

import java.time.LocalDate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class DomainEvent {
    
    private static final Logger log = LoggerFactory.getLogger(DomainEvent.class);
    
    LocalDate recordedDate, occuredDate;
    
    public DomainEvent(LocalDate occuredDate) {
        log.info("created at "+occuredDate.toString());
        this.occuredDate = occuredDate;
        this.recordedDate=LocalDate.now();
    }

    public abstract void process();
}

Java:
    package veranstaltung;

    import veranstaltung.DomainEvent;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

    public class EventProcessor {

    private static final Logger log= LoggerFactory.getLogger(EventProcessor.class);
    
    List logList= new ArrayList<DomainEvent>();
    
    public void process(DomainEvent domainEvent)
    {
        log.info("process"+domainEvent.getClass());
        domainEvent.process();
        logList.add(domainEvent);
    }
    
    }

Java:
    package veranstaltung;

    import java.time.LocalDate;

    public class PayedEvent extends DomainEvent{
    
    
    private Teilnehmer teilnehmer;
    
    public PayedEvent (LocalDate date, Teilnehmer t)
    {
        super (date);
        setTeilnehmer(teilnehmer);
    }

    @Override
    public void process() {
    teilnehmer.handlePayedEvent(this);
        
    }
    
    public PayedEvent setTeilnehmer(Teilnehmer teilnehmer)
    {
        this.teilnehmer=teilnehmer;
        return this;
    }
    
    public Teilnehmer getTeilnehmer() {
        return teilnehmer;
    }
    }
 
Prinzipiell ist der Aufbau schon ganz okay. Allerding musst du überlegen, dass ein Event ja von Ausserhalb in des System kommt. Daher kann das Event den Teilnehmer nicht kennen. Das kennt nur die ID. Durch das Event kann aber ein Service veranlaßt werden, den Teilnehmer aus der DB zu laden, und die Änderung vorzunehmen.
 
Na ja ich habe mich hier an diesem Beispiel orientiert Beispiel Event Sourcing.
Ich beschäftige mich damit, weil es höchstwahrscheinlich in der Prüfung drankommen wird. Das muss nur halbwegs den Prinzipien des Beispiels entsprechen.


Ich erhalte in der Zeile
Java:
eventProcessor.process(new PayedEvent(LocalDate.of(1970, 3, 20), t));
eine nullpointerexception. Wie kommt die zustande?

Java:
    package veranstaltung;

    import java.time.LocalDate;
import java.util.List;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;

import veranstaltung.Teilnehmer;
import veranstaltung.TeilnehmerRepository;

    @SpringBootApplication
    public class Main {

    public static void main(String [] args)
    {
    
        SpringApplication.run(Main.class, args);
        
    }
    }

    @Component
    class TeilnehmerRestCommandLineRunner implements CommandLineRunner {
    
    EventProcessor eventProcessor = new EventProcessor();
    Teilnehmer t = new Teilnehmer(5l,"meier","jochen","mainz");
    

    // this will inject the bike repository
    @Autowired
    TeilnehmerRepository teilnehmerRepository;

    @Override
    public void run(String... args) throws Exception {

    List<Teilnehmer> allTeilnehmer = (List<Teilnehmer>) teilnehmerRepository.findAll();

    System.out.println("There are "+allTeilnehmer.size()+" Teilnehmer in the repository.");
    for(Teilnehmer teilnehmer : allTeilnehmer) {
        System.out.println(teilnehmer.toString());
    }
        
    System.out.println(t);
    eventProcessor.process(new PayedEvent(LocalDate.of(1970, 3, 20), t));
        
    }
    }
 
Das kommt durch dein Copy und Paste...
Entwederist eventProcessor dann null an der Stelle oder in deinem Konstruktor passiert es
 
Ich habe im Debugger geschaut, der EventProcessor ist nicht null und der Teilnehmer t auch nicht.
 
Dann gib uns mal den Stacktrace damit wir nicht raten müssen. Nen Debugger ist eh das letzte Mittel was man nutzen sollte, logging ist oft sinnvoller.
Du klatscht da aber auch einiges an Code zusammen, was nicht funktionieren kann. Da gibt es eine Datenbank, aber der Teilnehmer wird hart im Code angegeben. Das macht schon mal Probleme, wenn du die Daten nachher aktualisieren willst. Die ID in der Entität wird generiert, aber in deinem Testobjekt wird sie mit 5 angegeben. Das wird dir auf die Füße fallen. Da du den Teilnehmer aus der DB laden musst, ist das Beispielprojekt gänzlich ungeeignet. Ein Event weiß nicht, wie es die Daten zu verarbeiten hat. Das weiß dann der Eventprocessor. Das muss sich auch um das Laden und Speichern der Daten in der DB kümmern.
 
Code:
java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:809) [spring-boot-2.4.4.jar:2.4.4]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:790) [spring-boot-2.4.4.jar:2.4.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) [spring-boot-2.4.4.jar:2.4.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1313) [spring-boot-2.4.4.jar:2.4.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) [spring-boot-2.4.4.jar:2.4.4]
    at veranstaltung.Main.main(Main.java:22) [classes/:na]
Caused by: java.lang.NullPointerException: null
    at veranstaltung.PayedEvent.process(PayedEvent.java:18) ~[classes/:na]
    at veranstaltung.EventProcessor.process(EventProcessor.java:19) ~[classes/:na]
    at veranstaltung.TeilnehmerRestCommandLineRunner.run(Main.java:49) ~[classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:806) [spring-boot-2.4.4.jar:2.4.4]

Das mit der ID wird noch geändert, da muss ich später nochmal schauen.
Ich habe testweise 3 Teilnehmer in einer data.sql Datei um zu sehen wie sich die Sache mit REST verhält. In der Prüfung wird es um REST und Event Sourcing gehen. Die data.sql Datei wird vermutlich gestrichen und die Objekte werden alle im CommandLineRunner letztendlich erzeugt.
 
Hmm, für mich wäre die Herangehensweise zu durcheinander. Woher die Daten herkommen, sollte dem Programm egal sein. Aber um dein Problem erstmal zu lösen, schau dir mal den Konstruktor vom Event an. Zeile 10 und 13 sind da interessant
Code:
setTeilnehmer(teilnehmer);
 
Okay ich bedanke mich.

Würden Sie sagen, dass es bzgl. des EventSourcings grundlegend akzeptierbar ist?
Unter der Ansicht, dass es zur simplen Demonstration geschaffen wurde.
 
Die Grundlagen passen wohl. Leider ist bei solchen Themen es immer schwer eine "simple" Demonstration zu erstellen. Es geht ja bei der Prüfung auch eher darum, dass Konstrukt verstanden zu haben. Also wird der Schwerpunkt darauf liegen, die unterschiedlichen Status des Teilnehmers verfolgen zu können.
 
Zurück
Oben