Savory / Danet

The most mature backend framework for Deno. Create awesome HTTP and WebSocket server as well as KVQueue workers !
https://danet.land
Other
269 stars 18 forks source link

Suggestion: Events for Danet #75

Closed marco-souza closed 7 months ago

marco-souza commented 8 months ago

Is your feature request related to a problem? Please describe.

As a Nest.JS developer, I'd like the possibility of making an event-driven system using the framework decorators and module system.

References:

Describe the solution you'd like I'm not sure about the internals, but I imagine something like this:

this.emitter.connect({
  // Add queue configs when the app is initializing (SQS, Kafka, etc)
})

than, I'd like to bind class methods to events:

// initialization
class MyListeners {

  @OnEvent('new-user')
  handleWelcomeNewUser(user: User) {
    // Do some side effect
  }

}

and also should be able to emit these events, something like:

// initialization
class MyListeners {

  createUser(user: UserDto) {
    // create user
    this.emitter.send('new-user', user)
  }

}

Describe alternatives you've considered

At the moment, the alternative I have considered is to make an EventEmitter module, which manages queues and subscriptions using Deno.Kv queues, to keep it as native as possible.

This is not ideal, but it can work if we set the (un)subscription in onAppBootstrap and onAppClose respectively.

@Injectable()
export class MyListeners implements OnAppBootstrap, OnAppClose {
  listeners: string[] = []

  constructor(@Inject(EventEmitter) private eventEmitter: EventEmitter) {}

  eventHandler() {
    // do something
  }

  async onAppBootstrap() {
    const eventId = await this.eventEmitter.subscribe('event', this.eventHandler);
    this.listeners.push(eventId)
  }

  async onAppClose() {
    await Promise.all(
      this.listeners.map(
        this.eventEmitter.unsubscribe,
      )
    );
  }

}
Sorikairox commented 8 months ago

Hey @marco-souza, thank you for this Feature Request.

I like it, is it something your would be willing to implement natively in Danet, in a separate module, similar to how the Swagger module work ?

If not, I might have some time to draft something this weekend or next week's !

marco-souza commented 8 months ago

@Sorikairox Thank you for the availability.

I was imagining it as a separate module, as you mentioned. I'm interested in working on it, but unfortunately, I won't have the time until the prod release in my job, planned for 1st week of next month.

If you have the time to draft something it would be great! I can help to discuss the proposal, review the code changes, and test it.

Sorikairox commented 8 months ago

Hey @marco-souza !

I took a look at Nest implementation and your feature request.

There are 2 distinct possible features:

Local event based

Similar to: https://docs.nestjs.com/techniques/events

These are "local" events across the instance, using the EventEmitter2 library, but we now have a Web API that works well too https://developer.mozilla.org/en-US/docs/Web/API/EventTarget

Queue/PubSub

Similar to: https://docs.nestjs.com/techniques/queues

Listening and sending data to a Queue, and we can extrapolate that we can make it work with Pub/Sub too without too much trouble.

From my understanding, you would like the latter so I will start with that with Deno.Kv first (even if both are cool and needed for different use cases).

I have a somewhat clear idea of how to implement that.

marco-souza commented 7 months ago

That's great @Sorikairox, thanks!

Regarding the local events, I also find it super handy for different use cases. Today I had some time to draft an Event module simulating the EventEmitter behavior. It's super simple (and suboptimal), so I'd appreciate some feedback.

PR https://github.com/Savory/Danet/pull/76

I implemented it as an internal module of danet, as it simplifies event subscription, but I'm unsure if it's the best resolution. I'm also not well-versed in reflect-metadata API (I've heard of it, but today was the first time I used it), so forgive me if my code looks dumb πŸ˜†

I'm not sure if it helps the Queue/PubSub feature, but let me know if we can join efforts to have both features πŸ’ͺ🏼

Sorikairox commented 7 months ago

@marco-souza Thank you for the PR ! Commenting on it.

I will have time to start working on the Queue/PubSub feature this weekend !

marco-souza commented 7 months ago

Hello @Sorikairox πŸ‘‹

This weekend I was also looking at my personal list of nestjs features that I use most, the next one would be the Task Scheduling feature, for handling batch jobs, clean-up tasks, and other time-related events. It also matches well with the Queue/PubSub feature.

I imagine It would be similar to the Events implementation, and we can use deno native cron jobs with Deno.Cron module. I want to implement it later this week, but first I have a couple of questions:

  1. Should I create a new ticket for tracking this feature?
  2. This feature could be written as a danet_cron external lib, but I also like having these common features out-of-the-box in the framework itself, so the framework would have some kind of standard library. On the other hand, this will increase the framework core size, even for those who won't use any of these features. IMO, we can continue to write these as part of the main framework until it becomes a problem and then we decide how to break it down, but I want to hear your thoughts on this.
Sorikairox commented 7 months ago

Hey @marco-souza !

  1. Yes !
  2. As long as we create them as we did for EventEmitterModule, we are fine. We will be able to easily migrate them to their own module

I think that tree-shaking is enough to reduce the framework size in bundling/production anyway ? It's a problem for later !

marco-souza commented 7 months ago

Totally agree with you! ~I'll create a new task proposing~ I created #78 and I'll let you know when I have the PR ready.