Ask HN: How do you deal with atomicity in microservice environments?
There is no one-size-fits-all approach to atomicity in microservices.  Microservices typically want to store the most minimal amount of state possible, and push the coordination up a level; i.e., the calling service will send all the relevant information to the service that is going to handle this tiny piece of the work.  Eventually there is going to be one service that owns some sort of workflow, and pushes the rest of the world in the direction of its desired outcome.<p>A year or so ago I needed to write a service to rotate accounts on network equipment every day or so.  The control channel and the network hardware tended to be flaky, so I designed a state machine to make as much progress as possible, and be able to pick up where it left off.  Each newly-created account was a row in a transactional database, and the successful completion of the operation was noted by changing a column from NULL to the current time.  The flow was; if a new account is needed, generate the credential (generated_at).  Find the old credentials and log into the device.  Add the new account (applied_at).  Try logging in with the new account (verified_at).  Wait until account expiration (2 days later).  Delete the account (deleted_at).  Verify that we can&#x27;t log in with the old account (delete_verified_at).<p>From this data, we could see what state every account was in, and query for a list of operations that needed to be performed.  (Or if nothing needed to be done, how long to sleep for so that we would re-run the loop at the exact instant when work needed to be done, not that it was critical.)<p>I believe that your account creation and account deletion should follow a similar workflow.  Accounts that fail to be created after retrying for X length of time should just move into the deletion workflow.<p>The user creation service and the day-to-day user operation service should probably be the same thing.  The &quot;user&quot; row in your database should be what the state machine uses to figure out what operations need to be performed.<p>A user record would probably look something like:<p><pre><code>  username, password, email, ... = &lt;stuff your user gives you&gt;
  created_at = &lt;now&gt;
  stripe_account_id = &lt;what the stripe account creation rpc returns&gt;
  stripe_account_verified_at = &lt;time when you verified that the stripe account works&gt;
  auth0_account_id = ...
  auth0_account_verified_at = ...
  delete_requested_at = &lt;when the user clicked &quot;delete account&quot; or you decided you don&#x27;t want to retry the above steps any more&gt;
  stripe_account_delete_verified_at = &lt;time that you ensured the account is gone&gt;
  auth0_account_delete_verified_at = &lt;ditto&gt;
</code></pre>
Now you can do queries to figure out what work needs to be done.  &quot;select username from users where delete_requested_at is null and stripe_account_verified_at is null&quot; -&gt; create stripe accounts for those users.  &quot;select username from users where delete_requested_at is not null and auth0_account_delete_verified_at is null&quot; -&gt; delete auth0 accounts for those users.<p>The last bit of complexity is to prevent two copies of your user service from running the query at the exact instant and deciding to create a stripe account twice.  I would personally just run one replica of that job.  This makes deployments slightly more irritating, since no user creation can occur during a rollout (when the number of replicas is 0), but it sure is simple.  I worked on a user-facing feature at Google that did that, and although I hated it, it worked fine.  It is also possible to add a column to lock the row; check that stripe_account_creation_started_at is null; change it to the current time; commit.  Only one replica will be able to successfully commit that and progress to the next step, but then you need a way of finding deadlocks (the app could crash right after the commit) and breaking them.<p>It is a little complicated but I personally would rather have exact accounting of what is happening and why, rather than guessing when some user logs in and doesn&#x27;t have a stripe account.<p>Edit to add: one last bit... I like running this all in a background loop that wakes up periodically to see what needs to be done, but your createAccount RPC should also be able to immediately wake this loop up.  That way if everything goes well, you don&#x27;t add any latency by introducing this state machine &#x2F; workflow manager.  If something happens like Stripe being down... progress will resume when the loop wakes up again.  For that reason, I think you should be explicit and provide the end user with a system that lets them request an account and lets them check the status of the request.  (Maybe not the user directly, but your signup UI will be consuming this stuff and providing them with appropriate feedback.  You don&#x27;t want the createAccount RPC to just hang while you&#x27;re talking to Stripe and Auth0 in the background.  Probably.  The happy case might take a second, but the worst case could take much longer.  Design your UI to be good for the worst case, and it will be good for the happy case too.)