There are many concepts and techniques for creating loosely coupled applications,Event is one of them. Events can eliminate many of dependencies in your code. Some times without events, SRP* is very hard to implement. Observable interface in java can help us to implement events (through Observer Pattern).
But wait, the goal of this post is a fast tutorial about Spring Event. Spring has some nice facilities for creating Event Driven Applications. You can raise a specific event in a bean and listen to it in the other bean.
Imagine a simple application with these requirements:
- There are some orders that can have different status
- when an order is in DELIVERED or POSTPONED state we need to send an email to the customer
The first (but not the best) solution for requirement satisfaction is sending email in our Order model, But There are some defects:
- It’s not responsibility of Order to sending email.
- If you follow Domain Driven principle, Order is a domain object but Email Sender maybe is a service ( different from Domain Service ) so you can’t use it in your model.
The other solution is raising some events in our Order model after changing its status. I’s not concern of Order what will happen after raising this event. In our example we need to listen to a specific kind of events, analyze them and do some business (Sending Email).
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
| @Configurable public class Order implements ApplicationEventPublisherAware { private final String orderId; private final Date createDate; private final CustomerInfo customerInfo; private ApplicationEventPublisher eventPublisher; private Date lastUpdateDate; private Status status; public Order(String orderId, CustomerInfo customerInfo) { this .orderId = orderId; this .customerInfo = customerInfo; status = Status.MODIFIABLE; this .createDate = new Date(); this .lastUpdateDate = this .createDate; } public String getOrderId() { return orderId; } public void checkOut() { if (status == Status.DELIVERED) { throw new IllegalStateException(String.format( "Order is already delivered" )); } this .status = Status.CHECKED_OUT; this .lastUpdateDate = new Date(); } public void deliver() { if ( this .status != Status.CHECKED_OUT && this .status != Status.POSTPONED) { throw new IllegalStateException(String.format( "Order status should be CHECKED OUT for delivery to be called. but is : %s" , status)); } this .status = Status.DELIVERED; this .lastUpdateDate = new Date(); this .eventPublisher.publishEvent( new OnOrderDelivered( this )); } public void postponeDelivery() { if (status != Status.CHECKED_OUT && status != Status.POSTPONED) { throw new IllegalStateException(String.format( "Can not postpone delivery in this state: %s" , status)); } this .status = Status.POSTPONED; this .lastUpdateDate = new Date(); this .eventPublisher.publishEvent( new OnOrderPostponed( this )); } public Status getStatus() { return status; } public CustomerInfo getCustomerInfo() { return customerInfo; } public Date getLastUpdateDate() { return lastUpdateDate; } public Date getCreateDate() { return createDate; } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this .eventPublisher = applicationEventPublisher; } public static enum Status { MODIFIABLE, CHECKED_OUT, POSTPONED, DELIVERED, CANCELED; } } |
As you see Order is a configurable class, if you have not worked with this concept before, don’t be anguished. For this post just you need to know Configurable classes can create everywhere with new keyword but they are managed by spring, so you can inject other beans in them or use most of the spring facilities with them. I promise to post an article about it as soon as possible
Order class implements
org.springframework.context.ApplicationEventPublisherAware
interface.
this interface has a setter method with name
setApplicationEventPublisher
that presents ApplicationEventPublisher
for using in your class.
as you see in deliver method we used this object to publish an event (
you can publish every event that extends
we have raised another event
this.eventPublisher.publishEvent(new OnOrderDelivered(this))
).you can publish every event that extends
org.springframework.context.ApplicationEvent
we have raised another event
OnOrderPostponed
when an order becomes postponed.
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
| public abstract class OnOrderStatusChanged extends ApplicationEvent { private final Order order; public OnOrderStatusChanged(Order source) { super (source); this .order = source; System.out.println(String.format( "Order:%s status is changed to %s" , source.getOrderId(), source.getStatus())); } public Order getOrder() { return order; } } public class OnOrderDelivered extends OnOrderStatusChanged { public OnOrderDelivered(Order order) { super (order); } } public class OnOrderPostponed extends OnOrderStatusChanged { public OnOrderPostponed(Order order) { super (order); } } |
OnOrderStatusChanged is an abstract class what OnOrderDelivered and OnOrderPostponed extends it. until now we could create our events and raise them. now if you create a spring test and call deliver method of an order you will see “Order:X status is changed to DELIVERED” The last step is doing something when these events published. we want to send an email to the customer when these method raised. additionally it’s valuable for customer to posting the product when his order is in delivered state.
Listeners are simple Beans that implements generic ApplicationListener interface. Parameter type in this interface is the type of event that you want to listen to it. it is possible to defince parameter type as the parent and listen to all of its childres. for example in our model if we use
Listeners are simple Beans that implements generic ApplicationListener interface. Parameter type in this interface is the type of event that you want to listen to it. it is possible to defince parameter type as the parent and listen to all of its childres. for example in our model if we use
OnOrderStatusChanged
our listener will catch all event from OnOrderDelivered
and OnOrderPostponed
It can be suitable for sending email in our scenario. but we don’t use this model and create two different listener for them.
As you see below their code is very simple
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| @Service public class OrderDeliveredEmailSender implements ApplicationListener,Ordered { @Override public void onApplicationEvent(OnOrderDelivered event) { System.out.println(String.format( "Message sent for delivered order to:%s ORDER-ID:%s" ,event.getOrder().getCustomerInfo().getEmail(),event.getOrder().getOrderId())); } @Override public int getOrder() { return 100 ; } } @Service public class OrderPostponedEmailSender implements ApplicationListener { @Override public void onApplicationEvent(OnOrderPostponed event) { System.out.println(String.format( "Message sent for postponed order to:%s ORDER-ID:%s" , event.getOrder().getCustomerInfo().getEmail(), event.getOrder().getOrderId())); } } |
These two beans will fire onApplicationEvent when correspond event is raised. For posting product to the customer we need to create another Listener for OnOrderDelivered event.
1
2
3
4
5
6
7
8
9
10
11
12
| @Service public class OnOrderDeliveredPost implements ApplicationListener,Ordered { @Override public void onApplicationEvent(OnOrderDelivered onOrderDelivered) { System.out.println(String.format( "Order:%s is posting for customer." ,onOrderDelivered.getOrder().getOrderId())); } @Override public int getOrder() { return 1000 ; } } |
As you see this listener will send product to the customer when its state is DELIVERED. But wait there what is Ordered interface? if you have not use
org.springframework.core.Ordered
interface, it is valuable to know that with using this interface you can define the order between beans in a collection. in our scenario customer like to receive an email before we post the product to him. for this purpose these two class implement Ordered interface, don’t forget the lowest order has the highest priority.
*Single Responsibility Principle
You can download source code from : https://github.com/psycho-ir/spring-event.git
Немає коментарів:
Дописати коментар