콘텐츠로 이동

Your First Event

This tutorial walks you through creating a complete event publishing setup with Curve.

Scenario

We'll build a user registration system that publishes a USER_REGISTERED event to Kafka.

Step 1: Define Your Domain Model

User.java
public class User {
    private Long id;
    private String username;
    private String email;
    private String firstName;
    private String lastName;
    private Instant createdAt;

    // getters, setters, constructors
}
UserRequest.java
public record UserRequest(
    String username,
    String email,
    String firstName,
    String lastName
) {}

Step 2: Create Event Payload

Create a payload class that implements DomainEventPayload:

UserRegisteredPayload.java
import io.github.closeup1202.curve.core.envelope.DomainEventPayload;
import io.github.closeup1202.curve.spring.pii.annotation.PiiField;
import io.github.closeup1202.curve.spring.pii.type.PiiType;
import io.github.closeup1202.curve.spring.pii.strategy.PiiStrategy;

public class UserRegisteredPayload implements DomainEventPayload {

    private Long userId;
    private String username;

    @PiiField(type = PiiType.EMAIL, strategy = PiiStrategy.MASK)
    private String email;

    @PiiField(type = PiiType.NAME, strategy = PiiStrategy.HASH)
    private String firstName;

    @PiiField(type = PiiType.NAME, strategy = PiiStrategy.HASH)
    private String lastName;

    private Instant registeredAt;

    // Constructor
    public UserRegisteredPayload(User user) {
        this.userId = user.getId();
        this.username = user.getUsername();
        this.email = user.getEmail();
        this.firstName = user.getFirstName();
        this.lastName = user.getLastName();
        this.registeredAt = user.getCreatedAt();
    }

    // Getters
    // ...
}

PII Protection

The @PiiField annotation automatically masks/hashes sensitive data before publishing.

Step 3: Implement Service with @PublishEvent

UserService.java
import io.github.closeup1202.curve.spring.audit.annotation.PublishEvent;
import io.github.closeup1202.curve.core.type.EventSeverity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    @PublishEvent(
        eventType = "USER_REGISTERED",
        severity = EventSeverity.INFO,
        payload = "#result.toEventPayload()"
    )
    public User registerUser(UserRequest request) {
        // Business logic
        User user = new User();
        user.setUsername(request.username());
        user.setEmail(request.email());
        user.setFirstName(request.firstName());
        user.setLastName(request.lastName());
        user.setCreatedAt(Instant.now());

        return userRepository.save(user);
    }
}

Annotation Breakdown

Parameter Value Description
eventType "USER_REGISTERED" Unique identifier for this event type
severity EventSeverity.INFO Event severity level
payload SpEL expression Extracts data from method result

SpEL Expressions

  • #result - Method return value
  • #args[0] - First method argument
  • #args[0].toEventDto() - Custom transformation

Step 4: Configure Application

application.yml
spring:
  application:
    name: user-service
    version: 1.0.0

  kafka:
    bootstrap-servers: localhost:9092

  jpa:
    hibernate:
      ddl-auto: update

curve:
  enabled: true

  kafka:
    topic: event.audit.v1
    dlq-topic: event.audit.dlq.v1
    async-mode: false  # Synchronous for reliability

  pii:
    enabled: true
    crypto:
      default-key: ${PII_ENCRYPTION_KEY}  # Base64-encoded 32-byte key

Step 5: Test Your Event

Unit Test with MockEventProducer

UserServiceTest.java
import io.github.closeup1202.curve.spring.test.MockEventProducer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context;

@SpringBootTest
class UserServiceTest {

    @Autowired
    private UserService userService;

    @Autowired
    private MockEventProducer mockEventProducer;

    @Test
    void shouldPublishEventWhenUserRegisters() {
        // Given
        UserRequest request = new UserRequest(
            "john_doe",
            "john@example.com",
            "John",
            "Doe"
        );

        // When
        User user = userService.registerUser(request);

        // Then
        assertThat(user.getId()).isNotNull();

        // Verify event was published
        var events = mockEventProducer.getPublishedEvents();
        assertThat(events).hasSize(1);

        var event = events.get(0);
        assertThat(event.getEventType()).isEqualTo("USER_REGISTERED");
        assertThat(event.getSeverity()).isEqualTo(EventSeverity.INFO);
    }
}

Integration Test with Kafka

UserServiceIntegrationTest.java
import org.springframework.kafka.test.context.EmbeddedKafka;
import org.springframework.test.context.TestPropertySource;

@SpringBootTest
@EmbeddedKafka(partitions = 1, topics = {"event.audit.v1"})
@TestPropertySource(properties = {
    "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}",
    "curve.enabled=true"
})
class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Test
    void shouldPublishToKafkaWhenUserRegisters() throws Exception {
        // Setup consumer
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<String> receivedEvent = new AtomicReference<>();

        // Consume from Kafka
        // ... (consumer setup)

        // When
        userService.registerUser(new UserRequest(
            "test_user", "test@example.com", "Test", "User"
        ));

        // Then
        assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
        assertThat(receivedEvent.get()).contains("USER_REGISTERED");
    }
}

Step 6: Verify in Kafka

Start your application and register a user:

curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john_doe",
    "email": "john@example.com",
    "firstName": "John",
    "lastName": "Doe"
  }'

Check the Kafka topic:

kafka-console-consumer --bootstrap-server localhost:9092 \
    --topic event.audit.v1 --from-beginning

Published event:

{
  "eventId": "7355889748156289024",
  "eventType": "USER_REGISTERED",
  "occurredAt": "2026-02-03T10:30:00Z",
  "publishedAt": "2026-02-03T10:30:00.123Z",
  "severity": "INFO",
  "metadata": {
    "source": {
      "serviceName": "user-service",
      "serviceVersion": "1.0.0"
    },
    "trace": {
      "traceId": "abc123"
    }
  },
  "payload": {
    "userId": 1,
    "username": "john_doe",
    "email": "j***@ex***.com",  // ← Masked!
    "firstName": "5a4b3c...",    // ← Hashed!
    "lastName": "7f8e9d...",     // ← Hashed!
    "registeredAt": "2026-02-03T10:30:00Z"
  }
}

Notice

PII fields are automatically protected based on @PiiField annotations!

Next Steps