-
Notifications
You must be signed in to change notification settings - Fork 1
EventBus
Here I present two alternatives for an Event Bus system. The first called "Hijacking the Logger" is easier to integrate into the existing architecture but it is not a architecturally clean solution. The second alternative called "More Indirection" is cleaner but more work.
No matter the alternative all event consuming objects should be able to register callbacks for specific event types. These callbacks should then be called when such an event is logged. The registration returns a handle that can be used for unregistration later.
sequenceDiagram
activate EventConsumer
EventConsumer->>+EventBus: register_callback(callback, event_type)
EventBus-->>-EventConsumer: handle
deactivate EventConsumer
sequenceDiagram
activate EventConsumer
EventConsumer->>+EventBus: unregister_callback(handle)
EventBus-->>-EventConsumer:
deactivate EventConsumer
This solution requires minimal change in already existing code and is therefore very simple to integrate into the current architecture. The idea is to override the save
method of LogEntry
and to notify the EventBus
there. The EventBus
takes the LogEntry
as argument and then looks for all registered callbacks associated with the entrys type and calls them passing the entry as argument again.
The downside is that it violates the single responsibility principle. The Logger
is not responsible for managing an event system.
classDiagram
class BaseModel {
<<abstract>>
}
class LogEntry {
<<abstract>>
+save()
}
class Logger {
+log(*args, **kwargs)
}
class Component {
}
class EventBus {
<<singleton>>
+callbacks: list[tuple[callable[[LogEntry], None], Type[LogEntry]]]
+register_callback(callback: callable[[LogEntry], None], entry_type: Type[LogEntry]): UUID
+unregister_callback(handle: UUID)
+notify(entry: LogEntry)
}
class EventConsumer {
+event_handle: UUID
+on_event(entry: LogEntry)
}
Logger <-- Component: logs event
LogEntry <-- Logger: creates
BaseModel <|-- LogEntry
EventBus <-- LogEntry: notifies
EventBus <-- EventConsumer: (un)registers callbacks
EventConsumer <-- EventBus: notifies
sequenceDiagram
activate Component
Component->>+Logger: log(*args, **kwargs)
Logger->>+LogEntry: create(*args, **kwargs)
LogEntry->>+LogEntry: save()
LogEntry->>+EventBus: notify(self)
EventBus->>+EventConsumer: on_event(entry)
EventConsumer-->>-EventBus:
EventBus-->>-LogEntry:
LogEntry-->>-Logger:
deactivate LogEntry
Logger-->>-Component:
deactivate Component
This alternative abstracts the logger interface into ILogger
and enables the usage of arbitrary many objects using this interface. Two of these objects are the already existing logger and also the EventBus
. Objects inheriting from Component
now communicate with the EventDistributor
instead of the Logger
using the same interface. The EventDistributor
then forwards the call to Logger
and EventBus
. The EventBus
then uses an Event
having an EventType
instead of the LogEntry
. The Event
holds all the data the LogEntry
would hold. The rest of the mechanism works same as the first alternative presented above.
The disadvantage is that it is more work and one has to change more existing code. Especially the code concerning the interaction with the logger interface.
classDiagram
class ILogger {
<<abstract>>
+log(*args, **kwargs)
}
class Logger {
}
class EventBus {
<<singleton>>
+callbacks: list[tuple[callable[[Event], None], EventType]]
+register_callback(callback: callable[[Event], None], event_type: EventType): UUID
+unregister_callback(handle: UUID)
}
class EventType {
<<enum>>
}
class Event {
}
class EventConsumer {
+event_handle: UUID
+on_event(event: Event)
}
class EventDistributor {
}
class Component {
}
ILogger <|-- Logger
ILogger <|-- EventBus
ILogger <|-- EventDistributor
EventDistributor "" o-- "n" ILogger
Component "" *-- "1" EventDistributor
Event "" *-- "1" EventType
Event <-- EventBus: creates
EventBus <-- EventConsumer: (un)registers callbacks
EventConsumer <-- EventBus: notifies
sequenceDiagram
activate Component
Component->>+EventDistributor: log(*args, **kwargs)
EventDistributor->>+Logger: log(*args, **kwargs)
Logger-->>-EventDistributor:
EventDistributor->>+EventBus: log(*args, **kwargs)
EventBus->>+Event: __init__(*args, **kwargs)
Event-->>-EventBus: self
EventBus->>+EventConsumer: on_event(event)
EventConsumer-->>-EventBus:
EventBus-->>-EventDistributor:
EventDistributor-->>-Component:
deactivate Component
When implementing the EventDistributor
this maybe helpful.