Xử lý sự kiện trong EzyPlatform
Back To Blogs- EzyArticle: Plugin cung cấp các nội dung bài viết, quản lý menu, danh mục bài viết giống với wordpress.
- ECommerce: Plugin cung cấp các tính năng liên quan đến thương mại điện tử.
- EzyDefence: Cung cấp các tính năng hỗ trợ bảo mật và truy vết thông tin.
- ...
Nghĩa là chúng không có khả năng sử dụng được các lớp, các hàm của nhau, chính vì vậy EzyPlatform sử dụng cơ chế sự kiện để giao tiếp giữa các plugin với nhau, tránh tình trạng các plugin phụ thuộc chồng chéo vào nhau.
Thiết kế các lớp xử lý sự kiện
Các lớp để quản lý và xử lý sự kiện trong EzyPlatform sẽ được tổ chức như sau:
- Lớp EventHandlerManager sẽ quản lý toàn bộ các lớp xử lý sự kiện trong phạm vi admin, web hay socket, nghĩa là bạn không thể gửi một sự kiện từ admin đến web hay socket được vì ba thành phần này độc lập với nhau. Một sự kiện sẽ có nhiều lớp xử lý chứ không phải là một, mã nguồn cài đặt bên trong của nó tương ứng là:
Map<String, List<EventHandler>>. - Giao diện EventHandler cung cấp hàm xử lý sự kiện và hàm trả về tên của sự kiện.
- Lớp trừu tượng AbstractEventHandler cài đặt sẵn một số hàm tiện ích để giảm thiểu số lượng dòng code mà các nhà phát triển phải tạo ra.
Cần lưu ý rằng, khi một sự kiện được gửi đi, nếu kết quả củ một handler trả về kết quả khác null thì các handler còn lại sẽ không được xử lý nữa, vậy nên nếu bạn muốn lấy dữ liệu từ một plugin khác ví dụ từ plugin A lấy dữ liệu từ plugin B, thì ở plugin B bạn phải cài đặt một handler trả về kết quả khác null.
Ngoài ra EzyPlatform cũng cung cấp một giao diện có tên VoidEvent. Nếu bạn muốn tất cả các handlers đều được gọi để xử lý bạn có thể tạo ra một lớp Event cài đặt giao diện này, ví dụ:
@Getter @AllArgsConstructor public class OrderActionEvent implements VoidEvent { private final long orderId; private final OrderAction action; }
Mã nguồn sử dụng
Như đã nói ở trên chúng ta có thể sử dụng việc xử lý sự kiện để:
- Lấy dữ liệu.
- Lan truyền thông tin.
Sử dụng sự kiện để lấy dữ liệu.
Một trong những vấn đề thực tế mà mình gặp phải đó là việc gọi để lấy thông tin từ EzyOA plugin sang plugin EzyCRM để lấy thông tin khách hàng cho việc gửi thông báo, hai plugin này là tách biệt với nhau vậy nên mình đã tạo ra một sự kiện chung:
public static final String INTERNAL_EVENT_NAME_FETCH_CUSTOMER_INFORMATION = "fetch_customer_information";
Sau đó ở plugin EzyCRM mình cài đặt lớp xử lý sự kiện như sau:
public class FetchCustomerInformationEventHandler extends AbstractEventHandler<Long, Map<String, Object>> { private final CustomerService customerService; @Override protected Map<String, Object> doHandleEventData(Long data) { CustomerModel customer = customerService.getCustomerById(data); return customer != null ? customer.toTemplateParameters() : null; } @Override public String getEventName() { return INTERNAL_EVENT_NAME_FETCH_CUSTOMER_INFORMATION; } }
Đầu vào là id của khách hàng và kết quả đầu ra là một Map chứa các thông tin của khác hàng thay vì đối tượng Customer, vì ở bên EzyOA không phụ thuộc voà EzyCRM nên không có lớp để ép kiểu.
Ở phía đầu gọi là EzyOA mã nguồn sẽ như sau:
public Map<String, Object> getCustomer( long userId ) { return eventHandlerManager.handleEvent( INTERNAL_EVENT_NAME_FETCH_CUSTOMER_INFORMATION, userId ); }
Sử dụng sự kiện để lan truyền thông tin.
Ở trên chúng ta đã tạo ra lớp OrderActionEvent đại diện cho sự kiện liên quan đến đơn hàng, khi lập trình Ecommerce, có một đơn đã thay đổi trạng, chúng ta có thể gửi đi sự kiện như sau:
eventHandlerManager.handleEvent(
new OrderActionEvent(
orderId,
OrderAction.REVIEWING
)
);
Ở đầu nhận, ví dụ là game EzySmashers, sẽ cần lắng nghe sự kiện và kiểm tra trạng thái đơn hàng là gì, sau đó cập nhật thông tin của người dùng tương ứng với mã nguồn như sau:
@EzySingleton @AllArgsConstructor public class EzySmashersOrderActionEventHandler extends OrderActionEventHandler { private final WebDataMetaService dataMetaService; private final WebOrderProductService orderProductService; private final WebOrderService orderService; private final WebProductCurrencyService productCurrencyService; private final WebProductService productService; private final WebUserBalanceService userBalanceService; private final WebShopService shopService; private final WebSettingService settingService; @Override protected void processEventData(OrderActionEvent data) { long orderId = data.getOrderId(); OrderModel order = orderService.getOrderById(orderId); List<OrderProductModel> orderProducts = orderProductService .getProductsByOrderId( orderId ); processOrderDiamondProducts( orderProducts, order.getCreatorUserId(), data.getAction() ); } private void processOrderDiamondProducts( List<OrderProductModel> orderProducts, long creatorUserId, OrderAction orderAction ) { if (orderAction == OrderAction.APPROVED) { ShopModel diamondShop = shopService.getShopByName( settingService.getTextValue( EzySmashersConstants.SETTING_KEY_DIAMOND_SHOP_NAME ) ); BigInteger totalNumberOfDiamonds = BigInteger.ZERO; for (OrderProductModel orderProduct : orderProducts) { long productId = orderProduct.getProductId(); ProductModel product = productService.getProductById(productId); if (product.getShopId() == diamondShop.getId()) { BigInteger numberOfDiamonds = new BigInteger( dataMetaService.getMetaValueByDataIdAndMetaKey( "ecommerce_products", productId, "number_of_diamonds" ) ); totalNumberOfDiamonds = totalNumberOfDiamonds.add( numberOfDiamonds.multiply(orderProduct.getAmount()) ); } } ProductCurrencyModel currency = productCurrencyService.getCurrencyByIsoCode( DIAMOND_CURRENCY_ISO_CODE ); userBalanceService.increaseUserBalance( creatorUserId, currency.getId(), new BigDecimal(totalNumberOfDiamonds) ); } } }
Tổng kết lại
Xử lý sự kiện trong EzyPlatform chính là một trong những cài đặt của observer design pattern. Dựa vào mẫu thiết kế này, nó giúp các thành phần, các plugin trong EzyPlatform phụ thuộc lỏng lẻo vào nhau, tránh tình trạng chồng chéo và phụ thuộc vòng tròn. Bạn có thể tận dụng cơ chế này của EzyPlatform mà không cần phải cài đặt lại nữa nhé.