Skip to content

CloudEvents

The module sda-commons-cloudevents provides some glue code to work with CloudEvents on top of Apache Kafka.

Introduction

CloudEvents is a general standard that can be used in combination with your favourite eventing tool like ActiveMQ or Kafka. The CloudEvents specification defines concrete bindings to define how the general specification should be applied to a specific tool. This module provides POJOs to use CloudEvent's structured content mode.

Producing CloudEvents

For simplicity, we recommend to extend our base class and add your own class for the data property. Additional documentation can be added at class level and for the data type.

Custom event

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
 * Copyright 2022- SDA SE Open Industry Solutions (https://www.sda.se)
 *
 * Use of this source code is governed by an MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT.
 */
package org.sdase.commons.spring.boot.cloudevents.app.partner;

import com.fasterxml.jackson.annotation.JsonClassDescription;
import io.swagger.v3.oas.annotations.media.Schema;
import java.net.URI;
import org.sdase.commons.spring.boot.cloudevents.CloudEventV1;

@JsonClassDescription("An event that is published when a partner has been created.")
public class PartnerCreatedEvent extends CloudEventV1<PartnerCreatedEvent.PartnerCreated> {

  private static final String DEFAULT_SOURCE = "/SDA-SE/partner/partner-stack/partner-service";
  private static final String DEFAULT_TYPE = "com.sdase.partner.ods.partner.created";

  public PartnerCreatedEvent() {
    super();
    super.setSource(URI.create(DEFAULT_SOURCE));
    super.setType(DEFAULT_TYPE);
  }

  @JsonClassDescription("Details about the created partner.")
  public record PartnerCreated(
      @Schema(
              description = "The unique id of a partner.",
              example = "FF427BC8-B38F-43CC-8AAB-512843808A18")
          String id) {}
}

You can use a standard org.springframework.kafka.support.serializer.JsonSerializer to publish the event.

Consuming CloudEvents

You can use org.springframework.kafka.support.serializer.JsonDeserializer to consume CloudEvents.

Polymorphism

Usually, an eventing API consists of multiple events related to one aggregate. You can use Jacksons subtype features to define multiple events in one model.

Multiple events in one API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
 * Copyright 2022- SDA SE Open Industry Solutions (https://www.sda.se)
 *
 * Use of this source code is governed by an MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT.
 */
package org.sdase.commons.spring.boot.cloudevents.app.polymorphism;

import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME;
import static org.sdase.commons.spring.boot.cloudevents.app.polymorphism.CarLifecycleEvents.MANUFACTURED;
import static org.sdase.commons.spring.boot.cloudevents.app.polymorphism.CarLifecycleEvents.SCRAPPED;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.sdase.commons.spring.boot.cloudevents.CloudEventV1;

@JsonTypeInfo(use = NAME, property = "type", visible = true, include = EXISTING_PROPERTY)
@JsonSubTypes({
  @Type(value = CarLifecycleEvents.CarManufactured.class, name = MANUFACTURED),
  @Type(value = CarLifecycleEvents.CarScrapped.class, name = SCRAPPED)
})
public abstract class CarLifecycleEvents<T> extends CloudEventV1<T> {

  static final String MANUFACTURED = "se.sda.car.manufactured";
  static final String SCRAPPED = "se.sda.car.scrapped";

  public static class CarManufactured
      extends CarLifecycleEvents<CarManufactured.CarManufacturedData> {
    public CarManufactured() {
      super();
      super.setType(MANUFACTURED);
    }

    public record CarManufacturedData(String brand, String model) {}
  }

  public static class CarScrapped extends CarLifecycleEvents<CarScrapped.CarScrappedData> {
    public CarScrapped() {
      super();
      super.setType(SCRAPPED);
    }

    public record CarScrappedData(ScrapReason reason) {
      public enum ScrapReason {
        ACCIDENT,
        TECHNICAL_DAMAGE
      }
    }
  }
}
Note: If such a model grows, you may want to extract the data types as top level classes.