Quick Start

 

This guickStart guide introduces how to apply domain events in aggregate root entity. not include command's single writer.

Maven pom.xml

<dependency>
    <groupId>org.jdon</groupId>
    <artifactId>jdonframework</artifactId>
    <version>6.8</version>
</dependency> 

this quickStart's source code in github,

 

1. Create a Domain Model

DDD can help us find some domain models in business requirement, ar first let's begin with a domain model that represents a aggregate root entity. assume its name is MyModel:

public class MyModel {
   private String Id;
   private String name;
     ....
}

 

when you begin to use jdonframework , you should make sure the domain object in-memory cache, there are two steps to reach the goal:

(1) annotate your aggregate root with @Model

@Model
public class MyModel {
   private String Id;
   private String name;
     ....
}

full code see MyModel.java

 

(2) annotate your factory/repository of the aggregate root with @Introduce(“modelCache”)

 

When a domain model object with @Model is fetch from repository, we need use @Introduce(“modelCache”) to mak the object live in memory.

@Introduce(“modelCache”) must be annotated in the interfaces that @Model objects are created or reconstructed , the interfaces is such as repository class. DDD think repository is the transfering betwing the database data and domain object.

This step is very important for domain events

cachge

full code: RepositoryImp.java

 

2.create a Domain Event

domain event is emitted from a domain model, generally is a aggregate root entity, jdonframework can help you develope domain event easily.

at first, we create a domain event object, its name is MyModelDomainEvent, and this class is event producer, you can use jdonframework annotations to reache this goal.

annotate a Producer class with @Introduce("message")

@Introduce("message")
public class MyModelDomainEvent {

        @Send("MyModel.findName")
        public DomainMessage asyncFindName(FindNameEvent findNameEvent) {
                return new DomainMessage(findNameEvent);
        }

        @Send("saveMyModel")
        public DomainMessage save(MyModelCreatedEvent myModelCreatedEvent) {
                return new DomainMessage(myModelCreatedEvent);
        }

}

in this class, there are two methods with @Send("mytopic"); another is @Send("MyModel.findName"). they are all producer's emit methods

@Introduce(“message”):"message" is the om.jdon.domain.message.MessageInterceptor configured in aspect.xml It means introducing a MessageInterceptor for this class.

@ Send("mytopic"): "mytopic" is your topic name that equals to the "mytopic" in @Consumer("mytopic");
In this method, you must marshal your data into a DomainMessag that can be unmarshal in consumers.

full code :MyModelDomainEvent.java


3. create a consumer for domain event

every domain event has two parts: producer and consumer, annotate a class with @Consumer("mytopic"); or @OnEvent to make it is a consumer.

in above step, MyModelDomainEvent.java have two topic/queue names: "MyModel.findName" and "saveMyModel", we must implement two consumers to listen/observe them.

the first topic consumer class:

@Consumer("MyModel.findName")
public class FindNameListener implements DomainEventHandler {

        public void onEvent(EventDisruptor event, boolean endOfBatch) throws Exception {
                FindNameEvent findNameEvent = (FindNameEvent) event.getDomainMessage().getEventSource();
                System.out.println("Asynchronous eventMessage=" + findNameEvent.getId());
                event.getDomainMessage().setEventResult("Asynchronous eventMessage=" + findNameEvent.getId());
        }
}

 

"FindNameListener" class must be labeled with @Consumer annotation, and implement a interface com.jdon.domain.message.DomainEventHandler, and then accomplish interface's method "onEvent". in this method you can acquire the domain Event(a value object, such as orderCreated ) that is emitted from producer "MyModelDomainEvent", here is FindNameEvent VO:

public class FindNameEvent {

        private final Long id;

        public FindNameEvent(Long id) {
                super();
                this.id = id;
        }

        public Long getId() {
                return id;
        }

}

 

but maybe you wonder where the "FindNameEvent" instance comes from? in Procuder class, the method "asyncFindName" returns a type DomainMessage, and the code is "return new DomainMessage(findNameEvent)", yeah, the findNameEvent is input. in general, we should input a DomainEvents, such as "new OrderCreated(xx,xx)" , donot directly put entity instance.

 

another consumer annotation is @OnEvent, it can annotate any class with @Component:

@Component("mymrepository")
@Introduce("modelCache")
public class RepositoryImp implements MyModelRepository {

        @Around
        public MyModel getModel(Long key) {
                MyModel mym = new MyModel();
                mym.setId(key);
                return mym;
        }

        @OnEvent("saveMyModel")
        public void save(MyModelCreatedEvent myModelCreatedEvent) {
                System.out.print("\n No.2 @OnEvent:" + this.getClass().getName());

        }

}

 

the topic "saveMyModel" consumer is RepositoryImp, in this repository we just find the aggregate root "MyModel" instance, and we can save the model by "save(MyModelCreatedEvent myModelCreatedEvent)", here method parameter MyModelCreatedEvent is from the producer's method return's object(DomainMessage). myModelCreatedEvent as event object is too recommended, in general, we transfer a domainevent such as new OrderSaved(), if you saving the domain events, you can replay them to get root entity's state, this is called Event Soucing.

 

public class MyModelCreatedEvent {

        private final Long id;
        private final String name;

        public MyModelCreatedEvent(Long id, String name) {
                super();
                this.id = id;
                this.name = name;
        }

        public Long getId() {
                return id;
        }

        public String getName() {
                return name;
        }

}

 

 

4. Introduce producer into domain object

now we have a producer and two consumer, we need introduce the producer into our aggregate root entity(domain object):

@Model
public class MyModel {

        private Long id;
        private String name;

        @Inject
        private MyModelDomainEvent myModelDomainEvent;
        public String getName() {
                if (this.name == null) {
                        DomainMessage message = myModelDomainEvent.asyncFindName(new FindNameEvent(this.id));
                        this.name = (String) message.getBlockEventResult();
                }
                return name;
        }

        public void save() {
                myModelDomainEvent.save(new MyModelCreatedEvent(this.id, this.name));
        }

 

@Inject is used to inject any class instance into domain model, here we inject our procuder MyModelDomainEvent.

and in save() method, we invoke "myModelDomainEvent.save(new MyModelCreatedEvent(..))" to implement fire a domain event, such as new OrderSaved(); here,

getName() method send a lazy-load event.

 

5. Client

all main steps finished.

Service is a bounded context, and is a client for our aggregate:

@Service("serviceSampleTwo")
public class ServiceSampleTwo implements IServiceSampleTwo {

        private MyModelRepository repository;

        public ServiceSampleTwo(MyModelRepository repository) {
                super();
                this.repository = repository;
                this.nameFinderContext = nameFinderContext;
        }

        public Object eventPointEntry() {
                MyModel myModel = repository.getModel(new Long(100));
                MyModel myModel2 = repository.getModel(new Long(100));
                return myModel.sayHelloSynchronous() + " and " + myModel2.getName();

        }
         public void onEventTest() {
                MyModel myModel = repository.getModel(new Long(100));
                myModel.setName("new name");
                myModel.save();
        }

 

Service's invoking :

AppUtil appUtil = new AppUtil();
IServiceSampleTwo serviceSample = (IServiceSampleTwo) appUtil.getService("serviceSampleTwo");
String res = (String) serviceSample.eventPointEntry();
System.out.print(res);
Assert.assertEquals(res, "Synchronous sayHello and Asynchronous eventMessage=100"); serviceSample.onEventTest();

above code is in SampleAppTest.java

 

this quickStart's source code in github

getting-started-with-jdon blank project