create the account first last
Most signup flows I've worked on create the user row on the first screen. You type an email, a row appears, and every screen after that patches it. It feels efficient. It is also how you end up with thousands of accounts that have an email, no phone, no store, and no way to finish.
I used to think the fix was cleanup jobs. I now think the fix is to clean up half-created accounts never create them.
the flow, as a state machine
The client doesn't decide what comes next. It asks the server, and the server answers with the next step.
stateDiagram-v2 direction TB [*] --> identify identify --> verify_email: email or oauth verify_email --> details: otp ok verify_email --> verify_email: wrong code details --> verify_phone verify_phone --> create: otp ok create --> [*]: account + store
Nothing is written to the users table until the last arrow. Everything before it lives in a short-lived session.
| step | stored where | survives a refresh | creates rows |
|---|---|---|---|
| identify | session (TTL) | yes | no |
| verify email | session (TTL) | yes | no |
| details | session (TTL) | yes | no |
| verify phone | session (TTL) | yes | no |
| create | database | yes | yes, in one transaction |
the server owns the next step
type Step string
const (
Identify Step = "identify"
VerifyEmail Step = "verify_email"
Details Step = "details"
VerifyPhone Step = "verify_phone"
Done Step = "done"
)
// Next is the only place that knows the order.
// The client renders whatever it is told to render.
func Next(s *Session) Step {
switch {
case s.Email == "":
return Identify
case !s.EmailVerified:
return VerifyEmail
case s.Name == "" || s.StoreName == "":
return Details
case !s.PhoneVerified:
return VerifyPhone
default:
return Done
}
}Because the order lives in one function, adding a step is one case, not a change in three apps.
what it bought us
- No half-created accounts. There is nothing to clean up because nothing was written.
- The client got simpler. It stopped tracking progress and started rendering steps.
- Retrying is safe. Refreshing the page asks the server where you are, and the answer doesn't change.
The trade-off is that the session store becomes important. If it goes down, signups pause. I'm fine with that. A paused signup is better than a broken account.