Getting started with Spring Boot
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.
1. Add the starter
Section titled “1. Add the starter”<dependency> <groupId>org.occurrent</groupId> <artifactId>occurrent-mongodb-spring-boot-starter</artifactId> <version>0.34.0</version></dependency>compile 'org.occurrent:occurrent-mongodb-spring-boot-starter:0.34.0'libraryDependencies += "org.occurrent" % "occurrent-mongodb-spring-boot-starter" % "0.34.0"@Grab(group='org.occurrent', module='occurrent-mongodb-spring-boot-starter', version='0.34.0')[org.occurrent/occurrent-mongodb-spring-boot-starter "0.34.0"]'org.occurrent:occurrent-mongodb-spring-boot-starter:jar:0.34.0'<dependency org="org.occurrent" name="occurrent-mongodb-spring-boot-starter" rev="0.34.0" />There’s also a reactive starter for Project Reactor.
2. Turn it on
Section titled “2. Turn it on”Add @EnableOccurrent (from org.occurrent.springboot.mongo.blocking) next to @SpringBootApplication, and declare how your domain events are named in the event store:
@SpringBootApplication@EnableOccurrentpublic class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }
@Bean public CloudEventTypeMapper<GameEvent> cloudEventTypeMapper() { return ReflectionCloudEventTypeMapper.simple(GameEvent.class); }}@SpringBootApplication@EnableOccurrentclass Application {
@Bean fun cloudEventTypeMapper(): CloudEventTypeMapper<GameEvent> = ReflectionCloudEventTypeMapper.simple(GameEvent::class.java)}
fun main(args: Array<String>) { runApplication<Application>(*args)}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.
3. Configure it
Section titled “3. Configure it”Point Spring Boot at MongoDB, and give the converter a CloudEvent source:
spring: mongodb: uri: mongodb://localhost:27017/gameoccurrent: cloud-event-converter: cloud-event-source: urn:mycompany:mygame:gameSubscriptions 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.
4. Write events
Section titled “4. Write events”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:
@RestControllerpublic 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)); }}@RestControllerclass GameController(private val applicationService: ApplicationService<GameEvent>) {
@PostMapping("/games/{gameId}/guesses") fun guess(@PathVariable gameId: UUID, @RequestParam word: String) { applicationService.execute(gameId) { history -> Game.guess(history, word) } }}5. React to events
Section titled “5. React to events”Annotate a method with @Subscription (from org.occurrent.annotation) to receive the events as they are written:
@Componentpublic class GuessLogger {
@Subscription(id = "logGuesses") void log(GuessMade e) { System.out.println("Someone guessed %s".formatted(e.word())); }}@Componentclass GuessLogger {
@Subscription(id = "logGuesses") fun log(e: GuessMade) { println("Someone guessed ${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.
Where to go next
Section titled “Where to go next”@Projectionmaintains a read model, and@Sagaruns a process that reacts to events over time.- Inject
DomainEventQueriesto query events directly. - What the starter configures says what the starter sets up for you, and the Spring Boot starter page says how to replace any of it with your own bean.
- Testing with Spring Boot shows how to test the application.
- The layer 1 pages, starting with the ApplicationService, explain the parts the starter builds on.
