Skip to content

Instantly share code, notes, and snippets.

@aleixmorgadas
Last active September 6, 2023 04:07
Show Gist options
  • Save aleixmorgadas/be30c6030db2fd9b560ac31cb873f68c to your computer and use it in GitHub Desktop.
Save aleixmorgadas/be30c6030db2fd9b560ac31cb873f68c to your computer and use it in GitHub Desktop.
Aggregate with MongoDB

Entity Event Sourced with Spring Boot and MongoDB

package dev.aleixmorgadas.eventsourcedmongo.domain;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.*;
@Document
public class Profile {
@Id
String id;
@Transient
final State state = new State();
final List<Event> events = new ArrayList<>();
public Profile(String id, String name) {
apply(new Event.Created(id, name));
}
@PersistenceCreator
Profile(String id, List<Event> events) {
this.id = id;
events.forEach(this::apply);
}
void apply(Event event) {
events.add(event);
// update with pattern matching in Java 21
if (event instanceof Event.Created created) {
on(created);
} else if (event instanceof Event.AgeUpdated ageUpdated) {
on(ageUpdated);
}
}
void on(Event.Created created) {
id = created.id;
state.name = created.name;
}
void on(Event.AgeUpdated ageUpdated) {
state.age = ageUpdated.age();
}
String name() {
return state.name;
}
public static Profile of(String name) {
return new Profile(UUID.randomUUID().toString(), name);
}
public int age() {
return state.age;
}
public void withAge(int age) {
apply(new Event.AgeUpdated(age));
}
static class State {
String name;
int age;
}
sealed interface Event {
record Created(String id, String name) implements Event {}
record AgeUpdated(int age) implements Event {}
}
}
package dev.aleixmorgadas.eventsourcedmongo.domain;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import static org.assertj.core.api.Assertions.assertThat;
@DataMongoTest
@Testcontainers
public class ProfileRepositoryTest {
@Container
@ServiceConnection
static MongoDBContainer mongo = new MongoDBContainer(DockerImageName.parse("mongo:6.0.7"));
@Autowired
ProfileRepository repository;
@Test
void savesProfileIntoDB() {
var profile = Profile.of("Aleix");
profile.withAge(30);
repository.save(profile);
var id = profile.id;
var savedProfile = repository.findById(id).orElseThrow();
assertThat(savedProfile.events).hasSize(2);
assertThat(savedProfile.name()).isEqualTo("Aleix");
assertThat(savedProfile.age()).isEqualTo(30);
}
}
package dev.aleixmorgadas.eventsourcedmongo.domain;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ProfileTest {
@Test
void createsAProfile() {
var profile = Profile.of("Aleix");
assertThat(profile.name()).isEqualTo("Aleix");
assertThat(profile.events).hasSize(1);
}
@Test
void addsAge() {
var profile = Profile.of("Aleix");
profile.withAge(30);
assertThat(profile.age()).isEqualTo(30);
assertThat(profile.events).hasSize(2);
assertThat(profile.events.get(1)).isInstanceOf(Profile.Event.AgeUpdated.class);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment