Skip to main content

Domain-event

Introduction

The domain-event module is a wrapper around Disruptor. It can help your system implement models similar to Guava EventBus and Spring ApplicationEvent for decoupling business logic and reducing concurrency complexity.

Disruptor is an open-source concurrency framework and can be viewed as a lightweight, ultra-fast single-machine MQ. It is a high-performance queue developed by LMAX (a UK FX trading company), greatly simplifying concurrent program development and winning the 2011 Duke's Program Framework Innovation Award.


Domain-event features

  1. Domain-driven design based on the LMAX architecture.
  2. Follows the single responsibility principle, enabling high extensibility, high scalability, and low coupling.
  3. Asynchronous, highly concurrent, thread-safe processing via Disruptor's ring buffer.
  4. Uses event consumption style coding, which keeps code organized even when business logic becomes complex, with lower maintenance cost.
  5. Flexible customization of business thread models.
  6. Plugin-style domain events, fully pluggable and composable.

The module is simple to use. In practice, you only need to focus on two things:

  1. Define business data carriers (domain messages).
  2. Handle events for those business data carriers (domain events).

Performance Data

see https://lmax-exchange.github.io/disruptor/user-guide/index.html

Multiple Producer
Run 0Disruptor=26,553,372 ops/sec
Run 1Disruptor=28,727,377 ops/sec
Run 2Disruptor=29,806,259 ops/sec
Run 3Disruptor=29,717,682 ops/sec
Run 4Disruptor=28,818,443 ops/sec
Run 5Disruptor=29,103,608 ops/sec
Run 6Disruptor=29,239,766 ops/sec
Single Producer
Run 0Disruptor=89,365,504 ops/sec
Run 1Disruptor=77,579,519 ops/sec
Run 2Disruptor=78,678,206 ops/sec
Run 3Disruptor=80,840,743 ops/sec
Run 4Disruptor=81,037,277 ops/sec
Run 5Disruptor=81,168,831 ops/sec
Run 6Disruptor=81,699,346 ops/sec

Usage Scenarios

Scenario 1

Assume a business scenario where after user registration is completed, you need to trigger both email notification and reward points logic. Without using MQ, what are possible approaches?

Possible approaches include:

  1. After user registration completes, start threads for email notification and reward points.
  2. Use disruptor.
  3. Producer-consumer pattern.
  4. Observer pattern, etc.

Here we use Disruptor to implement a model similar to Spring ApplicationEvent, referred to here as domain events. Event-driven and observer patterns are similar in some aspects: when a subject changes, dependents are notified. However, the observer pattern is typically tied to a single event source, while event-driven models can be associated with multiple event sources.



Scenario 2

A domain event can be treated like a thread and is recommended for compute-intensive business. For example, in card-playing logic, all tables can share one domain event for play operations. When game settlement involves IO, move settlement to another thread so play threads are not blocked.

How to Install

This module is optional. Add it to pom.xml when needed.

see https://central.sonatype.com/artifact/com.iohao.net/extension-domain-event

pom.xml
<dependency>
<groupId>com.iohao.net</groupId>
<artifactId>extension-domain-event</artifactId>
<version>${ionet.version}</version>
</dependency>

Defining Domain Messages

  1. Define a domain entity and implement the Eo interface.
  2. Eo is provided by the framework. Domain messages are user-defined, and using the Eo suffix is recommended.
public record StudentEo(int id) implements Eo {
}

Defining Domain Events

To define a domain event, implement DomainEventHandler. This interface handles domain messages, and each consumer class should handle one event only (single responsibility principle).

public final class StudentEmailEventHandler1 implements DomainEventHandler<StudentEo> {
@Override
public void onEvent(StudentEo studentEo, boolean endOfBatch) {
log.debug("Sending email to a student: {}", studentEo);
}
}

Test

  • code 6: stop domain events.
  • code 12: configure domain event - send an email to a student.
  • code 13: configure domain event - go home.
  • code 14: configure domain event - put the student to sleep.
  • code 17: start domain events.
  • code 22: publish event.
public class StudentDomainEventTest {
DomainEventApplication domainEventApplication;

@AfterEach
public void tearDown() {
domainEventApplication.stop();
}

@BeforeEach
public void setUp() {
var setting = new DomainEventSetting();
setting.addEventHandler(new StudentEmailEventHandler1());
setting.addEventHandler(new StudentGoHomeEventHandler2());
setting.addEventHandler(new StudentSleepEventHandler3());

domainEventApplication = new DomainEventApplication();
domainEventApplication.startup(setting);
}

@Test
public void testEventSend() {
new StudentEo(1).send();
}
}
tip
  • Event publishing is simple; only one event is configured above.
  • If you later need to record what courses the student took today, just add another configuration (extensible) with no business code changes (low coupling).
  • If you later no longer need the email event, remove its configuration directly, still with no code changes (high scalability).
new StudentEo(1).send();

Providing Domain Events for Third-Party Libraries

We can provide domain events for third-party classes. Assume UserLogin is from a third-party library.

public record UserLogin(int id, String name) {
}

Below is our handler for UserLogin.

...
@Test
public void testEventSend() {
var userLogin = new UserLogin(101, "Michael Jackson");
DomainEventPublish.send(userLogin);
}

public class UserLoginEmailEventHandler implements DomainEventHandler<UserLogin> {
@Override
public void onEvent(UserLogin userLogin, boolean endOfBatch) {
log.info("Sending email to logged-in user {}", userLogin);
}
}