Occurrent 0.14.0 is now available. It’s features a new query DSL as well as a Spring Boot starter project to faster get started with Spring Boot. Changes are:

  • Non-backward compatible change: CloudEventConverter’s now has a third method that you must implement:

    /**
     * Get the cloud event type from a Java class.
     *
     * @param type The java class that represents a specific domain event type
     * @return The cloud event type of the domain event (cannot be {@code null})
     */
    @NotNull String getCloudEventType(@NotNull Class<? extends T> type);
    

    The reason for this is that several components, such as the subscription dsl, needs to get the cloud event type from the domain event class. And since this is highly related to “cloud event conversion”, this method has been added there to avoid complicating the API.

  • Introduced the concept of CloudEventTypeMapper’s. A cloud event type mapper is component whose purpose it is to get the cloud event type from a domain event type and vice versa. Cloud Event Type mappers are used by certain CloudEventConverter’s to define how they should derive the cloud event type from the domain event as well as a way to reconstruct the domain event type from the cloud event type. and the new domain queries DSL. You should use the same type mapper instance for all these components. To write a custom type mapper, depend on the org.occurent:cloudevent-type-mapper-api module and implement the org.occurrent.application.converter.typemapper.CloudEventTypeMapper (functional) interface.
  • Introduced a blocking Query DSL. It’s a small wrapper around the EventStoreQueries API that lets you work with domain events instead of CloudEvents. Depend on the org.occurrent:query-dsl-blocking module and create an instance of org.occurrent.dsl.query.blocking.DomainEventQueries. For example:

    EventStoreQueries eventStoreQueries = .. 
    CloudEventConverter<DomainEvent> cloudEventConverter = ..
    DomainEventQueries<DomainEvent> domainEventQueries = new DomainEventQueries<DomainEvent>(eventStoreQueries, cloudEventConverter);
       
    Stream<DomainEvent> events = domainQueries.query(Filter.subject("someSubject"));
    

    There’s also support for skip, limits and sorting and convenience methods for querying for a single event:

    Stream<DomainEvent> events = domainQueries.query(GameStarted.class, GameEnded.class); // Find only events of this type
    GameStarted event1 = domainQueries.queryOne(GameStarted.class); // Find the first event of this type
    GamePlayed event2 = domainQueries.queryOne(Filter.id("d7542cef-ac20-4e74-9128-fdec94540fda")); // Find event with this id
    

    There are also some Kotlin extensions that you can use to query for a Sequence of events instead of a Stream:

    val events : Sequence<DomainEvent> = domainQueries.queryForSequence(GamePlayed::class, GameWon::class, skip = 2) // Find only events of this type and skip the first two events
    val event1 = domainQueries.queryOne<GameStarted>() // Find the first event of this type
    val event2 = domainQueries.queryOne<GamePlayed>(Filter.id("d7542cef-ac20-4e74-9128-fdec94540fda")) // Find event with this id
    
  • Introducing spring boot starter project to easily bootstrap Occurrent if using Spring. Depend on org.occurrent:spring-boot-starter-mongodb and create a Spring Boot application annotated with @SpringBootApplication as you would normally do. Occurrent will then configure the following components automatically:
    • Spring MongoDB Event Store instance (EventStore)
    • A Spring SubscriptionPositionStorage instance
    • A durable Spring MongoDB competing consumer subscription model (SubscriptionModel)
    • A Jackson-based CloudEventConverter
    • A GenericApplication instance (ApplicationService)
    • A subscription dsl instance (Subscriptions)
    • A reflection based type mapper that uses the fully-qualified class name as cloud event type (you should absolutely override this bean for production use cases) (CloudEventTypeMapper) For example, by doing:
      @Bean
      public CloudEventTypeMapper<GameEvent> cloudEventTypeMapper() {
        return ReflectionCloudEventTypeMapper.simple(GameEvent.class);
      }
      

      This will use the “simple name” (via reflection) of a domain event as the cloud event type. But since the package name is now lost, the ReflectionCloudEventTypeMapper will append the package name of GameEvent when converting back into a domain event. This only works if all your domain events are located in the exact same package as GameEvent. If this is not the case you need to implement a more advanced CloudEventTypeMapper such as:

      class CustomTypeMapper : CloudEventTypeMapper<GameEvent> {
          override fun getCloudEventType(type: Class<out GameEvent>): String = type.simpleName
            
          override fun <E : GameEvent> getDomainEventType(cloudEventType: String): Class<E> = when (cloudEventType) {
              GameStarted::class.simpleName -> GameStarted::class
              GamePlayed::class.simpleName -> GamePlayed::class
              // Add all other events here!!
              ...
              else -> throw IllegalStateException("Event type $cloudEventType is unknown")
          }.java as Class<E>
      }
      

      See org.occurrent.springboot.OccurrentMongoAutoConfiguration if you want to know exactly what gets configured.

  • Upgraded spring-boot from 2.5.4 to 2.5.6.