Skip to content

Commit 3b632e8

Browse files
committed
docs: update developer guide with instructions for adding new messages and using the event system
1 parent c3201d9 commit 3b632e8

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

docs/_prebuilt/developerguide.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,3 +813,96 @@ The new aggregator must inherit from the **Aggregator** class. You can use **Fed
813813
# self.print_model_size(accum)
814814
return accum
815815
```
816+
817+
### **Adding new messages**
818+
To add a new message to the application, follow these steps:
819+
820+
**1.** Create your message in the **nebula.proto** file inside the **nebula/core/network/pb**. Follow the structure used by other messages. For example:
821+
822+
??? quote "New message example"
823+
```python linenums="56"
824+
message FederationMessage {
825+
enum Action {
826+
FEDERATION_START = 0;
827+
REPUTATION = 1;
828+
FEDERATION_MODELS_INCLUDED = 2;
829+
FEDERATION_READY = 3;
830+
}
831+
Action action = 1;
832+
repeated string arguments = 2;
833+
int32 round = 3;
834+
}
835+
```
836+
837+
Even if your message only has one “type,” define it in this manner so that subsequent steps stay consistent.
838+
839+
**2.** Once the message is created in **nebula.proto**, run the protobuf protocol as indicated in the file to generate the **nebula_2pb.py** file.
840+
841+
**3.** In the file **/core/network/actions.py**, add the class that represents your message, following the structure of the others, and include it in the dictionary ACTION_CLASSES as a key-value pair (message_name, created_class). The message_name should be in lowercase.
842+
843+
**4.** In the file **/core/network/messages.py**, add your message template in the dictionary inside the _define_message_templates() function.
844+
Follow the existing structure, indicating in parameters the different parameters of your message.
845+
In defaults, you can provide default values for your parameters to simplify message creation.
846+
It is important to use the exact same parameter names you defined in nebula.proto.
847+
848+
**5.** In the file **core/engine.py**, define your callback that will be executed when the message is received.
849+
Use the same naming convention used in the other callbacks:
850+
851+
??? quote "Naming convention example"
852+
```python linenums="1"
853+
<message_name>_<message_action>_callback(self, source, message)
854+
```
855+
856+
**If you have followed all the previous steps, at runtime, the events and callbacks associated with your message will automatically be registered.** The system will run the callback you defined whenever the new message is received. There is nothing else to do—your message is now implemented in the messaging protocol!
857+
858+
### **Event System**
859+
An **Event-Driven Architecture (EDA)** is a design model in which system components communicate through events instead of direct calls. In this architecture, producers generate events that are consumed by other services in an asynchronous manner, allowing a high degree of decoupling, scalability, and flexibility.
860+
861+
#### **Events**
862+
There are currently three different event types defined in the file **/core/nebulaevents.py**:
863+
864+
- **NodeEvent**: Events associated with the training-aggregation process that comprises FL.
865+
- **MessageEvent**: Events associated with Nebula’s communication protocol.
866+
- **AddonEvent**: Events associated with additional components that can be added to scenarios.
867+
868+
Each event type has a different internal structure and is managed independently by the event handler, including maintaining a separate queue for each event type. Moreover, for **NodeEvent**, there is an option to define whether the subscribed listeners should be run concurrently or not.
869+
870+
Meanwhile, we have the **EventManager**, which controls how events are subscribed to and published. We will first look at how to use this functionality with Nebula’s native events and then go through the steps for creating new events.
871+
872+
??? quote "Import"
873+
```python linenums="1"
874+
from nebula.core.eventmanager import EventManager
875+
```
876+
877+
Once it is imported, we can subscribe to and publish events in our **.py** files:
878+
879+
??? quote "Subscribing to an event"
880+
```python linenums="1"
881+
await EventManager.get_instance().subscribe(EventType, callback_used_on_trigger)
882+
```
883+
884+
Note that **EventType** is the class that represents the event (not a specific instance) and **callback_used_on_trigger** is a coroutine (defined with **async**). To specify **EventType**, you need to import it from **nebulaevents.py**
885+
886+
??? quote "Import"
887+
```python linenums="1"
888+
from nebula.core.nebulaevents import EventType
889+
```
890+
891+
**Publishing an event:**
892+
893+
1. **Import the event type you want to publish.**
894+
895+
2. **Create an instance of that event, adhering to its definition.**
896+
897+
3. **Use the corresponding publish function for that event type.**
898+
899+
??? quote "Event example"
900+
```python linenums="1"
901+
current_time = time.time()
902+
rse = RoundStartEvent(self.round, current_time)
903+
await EventManager.get_instance().publish_node_event(rse)
904+
```
905+
906+
When the event is published, all subscribed listeners for that event type will be triggered. As mentioned, there are three different **publish** functions, each tied to a specific type of event.
907+
908+
Finally, to **create a new event**, go to the file **/core/nebulaevents.py**. Depending on the type of event you wish to implement, create a class that extends one of the three native event types. After doing this, the usage of your new event is transparent to the rest of the system, and you can use the functions described above without any issues.

0 commit comments

Comments
 (0)