Time for some learning
I set myself a little challenge to get a basic Vue.js app working, with persistence (probably to mongo) and real-time comms via web sockets, the application is a basic note taking app where users can see who else is online, the notes they’ve made, and even collaborate on the same notes.
I’ve hosted the application(s) in Heroku, my platform of choice for personal projects due to its free tier, ease of use, great add ons and now dev pipelines.
Both are public projects in my GitHub profile, links below;
THOUGHTS ON THE PROCESS
The steps I undertook to build the application(s) are described below (with some estimates of time taken for reference) but before we get into that I wanted to jot down some thoughts on the task and the process;
Vue is lovely
I’ve not used it before but I can see it’s potential, our team assessed Vue vs React and picked the latter for a rewrite of our SPA based on its maturity, contributors, and adoption, but we could easily have picked Vue.
I have used the boilerplate vue-cli project from the official VueJs templates, I had not used webpack before this little challenge so I needed to spend some time understanding the dev setup. It just works out of the box which is nice. The hot reloading is especially good.
On that note, I manually pulled in bootstrap CSS and fonts for some styling tweaks, which is probably entirely wrong as I have circumvented webpack. If I had more time I would learn how to do this properly!
The tests do not run, again with more time I would like to get at least some form of unit testing in place.
The example I based the SPA off uses native JS for things like array manipulation, If I were to invest any more time in this little demo I would pull in underscore or lodash to help with these operations.
There is no routing in place, the app is served on the root, if I were to build out the application I would need to set up routing as the functionality grows.
I have not implemented explicit closing of connections, nor an accurate connection status of users. I do have a list of connected users but this is created as users connect and is not actively maintained (other than when closing your connection, others eventually see you disappear from their connected user list). This missing functionality was purely down to time, it would be nice to show status against the usernames on individual notes, but for the purposes of demonstrating the stack I deemed this not strictly necessary.
I implemented my own socket server, when the client connects it sends its user and any local notes it may already have (from local storage) to the socket server, this in turn stores this data (for now in memory cache but with more time it would be to a document store) and then broadcasts all users and notes back to everyone.
This last point is a bit heavy handed but it means that everyone gets updated, each client handles the messages appropriately and stores in its own local storage, thus completing the synchronisation. This mitigates the client side distributed sync issue I had during development where system notes would have been lost forever when everyone was offline, or if everyone cleared their local storage. Now as long as the socket server remains up and it’s memory cache intact the system state is preserved. Proper redundancy can be achieved via storage to disk, either a database or document store such as Mongo, given the time.
More users, more problems
A race condition exists on the edit of the same note by two (and presumably more) users, the last user to save receives a duplicate that is never synced to the other user’s notes, due to the way I have written the logic. This could be solved by locking the notes upon edit and propagating the lock. So only one user can edit one note at a time, upon saving the note would be unlocked and the note could be edited by another user and start the cycle again, this feature is a trade-off of time vs value, for a production system this is a must have, for a demo project at least we are aware of it and can decide on time vs value to fix/implement.
One thing I found when developing the socket server and connecting to this, in my implementation hosted in Heroku I need to leave the port number off the address that the client app uses to connect, a minor point but it caused a bit of pain.
I initially created my repository in bitbucket, so have moved over to GitHub but in so doing may have mangled my commit history! I’ve pushed all my local branches to this new remote and continued work from here so you can see the branches and how I’ve broken down the tasks as per the summary below.
QUESTIONS & ANSWERS
Some responses to common questions about this little exercise.
Q. What was your reasoning for implementing the solution they way you have?
Having never used Vue or Websockets I decided to use a template project and learn the fundamentals to get a working prototype running, and then research and implement communication via the socket server. I made a choice not to test drive the development due to time constraints as I needed to learn a new framework and create a running application in a relatively short space of time.
Q. How would you manage users going offline and online?
I could periodically attempt to ping the socket server or another remote endpoint and use the response from this to indicate connection status. Once I know that then I can use local storage as our cache of unsynchronised changes and when a connection becomes available after a period of being offline I could send up unsynchronised data.
Q. How would you manage allowing users to continue editing and adding notes when in offline mode and then syncing when they come back online?
Answered above, if the user is offline then do not send messages to the socket server, just mark content as unsynchronised and store locally until later, then send and mark as synchronised when online.
Q. How would you manage conflicts between edits made by different users to the same note?
There are several approaches I could take to this;
- I could give precedence to the last change seen.
- I could agree on a rule that the creator always overrides others in the case of a conflict.
- Or my preferred solution (and one which would mitigate the race condition duplicate note scenario described above) is to mark a note as locked when a user starts to edit, propagate that lock to all other users and ensure the application does not allow a user to edit a ‘locked’ note. Then upon saving of the locked note I could clear the lock, propagate this via the server and allow other’s to edit.
SUMMARY OF STEPS TAKEN, WITH TIME SPENT
- Set up repo and boilerplate Vue app http://vuejs-templates.github.io/webpack/
- Followed installation and getting started notes
- Implemented vuex for store/persistence https://www.npmjs.com/package/vuex
- Addition of store and basic components from to do list tutorial can now add, edit, complete and remove todos
- Refactored components from todos to notes
- merged vuex branch up to develop created a new branch from develop called todos-to-notes
- refactored todos to notes
- added created date, and filter using moment to display it
- updated styling/layout
- added basic validation of note description
- merged todos-to-notes up to develop
- Implemented local storage of notes
- created a new branch from develop called
- rehydrate app from local storage on start/page reload
- persist add, edit and remove in local storage
- persist archival of notes in local storage, using the newly added identifier to find and update the archived note
Web socket server
- Implemented use of web socket server
- read up on web sockets
- implemented basic connection
- new branch
socketscreated for this part of the build
- allocated user an identifier for reference
- show user id against notes
- send a message to socket server whenever there is a change to user’s notes (add, edit, archive or remove)
- process received messages from socket server informing of another user’s note changes
- show other user’s notes
- List of other connected users (Connection status, only set upon connection, not updated/maintained so inaccurate)
Server side memory store
- Implemented in memory storage on web socket server
- created my own socket server based on the example and further research due to deployment challenges!
- added unique legible user names to SPA interface, courtesy of: https://jsfiddle.net/katowulf/3gtDf/
- created arrays of users and notes in memory using node-cache
- ensured that on client connect the user and local notes are sent to the server for sync to other connected clients
- Deployed hosted SPA and websocket server applications to Heroku;
Estimate 2-3 hours:
- Implement persistent (to disk) server-side storage
So there you go, roughly 10 hours effort to get a basic prototype up and running, not bad if I do say so myself.
I thought I’d change up the format for this project page seeing as this was purely a learning exercise, rather than a production application. I hope you like the application and that there is some useful information for you. If nothing else it has helped me to get my thoughts down in writing and to show how I approached this problem, something which is useful when planning new projects or discussing technical challenges with colleagues or clients.