As an exercise in OTP for myself & my team, I was building an in-memory user accounts system. I am roughly following @lance’s path laid out in his book.
Account is a
GenServer. The primary reason behind this is to enforce that the operations and state transitions between “unregistered”, “unconfirmed”, “active”, “deactivated” states are wrapped in application-level transaction and are no race conditions such as 2 users registered with the same email because double form submit etc. This is enforced by simple state machine module that guards the transition rules.
What I am doing is that I start an
:simple_one_for_one strategy. It is responsible for starting new
I want to be able to find each account either by ID, or by e-mail. Both are unique. So I created 2 unique registries:
AccountnsById. Whenever I start server for particular account, in it’s
init function it tries to register in both registries. If this fails at any point - this likely means server is already started for particular account. When I attempt to log user in, I use registry
AccountsByEmail, when I have account ID - the other one.
I have a few questions to the above:
Is my use of
Registryas node-wide unique index good? I am especially concerned by the fact that I had to create 2 indices because I am looking the
Accountsup in 2 different ways.
I am designing the persistence mechanism for this now. What I think is that along the
Account, I will start an
AccountDiskWriteror something similar - another process that would be a
Accountchanges. It would serialize these changes and write to disk (most likely just insert to DETS).
I need a way to bring everything up on the server start up. I was thinking about simply going through all my
Accountsfrom DETS tables, and restoring it one by one on system start up. Does this make sense?
Where do I load the saved state of the
GenServerin case of restart by supervisor, or in case of 3)? I am having some trouble figuring out it. As far as I understand the
initfunction is blocking the parent
Supervisoruntil it returns, so it’ll become a bottleneck if I put the disk reads there. As alternative, I could send myself a message from the
initfunction, then handle it in
handle_cast, where I would read the state from DETS, and only then register to the both of my
Registries. I am interested, however, from my parent process (like web worker) to know if the
Accountwas started & restored properly. So in such solution I no longer can rely on the returned value of
initfunction, as the account will likely to start properly always - it will fail later in it’s life cycle when it restores state. So I have to block waiting to see if it appeared in Registry, possibly with some timeout. Is this correct solution? I suspect there may be simpler one.