Golang script to send and receive events with a tiny simple API
github.com
github.com
It's great that there's tests, but all of the tests follow the same pattern: On and then Trigger. It's the reverse order that's busted.
In addition to this, there is a lot of `reflect` usage. This in and of itself isn't really a problem, but this will really slow down the usage and I imagine there are relatively high performance use cases for the observer pattern.
Not trying to discourage you or anything! It is great that you built something and made it available for the world. Just trying to give some hints from someone that has worked with Go a lot (parent has as well!)
That said, built in race condition checking is a great feature and tests should always be run with it. Unfortunately this library would not gain any benefit from it without tests that actually execute code concurrently.
The Observer pattern which is shown here can be especially tricky. Once you register a callback for an event you don't necessarily know from which thread the callback will be called. Some libraries will fire callbacks from a worker thread, other might marshal them to some kind of main thread, etc. If callbacks are fired from multiple threads then event ordering won't be guaranteed and you probably need locking in the receiver (be aware of deadlocks). The next tricky thing is cancellation/unsubscription. In most multithreaded implementations you might still get notifications shortly after unsubscribe during special timing conditions if the thread that fires the notification has not yet processed the unsubscription (or operates on a copied and outdated subscriber list).
A singlethreaded runtime (like node) makes reasoning around this much easier. And you need a lot of less extra safeguarding code to care for these race conditions.
I don't know how you use the observable patterns but normally you first set you listeners and then you trigger the events, the other way around makes no sense
Perhaps I might have misunderstood what you're saying, so bear with me:
What about the case where you have two, completely independent processes: a event data Publisher, and a Subscriber proxy that proxies Subscription and Event transmissions between your Go program and the rest of the world via -I don't know- Websockets or plain TCP/IP or something.
When your Go program starts, you're likely to want start up your publisher immediately. As your program generates reportable events, it'll call Trigger even if noone has subscribed to those events, right? I mean, the whole point of a pub/sub mechanism is for the event producer(s) to not care at all about any of the event consumers... so it sounds to me like the case where Trigger will be called before anyone has called On is common. [0]
Anyway. A general thought about safe software design:
It's still important to ensure that unusual or incorrect usage of the public parts of an API fail safe whenever reasonably possible. Even the most conscientious programmers will have a brain fart and fail to remember the order in which API functions should be called. If you can detect improper usage, ideally you should signal an error (whether by return value or exception) and leave everything behind the API untouched. Trashing the list of subscribers because someone called On or Off immediately after calling Trigger is surprising and should be something that can't happen.
Anyway, good luck with your project. Think of fun and exciting ways that boneheads like me could break it, and make it so that we can't break it like that! :)
[0] Back when I used pub/sub mechanisms to attach networked controls to remote computerized systems, it was absolutely a requirement for each of those systems to run the same regardless of whether there were 0, 1, or 100 sets of controls hooked to them.
The observer pattern decouples notifiers from observers. Observers may come and go at any time, and whenever a notification is posted, all the currently registered observers see it. If you demand that all observers are added before the first notification, then your notifier has to know which (or how many) observers are going to register! You've coupled your notifiers and observers, which is exactly what we're trying to avoid.
Here's a concrete example: the event is the user pressing a key. Keypresses often create a new window or change the focus or something, so you can have new observers added in response to a keypress. So a Trigger() leads to an On(). It does make sense!
But you can't design an API like that in Go, it just wouldn't work.
I mean, this would be useful anywhere you want to send events/messages to some number of interested "parties", when you don't necessarily know what or who those parties are going to be ahead of time.
I'm not snarking when I ask this, it's a genuine question: Have you ever done that sort of thing before?
[0] https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_patt...
Really convenient when you've got various chains of transformations that need to be combined/reordered/dropped while processing streams of data.
> Have you ever done that sort of thing before? yes I did that's one of the reasons I had to code go-observable
I never said that you were. :)
> yes I did that's one of the reasons I had to code go-observable
I don't mean to sound mean or anything like this, but you do know that this question was directed directly at fiatjaf, and noone else, right? :) [0] I mean, I guess I appreciate the input, but I asked the question because it seemed obvious to me -because I spent several years working with software that made use of Pub/Sub mechanisms- that go-observable was a Pub/Sub mechanism for Go. So... I was wondering if fiatjaf had ever done any such thing in the past.
Obviously, as you are publishing a pub/sub mechanism in a given language it's either because you had need to do it in the past (or you were just doing it for funzies) and are sharing the fruits of your efforts. :)
[0] Or did my comment appear to be a direct descendent of your HN post, rather than a descendent of fiatjaf's question? I've heard that particular bug mentioned once or twice in the past.
I mean, the way pretty much everyone does GUIs (with the central message pump that pulls external events off of a queue and changes UI state based on those messages) is an entirely reasonable way to think of all systems that use message passing to communicate -e.g. Elixir or Erlang- between loosely coupled parts of the system.
One common use case is in workflow systems. For example, you might "wait" for a bunch of files to exist before kicking off a job. This could be implemented as a bunch of "subjects" notifying when the files exist, and then the "observer" taking action when all of its subjects notify.
It is also used in UI frameworks to update visual representation when underlying data changes.