The same goes for Schemas and Contexts. A Context can provide access to several schemas, or no schemas. All a context is a module, after all. Unlike an OOP system, you’re not inheriting any behaviour or methods or parameters. If you’re from an OOP background, it’s similar to the Facade pattern - you’re creating an interface to access functions.
Using multiple contexts is expected, in the same way that you might use both Keyword Lists, Maps, and the Enum module. Instead of directly dealing with the internals of a Map or Keyword list, you make changes through the functions in the module. Functions that apply to both are in Enum. The code inside Enum needs to know how to handle both, but you don’t need to look under the hood.
Contexts are similar. Instead of dealing directly with the tables or sql, you access them through the appropriate module (context).
Your latest post has a very fine grained breakdown. You don’t have to break things down to that level. If the underlying functionality is very disparate, then that distance could be good. If they’re very tightly coupled, then it might be good to keep them under the same context. So, if set_email and set_username always happen together then it might be simplest to put them in the same context, and possibly to collapse them into one method (update_profile).
I can be quite perfectionistic, so I understand the desire to do things “right” the first time. But it’s far more important to be able to be -flexible-, to be able to make changes easily, than to be “right”.
One of the reasons to organize your code into contexts is that it makes it easy to change later. If today you put all the suggested calls in one Context: User, or Account, and then in a month you decide that you want to extract notifications, your process is this.
1.) Create a new Notifications module
2.) Cut and paste the code from the old context to the new context
3.) Create new notification tests files
4.) Cut and paste the notification tests into the new files
5.) Search and replace the context names in your code and text files.
6.) Run your tests.
Compared to trying to find every use of a struct or field or reviewing every sql statement, it’s a much simpler change process. Keeping change simple is one of the most important reasons to organize your code into modules. Your contexts should be organized so that if you make changes to the internals of a system, it doesn’t affect the code using the system. So for example, if you change how ‘mark_as_read’ works under the hood, it doesn’t change any of the code that calls ‘mark_as_read’.
Even though it’s geared toward object orientated code, you may want to read up on Code Smells. I find at least half of them still apply to functional code, and understanding why they can indicate a problem, and if refactoring is needed, has really improved the maintainability of the code I write.