Typical item ordering app - questions about GenServer, Actors, PubSub and some different approaches

Have an app I’ve been working on for a while that will be used by a small number of folks, but also serves as a good playground for learning new technologies.

I’ve been working on rewriting this app using Phoenix and have implemented pretty much all of it using LiveView, which has been great.

That said, I’ve started thinking about reworking some parts of it, and while I enjoy learning about how to take advantage of some things I’ve not used much to this point - like GenServers - I want to make sure that I’m not setting bad habits or falling into anti-patterns. I guess all of that is to say that in this particular case, I don’t necessary NEED anything complicated because the technical demands are low and the traffic is also low. At the same time, I like using it as a way to learn how to approach solving some of these problems and want to try and build a habit of using best practices.

I’ve done a lot of reading on GenSevers, including many warnings about not forcing it and using GenServers more than needed. Additionally, I’ve read the often recommended The Erlangelist - To spawn, or not to spawn?, which was great.

I mostly wanted to get thoughts on the pros and cons of different approaches to break up the order handling portion of the application, which for now is handled in the LiveView process like everything else.

Actors as GenServers

One thought was to take each part of the processing (verify/reserve inventory, persist order to DB, export to a 3rd party system folks use to view incoming orders, communicate with user via email/text) and break them into GenServers, utilizing PubSub for communication. I like the decoupling this provides, but after reading the article above, I worry that this is an example of using processes to organize code, which is frowned upon! I do see some things, like inventory, that might benefit from being a GenServer simply because it would force sequential handling of order requests vs having everything in LV where I could get a problem if folks are checking inventory in parallel LV processes, then placing their orders. If there is 1 item left, both may verify that the item they want is available, then store their order. If I’m understanding correctly, a GenServer would give me the ability to prevent this due to the fact that it can only do one thing at a time. This would also allow me to store the inventory in the GenServer and avoid having to check with the database outside of startup.

Orders as GenServers

The idea here would be to create a GenServer for each order, allowing that GenServer to handle the order from placement to conclusion. With that said, I’m not sure this offers many benefits over just doing everything in LivewView. I could still run into issues where folks ordering at the same time cause problems when verifying inventory. It would give the benefit of a failed order being a separate process though.

Inventory only as a GenServer

Given the benefits mentioned above, I could simply make my inventory management a GenServer, having all orders check with it for inventory support, but allowing the other parts of the ordering process (persisting, exporting persisted order to a 3rd party, communication) stay in LiveView. I would likely still move the 3rd party interactions to GenServers for protecting against failures, but not as “Actors” that utilize PubSub. Just as typical call/cast interactions.

I know I’ve got a lot to learn and I’m sure my mental models aren’t all correct here, so hoping to learn a ton. Just wanted to throw some of these ideas out and get a sense for what some of you that have done this for a while and have more knowledge about how to handle these things would recommend.