Skip to content

Sự kiện

TrườngChi tiết
Tệp nguồnpackages/core/src/common/events/payment-events.ts (51 dòng), user-onboarding-events.ts (61 dòng), websocket-events.ts (95 dòng)
Importimport { <ClassName> } from '@nx/core';
Phương thức truyền tảiRedis pub/sub (sự kiện), WebSocket (phòng và chủ đề)
Tổng số lớp kênh2 lớp kênh sự kiện + 2 lớp tiện ích WebSocket

Tổng quan

BANA sử dụng Redis pub/sub cho giao tiếp sự kiện liên dịch vụ. Tất cả kênh sự kiện được định nghĩa trong @nx/core để cả nhà xuất bản và nhà tiêu thụ tham chiếu cùng hằng số. Ngoài ra, hai lớp tiện ích cung cấp đặt tên chuẩn hóa cho phòng và chủ đề WebSocket được dịch vụ Signal sử dụng.

Tất cả tên kênh sự kiện tuân theo quy ước đặt tên nhất quán:

<domain>.<entity>.<action>

Ví dụ: payment.order.success, user.seller.registered, commerce.initialized


1. Tóm tắt Kênh Sự kiện

KênhGiá trịNhà xuất bảnNhà tiêu thụ
PaymentEventChannels.PAYMENT_SUCCESSpayment.order.successDịch vụ SaleDịch vụ Finance, Dịch vụ Inventory
UserOnboardingEventChannels.SELLER_REGISTEREDuser.seller.registeredDịch vụ IdentityDịch vụ Commerce
UserOnboardingEventChannels.COMMERCE_INITIALIZEDcommerce.initializedDịch vụ CommerceDịch vụ Finance, Dịch vụ Inventory

2. PaymentEventChannels

Nguồn: packages/core/src/common/events/payment-events.ts

Kênh

TênGiá trịMô tả
PAYMENT_SUCCESS'payment.order.success'Thanh toán cho đơn hàng hoàn tất thành công (trạng thái PARTIAL hoặc COMPLETED)
  • Xuất bản bởi: Dịch vụ Sale (qua SalePaymentEventHandlerService)
  • Tiêu thụ bởi: Dịch vụ Finance (ghi nhận giao dịch thu nhập), Dịch vụ Inventory (trừ tồn kho)

Payload: TPaymentSuccessEvent

typescript
export type TPaymentSuccessEvent = {
  saleOrderId: string;
  saleOrderNumber: string;
  saleOrderStatus: string;

  merchantId: string;
  saleChannelId: string;

  payment: {
    total: number;
    paid: number;
    currency: string;
    isFullyPaid: boolean;
    paidAt: string;             // ISO timestamp

    financeWalletId?: string;   // Optional: target wallet for finance recording
  };
  items: Array<{
    id: string;
    itemType: string;
    itemId: string;
    quantity: number;
    mode: string;
  }>;

  createdBy: string;
  modifiedBy: string;
};
TrườngKiểuMô tả
saleOrderIdstringSnowflake ID của đơn hàng
saleOrderNumberstringSố đơn hàng dễ đọc
saleOrderStatusstringTrạng thái đơn hàng hiện tại sau thanh toán
merchantIdstringID Merchant (cần cho thao tác finance và inventory)
saleChannelIdstringKênh bán hàng nơi đơn hàng được đặt
payment.totalnumberTổng số tiền đơn hàng
payment.paidnumberSố tiền đã thanh toán trong lần thanh toán này
payment.currencystringMã loại tiền thanh toán
payment.isFullyPaidbooleanĐơn hàng đã được thanh toán đầy đủ hay chưa
payment.paidAtstringDấu thời gian ISO của hoàn tất thanh toán
payment.financeWalletIdstring?Ví tài chính đích cho ghi nhận thu nhập
itemsArrayCác mục đơn hàng với thông tin số lượng và loại
createdBystringNgười dùng đã tạo đơn hàng
modifiedBystringNgười dùng đã sửa đổi đơn hàng lần cuối

Luồng Thanh toán

Cách sử dụng

typescript
import { PaymentEventChannels } from '@nx/core';
import type { TPaymentSuccessEvent } from '@nx/core';

// Publisher (Sale Service)
await eventBus.publish(PaymentEventChannels.PAYMENT_SUCCESS, {
  saleOrderId: order.id,
  saleOrderNumber: order.number,
  saleOrderStatus: order.status,
  merchantId: order.merchantId,
  saleChannelId: order.saleChannelId,
  payment: {
    total: order.total,
    paid: attemptAmount,
    currency: 'VND',
    isFullyPaid: true,
    paidAt: new Date().toISOString(),
  },
  items: order.items.map(item => ({
    id: item.id,
    itemType: item.itemType,
    itemId: item.itemId,
    quantity: item.quantity,
    mode: item.mode,
  })),
  createdBy: order.createdBy,
  modifiedBy: order.modifiedBy,
} satisfies TPaymentSuccessEvent);

// Consumer (Finance Service)
eventBus.subscribe(PaymentEventChannels.PAYMENT_SUCCESS, async (data: TPaymentSuccessEvent) => {
  await financeWorkerService.handlePaymentSuccess(data);
});

3. UserOnboardingEventChannels

Nguồn: packages/core/src/common/events/user-onboarding-events.ts

Kênh

TênGiá trịMô tả
SELLER_REGISTERED'user.seller.registered'Người bán hoàn tất đăng ký
COMMERCE_INITIALIZED'commerce.initialized'Các thực thể Commerce (Organizer, Merchant, SaleChannel) được tạo

Payload: TSellerRegisteredEvent

typescript
export type TSellerRegisteredEvent = {
  userId: string;
  organizer: {
    name: { en: string; vi: string };
    slug: string;
  };
  merchant: {
    name: { en: string; vi: string };
    slug: string;
    currency: string;
    type: string;
  };
  saleChannel: {
    name: { en: string; vi: string };
    slug: string;
  };
};
TrườngKiểuMô tả
userIdstringID của người dùng mới đăng ký
organizer.name{ en, vi }Tên hiển thị organizer (song ngữ)
organizer.slugstringĐịnh danh organizer thân thiện URL
merchant.name{ en, vi }Tên hiển thị merchant (song ngữ)
merchant.slugstringĐịnh danh merchant thân thiện URL
merchant.currencystringMã loại tiền mặc định (ví dụ: VND)
merchant.typestringLoại merchant (xem hằng số MerchantTypes)
saleChannel.name{ en, vi }Tên hiển thị kênh bán hàng (song ngữ)
saleChannel.slugstringĐịnh danh kênh bán hàng thân thiện URL
  • Xuất bản bởi: Dịch vụ Identity (sau khi người dùng đăng ký)
  • Tiêu thụ bởi: Dịch vụ Commerce (tạo các thực thể Organizer, Merchant, SaleChannel)

Payload: TCommerceInitializedEvent

typescript
export type TCommerceInitializedEvent = {
  userId: string;
  organizerId: string;
  merchantId: string;
  saleChannelId: string;
};
TrườngKiểuMô tả
userIdstringID của người dùng đã kích hoạt quá trình onboarding
organizerIdstringID của Organizer mới tạo
merchantIdstringID của Merchant mới tạo
saleChannelIdstringID của SaleChannel mới tạo
  • Xuất bản bởi: Dịch vụ Commerce (sau khi tạo các thực thể)
  • Tiêu thụ bởi: Dịch vụ Finance (tạo ví Cash mặc định), Dịch vụ Inventory (khởi tạo theo dõi tồn kho)

Luồng Onboarding

Cách sử dụng

typescript
import { UserOnboardingEventChannels } from '@nx/core';
import type { TSellerRegisteredEvent, TCommerceInitializedEvent } from '@nx/core';

// Publisher (Identity Service)
await eventBus.publish(UserOnboardingEventChannels.SELLER_REGISTERED, {
  userId: newUser.id,
  organizer: { name: { en: 'My Store', vi: 'Cua Hang' }, slug: 'my-store' },
  merchant: { name: { en: 'My Store', vi: 'Cua Hang' }, slug: 'my-store', currency: 'VND', type: '000_DEFAULT' },
  saleChannel: { name: { en: 'Default', vi: 'Mac dinh' }, slug: 'default' },
} satisfies TSellerRegisteredEvent);

// Consumer (Commerce Service)
eventBus.subscribe(
  UserOnboardingEventChannels.SELLER_REGISTERED,
  async (data: TSellerRegisteredEvent) => {
    await commerceOnboardingService.initializeForSeller(data);
  },
);

// Publisher (Commerce Service)
await eventBus.publish(UserOnboardingEventChannels.COMMERCE_INITIALIZED, {
  userId,
  organizerId: organizer.id,
  merchantId: merchant.id,
  saleChannelId: saleChannel.id,
} satisfies TCommerceInitializedEvent);

// Consumer (Finance Service)
eventBus.subscribe(
  UserOnboardingEventChannels.COMMERCE_INITIALIZED,
  async (data: TCommerceInitializedEvent) => {
    await financeWorkerService.handleCommerceInitialized(data);
  },
);

4. WebSocketRooms

Nguồn: packages/core/src/common/events/websocket-events.ts

Lớp tiện ích để xây dựng định danh phòng WebSocket chuẩn hóa được dịch vụ Signal sử dụng.

Hằng số

TênGiá trịMô tả
ROOM_WR_PREFIX'wr'Tiền tố cho tất cả định danh phòng
ROOM_OBSERVATION_PREFIX'observation'Tiền tố phạm vi mặc định cho phòng quan sát

Định dạng Phòng

wr:{prefix}/{path1}/{path2}/...

Tiền tố mặc định là observation. Có thể cung cấp tiền tố tùy chỉnh cho các phòng không phải quan sát.

Phương thức: build()

typescript
static build(opts: { prefix?: string; paths: Array<string | number> }): string
Tham sốKiểuMặc địnhMô tả
prefixstring?'observation'Tiền tố phạm vi phòng
pathsArray<string | number>--Các phân đoạn đường dẫn theo thứ tự được nối sau tiền tố

Trả về: Chuỗi phòng đã định dạng: wr:{prefix}/{paths joined by /}

Ném lỗi: HttpError (400) nếu không có phân đoạn đường dẫn hợp lệ nào được cung cấp.

Các giá trị falsy trong mảng paths được tự động lọc bỏ.

Ví dụ

typescript
import { WebSocketRooms } from '@nx/core';

// Default prefix (observation)
WebSocketRooms.build({ paths: ['merchants', 'abc-123'] });
// => 'wr:observation/merchants/abc-123'

WebSocketRooms.build({ paths: ['merchants', 'abc-123', 'sale-orders'] });
// => 'wr:observation/merchants/abc-123/sale-orders'

// Custom prefix
WebSocketRooms.build({ prefix: 'control', paths: ['sessions', 'sess-456'] });
// => 'wr:control/sessions/sess-456'

// Falsy values are filtered
WebSocketRooms.build({ paths: ['merchants', '', 'orders'] });
// => 'wr:observation/merchants/orders'

5. WebSocketTopics

Nguồn: packages/core/src/common/events/websocket-events.ts

Lớp tiện ích để xây dựng định danh chủ đề WebSocket chuẩn hóa. Chủ đề phân loại tin nhắn WebSocket để client có thể đăng ký theo dõi luồng sự kiện cụ thể.

Hằng số

TênGiá trịMô tả
TOPIC_WS_PREFIX'ws'Tiền tố cho tất cả định danh chủ đề

Định dạng Chủ đề

ws:{path1}.{path2}.{path3}

Phương thức: build()

typescript
static build(opts: { paths: Array<string | number> }): string
Tham sốKiểuMô tả
pathsArray<string | number>Các phân đoạn đường dẫn theo thứ tự được nối bằng . sau tiền tố ws:

Trả về: Chuỗi chủ đề đã định dạng: ws:{paths joined by .}

Ném lỗi: HttpError (400) nếu không có phân đoạn đường dẫn hợp lệ nào được cung cấp.

Các giá trị falsy trong mảng paths được tự động lọc bỏ.

Ví dụ

typescript
import { WebSocketTopics } from '@nx/core';

WebSocketTopics.build({ paths: ['observation', 'sale', 'sale-order'] });
// => 'ws:observation.sale.sale-order'

WebSocketTopics.build({ paths: ['observation', 'payment', 'transaction'] });
// => 'ws:observation.payment.transaction'

WebSocketTopics.build({ paths: ['notification', 'merchant', 'abc-123'] });
// => 'ws:notification.merchant.abc-123'

6. Kiến trúc Tổng hợp

Luồng Sự kiện Liên Dịch vụ

Sự kiện so với Hàng đợi

Sự kiện (Redis pub/sub) và hàng đợi (BullMQ) phục vụ các mục đích khác nhau trong BANA:

Khía cạnhSự kiện (pub/sub)Hàng đợi (BullMQ)
Phân phốiPhát quảng bá fire-and-forgetPhân phối đảm bảo với thử lại
Nhà tiêu thụNhiều subscriberMột consumer mỗi phân vùng
Bền vữngKhông bền vữngĐược lưu trong Redis
Trường hợp sử dụngThông báo, cập nhật thời gian thựcXử lý công việc quan trọng
Định nghĩa trongcommon/events/common/queues/

TIP

Sự kiện và hàng đợi thường hoạt động cùng nhau. Một trình xử lý sự kiện có thể đưa công việc vào hàng đợi để xử lý đáng tin cậy. Ví dụ, sự kiện PAYMENT_SUCCESS được nhận bởi Dịch vụ Sale, sau đó đưa công việc vào hàng đợi qua SaleQueueDefinitions cho cả xử lý Finance và Inventory.


Thực hành Tốt nhất

1. Luôn Sử dụng Hằng số Kênh

typescript
// Good -- type-safe and refactorable
eventBus.subscribe(PaymentEventChannels.PAYMENT_SUCCESS, handler);

// Bad -- magic string
eventBus.subscribe('payment.order.success', handler);

2. Khai báo Kiểu cho Payload Sự kiện

typescript
// Good -- typed payload
eventBus.subscribe(
  PaymentEventChannels.PAYMENT_SUCCESS,
  async (data: TPaymentSuccessEvent) => {
    // TypeScript knows the shape of data
    const { merchantId, payment } = data;
  },
);

// Bad -- untyped
eventBus.subscribe(PaymentEventChannels.PAYMENT_SUCCESS, async (data: any) => { });

3. Sử dụng Các Trợ giúp WebSocket cho Đặt tên Nhất quán

typescript
// Good -- standardized room/topic naming
const room = WebSocketRooms.build({ paths: ['merchants', merchantId, 'sale-orders'] });
const topic = WebSocketTopics.build({ paths: ['observation', 'sale', 'sale-order'] });

// Bad -- manual string construction
const room = `wr:observation/merchants/${merchantId}/sale-orders`;
const topic = `ws:observation.sale.sale-order`;

Tài liệu Liên quan

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.