> My impression is that it's difficult to learn about these topics without real hands-on experience (something that seems hard to replicate in small/solo projects).
Maybe. Unfortunately, I don't think there's a cheat code there. Often the single best thing you can do is to put yourself near people who know what you want to know.
> Would you mind expanding on that last point? I'd be curious to learn more about these knowledge gaps that you observed, as well as any recommendations for "leveling up" past them, whether it be projects that one could build to learn more, books to read, etc.
Related to my response above, as an example of something I observed, we were able to teach people who had little Elixir experience about simple GenServers pretty quickly. They learned the API fine, understood the client/server metaphor, started making contributions to the product/codebase, great, product managers are happy. The gaps became apparent when we'd hear stuff like "the thing I wrote is crashing, in the logs I'm seeing things like `exit: genserver timeout`. What does this mean? Where do I even start?"
The issue here isn't really "Elixir" per se. The issue is that Elixir was sold to them as this quick, pragmatic functional programming language (and it is), but what they got along with Elixir-the-language was a production OTP application that was actually a concurrent distributed system that they didn't realize was a concurrent distributed system and now they have to figure out how to debug that, which might have been more than they were bargaining for. Now OTP starts to dominate the conversation.
You say you have a pretty good grasp on OTP so this might not apply to you specifically, but if I could recommend a single thing to Elixir developers, it would without question be to read as much of the Erlang stdlib documentation and source code as you can. They're really, really good and highly readable. Don't be grossed out by Erlang. Read up on genserver, supervisor, sys, dbg, monitors, links. Understand what is actually happening when a genserver boots. Understand what actually happens when a genserver crashes, and how this appears to the supervisor. Understand when to use a supervisor vs. a dynamic supervisor. Understand when you might want to use the process Registry.
If I can recommend more than one thing, it would be to just build toys and provide your own hands-on experience. Elixir, being so dynamic, lends itself to discovery-through-play better than almost anything. Build a 3 node cluster on your laptop and play with rpc to distribute work. Build a little worker pool and randomly crash the tasks. Build something that dispatches work with Registry. Build a little network server that listens for new tcp connections. Kill random processes and see how things react. Use dbg or sys or observer or the other tracing stuff to see live state. Build a release and figure out how to remote shell into it. The only difference is that a real app would have a real domain, which you'd have to learn regardless. Anyway, I hope this is useful.