Skip to content

Getting started with Spring Boot

Spring Boot · Modules: occurrent-mongodb-spring-boot-starter

With Spring Boot and MongoDB, the Spring Boot starter sets up what Getting started wires by hand. It gives you ApplicationService, Subscriptions and DomainEventQueries beans to inject. Your domain model stays the same, a plain function from past events to new events.

<dependency>
<groupId>org.occurrent</groupId>
<artifactId>occurrent-mongodb-spring-boot-starter</artifactId>
<version>0.34.0</version>
</dependency>

There’s also a reactive starter for Project Reactor.

Add @EnableOccurrent (from org.occurrent.springboot.mongo.blocking) next to @SpringBootApplication, and declare how your domain events are named in the event store:

@SpringBootApplication
@EnableOccurrent
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CloudEventTypeMapper<GameEvent> cloudEventTypeMapper() {
return ReflectionCloudEventTypeMapper.simple(GameEvent.class);
}
}

The type ends up in every stored event, so decide on it before you go to production. Without your own mapper the starter uses the fully qualified class name. See CloudEvent conversion for the other choices.

Point Spring Boot at MongoDB, and give the converter a CloudEvent source:

spring:
mongodb:
uri: mongodb://localhost:27017/game
occurrent:
cloud-event-converter:
cloud-event-source: urn:mycompany:mygame:game

Subscriptions read MongoDB change streams, so MongoDB has to run as a replica set. A single node replica set is fine for development. Configuration properties lists everything else you can set.

Inject the ApplicationService and call your domain functions through it. It loads the stream, runs your function and appends the new events under optimistic concurrency:

@RestController
public class GameController {
private final ApplicationService<GameEvent> applicationService;
public GameController(ApplicationService<GameEvent> applicationService) {
this.applicationService = applicationService;
}
@PostMapping("/games/{gameId}/guesses")
void guess(@PathVariable UUID gameId, @RequestParam String word) {
applicationService.execute(gameId, history -> Game.guess(history, word));
}
}

Annotate a method with @Subscription (from org.occurrent.annotation) to receive the events as they are written:

@Component
public class GuessLogger {
@Subscription(id = "logGuesses")
void log(GuessMade e) {
System.out.println("Someone guessed %s".formatted(e.word()));
}
}

The subscription stores where it is, so it continues from there after a restart, and when you run several instances only one of them receives each event.