I was watching Lance Halvorsen’s talk Phoenix is Not Your Application and thinking about how I might apply that to my Phoenix “draw” app.
What exactly is my “domain” for this app? Here are the requirements:
- Has Users that can sign-in/out
- An anonymous (limited) user is allowed
- Users can create/update/delete Canvases they own
- Users can share canvases with other users
- Users can delete their account
- Canvases can add strokes that have meta-data as to which user created it
- Canvases can add “erase” strokes that erase over previous strokes
- Canvases can created/updated/deleted/forked
- Canvases can contain strokes from multiple users
- Strokes are list of points. Each point has a UTC timestamp (for fun playback)
- Strokes have a pen setting. For simplicity, it’s just “black line” and “erase”
- UI shows the canvas
- As a user draws, the canvas shows their strokes
- As another user draws, the canvas shows all user’s strokes
- The user sees, not just a stroke when another user completes it, but sees the stroke as it’s in the process of being drawn.
- The UI allows the User to switch pen types
- The UI exposes actions pertaining to Canvases (new/edit/delete/share)
- The UI exposes actions for user stuff (sign-in/out, edit profile)
- timestamp (UTC milliseconds)
This is could be a long section here. For now, I’m going to assume a permissions system will exist that will enforce actions by users.
(Aside from the obvious…)
- Get me all strokes for a canvas since (timestamp) (to assist in UI connection/disconnection)
Usual CRUD stuff
With that in mind, I’m considering rewriting this with these Elixir Applications:
This application would be in charge of persisting and querying state.
This is where I implement the domain of the app. This has all the business logic around logging in and permissions. It would handle setting up sharing between users. This also handles taking in strokes from users for canvases and making sure everything is persisted. This would also facilitate sharing of canvases, exporting canvases to an image format.
It would also be in charge of notifying other layers about changes in state (probably would use “The New Event Manager” from http://blog.plataformatec.com.br/2016/11/replacing-genevent-by-a-supervisor-genserver as I’m undecided about GenStage for this use).
As I’m expecting this app to be mostly based around Phoenix Channels, this is where I implement the channel, used as the “message board” for canvas strokes. All “ephemeral” state will be sent down this channel and echoed to the other channels, and when actions result in more permanent state changes, this will send those changes to the App layer.
For example, as a stroke is drawn by the user, the stroke-in-process will be part of the ephemeral state and be echoed to the other channels so they can also draw the stroke-in-progress. However, it won’t be sent to the app itself because it is not yet a stroke. Once the user lifts up the mouse/finger, the stroke is complete and it will now be sent to the App. I want to draw a clear distinction between what the UI shows as a user is in the process of initiating action, vs. the result of that action.
In that sense “make new stroke. Here are the points” is an action”. Interactively creating/editing those points on a stroke is a transient state and not committed. The latter is just providing feedback to the user as they compose the final action. That is what is sent around the channels. Another way to think about it is “how valuable is this data? Would it hurt too much if it wasn’t persisted?
This exposes the HTML UI for presenting the app on the web. It also has the standard CRUD html endpoints for canvases and users.
- “Forking” a drawing from another user
- Exporting a drawing locally to PDF/PNG
- sharing to a slack channel
- As timestamps are recorded in the strokes, play back a movie of how a drawing was made.
- Make a shared drawing that only shows strokes within an ongoing time “window”, so as you are adding strokes to a drawing, old strokes are also being removed, oldest first. Then the drawing is continually turning into something else. This could be fun with multiple users.