I have learned that it is costly to not prioritise expressing one’s design concerns and ideas early. As a result, we have a shopping cart that is noticeably slow, goes down whenever the backend experiences problems, and is a potential performance bottleneck. Let’s have a look at the problem, the actual and my ideal designs, and their pros and cons.
We have added shopping cart functionality to our web shop, using a backend service to provide most of the functionality and to hold the state. The design focus was on simplicity – the front-end is stateless, any change to the cart is sent to the backend and the current content of the cart is always fetched anew from it to avoid the complexity of maintaining and syncing state at two places. Even though the backend wasn’t design for the actual front-end needs, we work around it. The front-end doesn’t need to do much work and it is thus a success in this regard.
However there are other concerns than simplicity and time-to-production that we could have been taken into account, I believe. But I failed to communicate those in time. The current solution would have been perfect if the network transfer times were negligible and if the backend service was 100% reliable. None of that is true. Every call takes at least 300ms and the backend can be overloaded or (as yesterday) unavailable due to network issues. I there is a high load, all customers will experience a very slow cart multiple times.
I prefer to design for robustness, with a healthy does of paranoia with respect to performance and the availability of any dependencies. I would have advocated to create the shopping cart in-browser and to hold the state primarily there, using localStorage (if available) for persistence and sharing across pages and tabs, sending the content to the backend asynchronously. When the user clicks “buy”, the item would be added immediately to the cart, not after few 100s of ms or a few seconds. And if the backend eventually rejected the change, we would notify the user and remove it from the cart (an “optimistic transaction” – that is what Om Next does by default). If the backend is down, we can inform the user that fulfilling the order isn’t possible at the moment but that the browser remembers it (even if quit) and that they should come back and try later again. I would primarily talk to the backend only at the last possible moment, when the user clicks “go to checkout” – or, perhaps, if the backend isn’t overloaded, also after any change to the cart but asynchronously, to improve the perceived performance when the final button is clicked. The front-end and especially the browser code would be more complex and take longer to develop and test but it would be very well isolated from backend problems and would generate minimal load on the backend. I believe that in this case, it would be worth it. (Though I admittedly tend to overcomplicate things, with my performance and availability paranoia.)
We all have different experiences and preferences. It is therefore important to get varied people involved in the design of core functionality early. I should not expect other people – no matter how capable – to have the same experiences and insights as me and should thus prioritize voicing them. (Though I have to keep in mind that I am not infalliable and the different experiences and preferences of others are equally, if not more important than mine.)
After introducing TLS session re-use and optimizing our server-side code, the time needed for the back-end calls went drastically down and it is pretty usable now. I still dislike the tight coupling to the back-end but there is currently nothing forcing us to change it. We have a potential performance problem but only the future (and proper performance testing) will show whether it is a real problem. May be the future will show me that the simple solution we have adopted is good enough (and much “cheaper” in terms of development and maintenance time).