Tạo và xử lý sự kiện nội bộ (internal event) trong ezyplatform plugin
Back To BlogsMục tiêu
- Giúp bạn hiểu được về sự kiện nội bộ trong EzyPlatform.
- Giúp bạn tạo được sự kiện nguồn cho EzyPlatform.
- Giúp bạn xử lý được sự kiện trong EzyPlatform.
Giới thiệu về sự kiện nội bộ trong EzyPlatform
Ở đây chúng ta sẽ có lớp sự kiện, lớp quản lý sử kiện và lớp xử lý sự kiện.
Lớp sự kiện
EzyPlatform không quy định cụ thể lớp sự kiện là gì, nghĩa là bạn có thể gửi bất loại sự kiện gì với bất kỳ loại dữ liệu gì.
Tuy nhiên nó cũng có interface VoidEvent để chỉ định rằng, nếu bạn xử lý bất kỳ lớp nào thừa kế VoidEvent thì sự kiện nó sẽ không quan tâm đến kết quả trả về và sẽ thực thi trên mọi lớp xử lý sự kiện.
Ví dụ nếu bạn có một lớp MessageEvent cài đặt VoidEvent thế này:
public class MessageEvent implements VoidEvent {}
Và có 3 lớp event hander xử lý sự kiện MessageEvent thì cả 3 lớp sẽ được thực thi mà không bị dừng lại ở giữa chừng dù có kết quả trả về.
Lớp quản lý sự kiệnEventHandlerManager
Tất cả các sự kiện nội bộ trong EzyPlatform sẽ được quản lý thông qua lớp EventHandlerManager:
public class EventHandlerManager { public <R> R handleEvent(Object data) {} public <R> R handleEvent(String eventName, Object data) {} }
Lớp này cung cấp hàm handleEvent để gửi sự kiện cho các đối lớp xử lý, các tham số của nó bao gồm:
eventName: Là tên sự kiện, nếu bạn không truyền tên sự kiện nó sẽ lấy tên lớp dữ liệu làm tên sự kiện.data: Là đối tượng chứa dữ liệu cho sự kiện, nó có thể là bất kỳ loại dữ liệu nào.
Ở admin thì bạn sẽ sử dụng lớp AdminEventHandlerManager kiểu inject thông qua hàm tạo:
@EzySingleton @AllArgsConstructor public class AdminEventExample { private final AdminEventHandlerManager eventHandlerManager; }
Hoặc inject thông qua việc sử dụng @EzyAutoBind annotation:
@Setter @EzySingleton public class AdminEventExample { @EzyAutoBind private AdminEventHandlerManager eventHandlerManager; }
Cũng tương tự với web chúng ta có thể sử dụng kiểu inject qua hàm tạo:
@EzySingleton @AllArgsConstructor public class WebEventExample { private final WebEventHandlerManager eventHandlerManager; }
Hoặc inject thông qua việc sử dụng @EzyAutoBind annotation:
@Setter @EzySingleton public class WebEventExample { @EzyAutoBind private WebEventHandlerManager eventHandlerManager; }
Lớp xử lý sự kiện
EzyPlatform cung cấp cả interface EventHandler:
public interface EventHandler<D, R> { R handleEventData(D data); String getEventName(); int getPriority(); }
Và lớp abstract AbstractEventHandler:
// D: Là kiểu dữ liệu của sự kiện // R: Là kiểu dữ liệu kết quả trả về public abstract class AbstractEventHandler<D, R> extends EzyLoggable implements EventHandler<D, R> { @Override public final R handleEventData(D data) { try { return doHandleEventData(data); } catch (Exception e) { return handleException(e); } } protected R doHandleEventData(D data) { processEventData(data); return null; } protected void processEventData(D data) {} protected R handleException(D data, Exception e) { return handleException(e); } protected R handleException(Exception e) { processException(e); return null; } protected void processException(Exception e) {} @Override public String getEventName() { // cài đặt } }
Để bạn có thể cài đặt lớp xử lý sự kiện, tuy nhiên thông thường bạn sẽ hay sử dụng AbstractEventHandler hơn vì nó đã cung cấp các hàm tiện ích rồi:
doHandleEventData: Bạn ghi đè hàm này nếu muốn trả về kết quả, khi kết quả trả về khácnullthì các event handler khác sẽ không được thực thi nữa.processEventData: Bạn ghi đè hàm này nếu chỉ muốn xử lý sự kiện và không trả về kết quả.handleException: Bạn ghi đè hàm này nếu muốn trả về kết quả khi có lỗi, khi kết quả trả về khácnullthì các event handler khác sẽ không được thực thi nữa.processException: Bạn ghi đè hàm này nếu chỉ muốn xử lỗi và không trả về kết quả.
Tạo một sự kiện nguồn
Như vậy là bạn đã hiểu về sự kiện nội bộ của EzyPlatform. Bây giờ quay trở lại với lớp AdminPersonalPostWordCountDataAppender mà chúng ta đã tạo trong bài trước.
Bây giờ chúng ta sẽ bổ sung thêm mã nguồn để phát sinh sự kiện personal_post_word_count cho các lớp xử lý như sau.
Đầu tiên chúng ta sẽ tạo một constant trong lớp PersonalConstants:
public static final String INTERNAL_EVENT_NAME_POST_WORD_COUNT = "personal_post_word_count";
Tiếp theo chúng ta sẽ bổ sung mã nguồn vào lớp AdminPersonalPostWordCountDataAppender như sau:
@EzySingleton public class AdminPersonalPostWordCountDataAppender extends AdminDataAppender<PostHistory, PersonalPostWordCount, Long> { // các cài đặt khác private final AdminEventHandlerManager eventHandlerManager; // các cài đặt khác public AdminPersonalPostWordCountDataAppender( ClockProxy clock, AdminEventHandlerManager eventHandlerManager, // các cài đặt khác ) { // các cài đặt khác this.eventHandlerManager = eventHandlerManager; // các cài đặt khác } // các cài đặt khác @Override protected void addDataRecord(PersonalPostWordCount dataRecord) { postWordCountRepository.save(dataRecord); eventHandlerManager.handleEvent( INTERNAL_EVENT_NAME_POST_WORD_COUNT, EzyMapBuilder.mapBuilder() .put("postId", dataRecord.getPostId()) .put("wordCount", dataRecord.getWordCount()) .toMap() ); } // các cài đặt khác }
Ở đây trong hàm addDataRecord, sau khi đã lưu bản ghi số lương từ đếm được vào cơ sở dữ liệu, chúng ta sẽ phát đi sự kiện để cho các lớp xử lý nhận được.
Xử lý sự kiện
Hãy nói chúng ta sẽ dựa vào số lượng từ để tính ra thời gian đọc cho bài viết, chúng ta sẽ tạo ra lớp AdminTimeToReadPostWordCountEventHandler với nội dung như sau:
@EzySingleton @AllArgsConstructor public class AdminTimeToReadPostWordCountEventHandler extends AbstractEventHandler<Map<String, Object>, Void> { private final AdminPostMetaService postMetaService; private static final BigDecimal READ_TIME_PER_WORD = new BigDecimal("0.24"); @Override protected void processEventData(Map<String, Object> data) { long postId = (long) data.get("postId"); long wordCount = (long) data.get("wordCount"); long readTime = BigDecimal.valueOf(wordCount) .multiply(READ_TIME_PER_WORD) .longValue(); postMetaService.savePostMetaUniqueKey( postId, META_KEY_TIME_TO_READ, readTime ); } @Override public String getEventName() { return INTERNAL_EVENT_NAME_POST_WORD_COUNT; } }
Lớp này xử lý sự kiện personal_post_word_count, tính ra thời gian đọc dựa trên số từ và lưu dữ liệu vào bảng ezyarticle_post_meta.
Lưu ý
- Sự kiện có thể được phát sinh và xử lý ở các module khác nhau, ví dụ một sự kiện được phát sinh ở module EzyArticle admin có thể được xử lý ngay tại EzyArticle hoặc tại module E-Commerce admin.
- Sự kiện không thể được xử lý trên các thành phần khác nhau, ví dụ sự kiện được phát sinh ở admin sẽ không được xử lý ở web hay socket vì các thành phần này là tách biệt chạy trên các chương trình khác nhau với cổng khác nhau.
- Sự kiện không thể xử lý trên các máy chủ khác nhau, ví dụ sự kiện được phát sinh ở module EzyArticle admin được cài trên EzyPlatform ở máy chủ A, sẽ không thể gửi được sự kiện đến cho module EzyArticle admin trên EzyPlatform ở máy chủ B vì các máy chủ này chạy độc lập với nhau.