I’m in favor of putting it in the Django config. It’s simpler on the implementation side, more predictable, and easier to see what’s going on. For serious integrations, it’s likely you’d want a whole settings file that does just this, and gets included into the main settings file (or maybe you pull it programmatically from JSON or some such).
Regardless, one big dict of {str: List[str]} doesn’t sound too bad. If installs get to the point where it’s difficult to manage, then we can figure out alternative mechanisms to compile out a config based on some more terse configuration. But I’m not convinced we’ll really get there or that we know what the ideal ergonomics of that would actually be before folks start managing a lot of these.
Adding a few other comments below. I caveat all of the following with the acknowledgement that I am not the target audience for this. I’ve been in the room for these conversations in the past, but at the end of the day, I realize that I don’t do this part for a living. I offer the items below as suggestions and possibilities, but I rely on you folks to determine whether these use cases are at all plausible or worth the complexity.
Django Signals for broadcasts
Can we split the synchronous transforms and the broadcast notifications mechanisms completely, even if the writeup for both are part of the same proposal? General notifications can be done using Django signals–it’s well understood, and we don’t really have to provide an async option as long as we document the everywhere-in-platform pattern of “catch the signal, queue a celery task”.
Ergonomics of Writing the Hooks
If I’m writing the hook that provides openedx.lms.auth.post_login.action.v1, how is that made clear to me that this isn’t something I can casually refactor, but is in fact a big, public promise? A separate hooks.py module? A decorator along the lines of @hook('openedx.lms.auth.post_login.action.v1')
Filter-style hooks sound like middleware
Do filters have the capability of altering parameters to the function (as well as altering the results coming back)? If so, they sound a lot like middleware. I think that’s a good thing actually, but it makes me wonder if these filter functions should be classes instead, so that you could get called with some sort of initialization. Maybe that’s overkill, but it might also be a straightforward way to provide the request object context to something that wouldn’t normally use it, in case your filter needed to check some querystring params or some such.
Async
Django 3.2 is right around the corner, and async views+middleware come with that. I’m not sure when edx-platform will upgrade, but it’s at most a year away (since 2.2 LTS support ends at that time).
Integration hooks may lean towards things that are I/O-heavy and could benefit from being async. I don’t think we should implement this right away, or even really spend too much time specifying the details. But it might be good to think about how we might do this at a high level so that we don’t box ourselves in. This might be another case where having a class would give us the flexibility to do a sync and async path down the road. I expect there are patterns we might copy from middleware here as well.