Muammo aniqlanishi
Lakey loyihasida ichki xabarlashuv doirasi bo'lgan Prologue'ning Vello modulidan foydalangan holda tashqi hodisalarni saqlaymiz va ularni LLM kontekstida foydalanish uchun boshqaramiz.
Prologue dastlab taqdim etilgan hodisalarni konvertatsiya qiluvchi 'TypeAwareEventConverter' xabarlarni qabul qilganda sarlavhaning 'payloadClass' qiymatini o'qiydi va sinf nomining ishga tushirilayotgan xizmatning klass yo'liga mavjudligini tekshiradi, so‘ngra deserializatsiya qiladi.
Bu usul bir xil xizmat ichida yoki tashqi xizmatning hodisalar moduli bog'langan holda hodisalarni almashish paytida muammo tug'dirmaydi. Chunki hali Hodisa sinfi xizmatning klass yo'ida mavjud. Ammo aniqlanmagan va chiqarilgan hodisalar uchun vaziyat boshqacha. Aniqlanmagan hodisaning sinfi Lakey'ning klass yo'lida yo'q, shuning uchun istisno kelib chiqadi va oxir-oqibat qilinishi kerak bo'lgan hodisa normal ravishda ishlov berilmay yo'qoladi.
Tashqi xizmatning hodisa sxemasi har gal o'zgartirilganda Lakey ichki mantiqi ham o'zgaradi, shuning uchun xizmatlar o'rtasida kuchli bog'lanish paydo bo'ladi. Tashqi xizmatning hodisalar moduliga bog'lanmasdan hodisalarni saqlash usulini o'ylab turgan edik va bu muammoni hal qilish uchun 'LakeyEventConverter'ni to'g'ridan-to'g'ri ishlab chiqdik.
Mavjud EventConverter'ning ishlash usuli
1. Xabar yuklamasini belgilangan kodlash bilan satrga aylantiradi.
2. Sarlavhaning 'payloadClass' qiymati bo'sh bo'lsa, 'Map' turiga deserializatsiya qilib qaytaradi.
3. 'payloadClass' qiymati mavjud bo'lsa, 'Class.forName()' orqali sinfni yuklab oladi va ushbu turga deserializatsiya qiladi.
4. Sinfni yuklab olishda muvaffaqiyatsizlikka uchraganda, oxir-oqibat 'EventConversionFailed' istisnosi yuzaga keladi.
Hal etish usuli: LakeyEventConverter
4-raqam muammosini hal qilish uchun 'TypeAwareEventConverter'ni meros qilib olib, 'convert()' metodini o'zgartirdim. Tur o'zgarishida muvaffaqiyatsizlikka uchragan hodisalarni tashlamasdan, talqin qilingan asosiy ko'rinishda saqlash usuli.
public class LakeyEventConverter extends TypeAwareEventConverter {
private final String encoding;
public LakeyEventConverter(
@Value("${vizend.prologue.vello.encoding:UTF-8}") String encoding) {
super(encoding);
this.encoding = encoding;
}
@Override
public Object convert(VelloMessage message) {
try {
Object object = super.convert(message);
if (object instanceof LakeyOutgoingDomainEvent out) {
return out.toIncomingEvent();
}
return object;
} catch (EventConvertException e) {
String payloadJson = new String(message.getPayload(), Charset.forName(encoding));
Map<String, Object> payload = JsonUtil.fromJson(payloadJson, Map.class);
String fqcn = message.getPayloadClass();
String simpleClassName = fqcn.substring(fqcn.lastIndexOf('.') + 1);
return UnreferencedEvent.builder()
.eventType(simpleClassName)
.headers(message.getHeaders())
.payload(payload)
.messageId(message.getId())
.subject(message.getSubject())
.receivedAt(LocalDateTime.now())
.build();
}
}
}
'LakeyEventConverter'ning ishlash oqimi ikki holatga bo'linadi.
1. Oddiy konvertatsiya muvaffaqqiyati holatida
Ota konvertor ('TypeAwareEventConverter') konvertatsiyasi muvaffaqiyat qozongan bo'lsa, ammo Lakey o'ziga xos hodisalari yoki bog'liq hodisa klasslari bo'lsa, konvertatsiyalangan ob'ektni aynan qaytaradi. Bu holatda qo'shimcha qayta ishlash talab etilmaydi.
2. Tashqi hodisalar kabi tur o‘zgartirishda muvaffaqiyatsizlikka uchragan hollarda
Ayrim holatlarda ota-ona klassi 'EventConvertException' ni tashlaydi, bu esa ushlab qolib alohida qayta ishlanadi. Bu holatda yukni 'Map<String, Object>' ga qayta ketlamasdan o'tkazib, sarlavhada joylashgan FQCN dan oddiy klass nomini chiqarib olib 'eventType' sifatida foydalanamiz. Keyinchalik bu asosida 'UnreferencedEvent' obyektini yaratib qaytaramiz.
Bu yerda oddiygina yukni qoldirish bilan cheklanmaymiz, quyidagi ma'lumotlarni ham saqlaymiz.
* Xabar ID si, qabul qilingan mavzu (subject), xabar sarlavhalari, qabul qilish vaqti, asl yuk
UnreferencedEvent
UnreferencedEvent havola qilinadigan klassi bo‘lmagan hodisalarni saqlovchi obyekt hisoblanadi. Ismi bilan bir xil, Lakey turini bilmadi, lekin to‘plamga arziydigan hodisani ifodalaydi.
@Getter
@Builder
public class UnreferencedEvent {
private String eventType;
private Map<String, String> headers;
private Map<String, Object> payload;
private LocalDateTime receivedAt;
private String messageId;
private String subject;
}
Bu yerda muhim qiymat yukdan iborat emas. messageId takrorlashni boshqarish uchun kerak va subject hodisa qabul qilingan mavzuni aniqlashda ishlatiladi. headers va receivedAt esa kelajakda hodisaning manbai va qabul qilish vaqtini kuzatishda yordam beradi.
UnreferencedEvent dan RawEvent gacha
LakeyEventConverter UnreferencedEvent ni qaytarishi bilan, Prologue ning @EventHandler bilan belgilangan UnreferencedEventHandler bu hodisani qabul qiladi. Handler alohida ishbilarmonlik qarorini qabul qilmay, RawEventFlow.register() ga o'tkazadi.
@Component
@RequiredArgsConstructor
public class UnreferencedEventHandler {
private final RawEventFlow rawEventFlow;
@EventHandler
public void handleUnreferencedEvent(UnreferencedEvent unreferencedEvent) {
if (unreferencedEvent == null) {
log.warn("UnreferencedEvent is null");
return;
}
rawEventFlow.register(unreferencedEvent);
}
}
RawEventFlow.register() da uchta narsani tekshiradi.
public void register(UnreferencedEvent unreferencedEvent) {
if (!rawEventAction.isTargetEvent(unreferencedEvent)) {
return;
}
RawEventCdo rawEventCdo = rawEventAction.convertToRawEventCdo(unreferencedEvent);
if (rawEventLogic.existsByEventId(rawEventCdo.getEventId())) {
return;
}
String rawEventId = rawEventLogic.registerRawEvent(rawEventCdo);
eventProxy.produceEvent(RawEventProjectionRequestedEvent.newInstance(rawEventId));
}
1. Maqsad mavzumi ekanligini tekshirish
Tashqi tomondan kelayotgan barcha hodisalarni saqlamaymiz. LakeyConfigProperties da belgilangan targetTopics ga kiritilgan mavzularni qayta ishlaymiz. To'plamga olinmaydigan hodisalarni dastlabki bosqichda filtrlash orqali keraksiz saqlash va keyingi qayta ishlashni kamaytiramiz.
2. Takroriy hodisa ekanligini tekshirish
messageId ni eventId sifatida ishlatib, allaqachon saqlangan hodisani tekshiramiz. Bir xil hodisa qaytadan kelsa ham, bir martagina saqlanadi.
3. RawEvent sifatida saqlash
Agar mavzu to'g'ri bo'lsa va takrorlanmasa, UnreferencedEvent'ni RawEventCdo'ga aylantirib saqlaymiz. Saqlangandan so'ng RawEventProjectionRequestedEvent'ni chiqaramiz. Haqiqiy ma'lumotlarni oldindan ishlash esa ushbu voqeani qayta ishlovchi handler bosqichida amalga oshiriladi.
Xulosa
Ushbu tuzilmalarni yaratish sababi aniq. Voqea turini oldindan bilmagan taqdirda ham, kelayotgan voqeani yo'qotmaslikdir.
Avvalgi 'TypeAwareEventConverter' sinf yo'qligini aniqlaganida, istisno chiqarar edi va ishlov berishni to'xtatardi. Biroq 'LakeyEventConverter' esa istisno qilingan voqealarni 'UnreferencedEvent' deb nomlangan Umumiy Entitiga saqlaydi. Natijada Lakey, tashqi xizmatlarning voqealar sinflariga to'g'dan-to'g'ri bog'lanmasdan voqealarni to'plashi mumkin bo'ldi.
Agar voqealarni qayta ishlov berishda xizmatlar orasidagi bog'liqlik darajasini zaiflashtirish zarur bo'lsa, iltimos hisobga oling.
Ted