Monday, August 24, 2015

Lessons, Surprises, Mistakes - v2015

Rewriting the application always takes much longer time then you think. The POS was ready for the new season but the admin part was missing and was only implemented during the first month. On the other hand, the new architecture splits the deliverable code to the kiosk and admin packages so admin updates do not affect kiosks and the kiosk part is smaller.

Database structure
I have the proper SQL initialization script (instead of creating the tables by AMS). The database is normalized so getting data for reports is easier. Previously, the transactions were stored in one table where items was just a string of item ids. The reason was to have simple server code - I did not want to write server JavaScript code which inserts data into two tables. Now, the Entity Framework handles all the complexity so the transaction items are stored in separate table.

Query speed differences
I thought there wouldn't be big difference when running SQL queries on my local SQL 2014 comparing to Azure - and if so, that Azure would be faster. But I was wrong. For some reason, the same query via Entity Framework took couple of seconds on local database but couple of minutes in Azure. I had no idea how to debug it and find the reason so I rewrote the query into pure SQL which works fine.

Slow Android browser
When I was testing the application on tablet, I saw it is very slow. After you touch a button, you had to wait a little bit to be your next touch recognized. Annoying. So I tried all available browsers for Android and the best one is Opera. It hast the largest screen estate and it is really fast - see the picture.

Ghost transactions
When my neighbor checked the sales one morning, he was very surprised. There were sales for 450 000,- Kč in one kiosk. Quite a lot when a small ice cream costs 14,- Kč! It turned out that the touch layer on the tablet went crazy and generated touch events on the lower part of the display. The kiosk signed in a cashier at 3:15 by itself and created one transaction every two seconds on average. Interesting bug - on the other hand, it was nice load test :-).

Shift duplicate check
Kiosks send all errors to the server and I check them from time to time. Early after launch, there was an error that shift cannot be saved because it already exists in database. It turned out I forgot duplicate error handling for new shifts. If the shift was correctly stored in the database but the server response got lost, kiosk tried to send the shift again and again.

Thursday, August 20, 2015

POS v2015

The new version of POS should resolve several issues I had with the first version:
  • Smaller application size - 402 kB is quite a lot when my code is just 78kB
  • Admin part should work on iPhone - as I wrote previously, this looks like Durandal issue
  • Resolve token expiration
  • Resolve the mysterious double transactions

Plus I wanted to try new stuff:
  • Azure Mobile Services with .NET backend
  • Gulp, Browserify
  • Mocha, Chai and Sinon for JS testing

The above basically means to rewrite the app from scratch.

Smaller application size, iPhone
To decrease application size, I decided to remove Durandal and jQuery and go with just Knockout. Communication among view models/modules is done via knockout-postbox - a simple pub/sub library. Other libraries I use are Underscore.js and Moment.js. The final size is 220kB - almost a half of the first version. The biggest part is of course AMS client library with 134kB.

Removing Durandal also removed sign-in issue on iPhone so my neighbor can finally use his phone to check daily sales.

Resolve token expiration
In the end, I've implemented the easier solution. Application remembers when the login token was acquired and it signs out kiosk user after 25 days as soon as there is no opened shift. Because credentials are stored in the browser, it's just one more click for the cashier.

Resolve the mysterious double transactions
Of course it was my fault! When I performed deep analysis of duplicated transactions, I've found out they are single-item transactions within sequence of single-item transactions. And then it struck  me - I don't check whether save is in progress when saving new transaction. As the transactions from local storage are sent in FIFO order, the same transaction was sent several times to the server when cashier created multiple transactions in row. Some of the duplications were caught by the server check for the transaction duplication but some slipped through as saving data on server is asynchronous.

The new version of course have the save in progress check and I have not seen any duplicate transactions.

Azure Mobile Services with .NET backend
Using .NET backend in AMS server part is much easier for me then using JS backend. One can use local server for development with local database so new functionality development and testing is simpler. Also adding custom endpoints is easy as it is almost normal Web Api application. I was hacking CRUD endpoints in JS backend in past.

The backend uses Entity Framework to communicate with SQL server. I don't like EF much. It's a black box to me - I don't know how it creates SQL queries and what should I do to optimize them. So I rather use pure SQL queries for all custom endpoints functionality (get data for reports).

Gulp, Browserify, Mocha, Chai, Sinon
I find Gulp easier to work with than working with Grunt. It's probably because I prefer to write what I want to do then to define a configuration. Configuration is fine for simple cases but for anything else, the list of commands to do is better.

I use Browserify to manage module dependencies and create the final package. I found example how to use Browserify with Mocha so I switched to Mocha from Jasmine. It really does not matter which test library one use as long as you test your code.

Tuesday, August 12, 2014

Open issues

This is the last article for the moment. It is about things I don't know how to solve or have not yet had time to solve.

Double transactions
I've already mentioned this issue but I still don't know what is the root cause and how to mitigate it. The check to not insert already inserted transaction prevents creation of ~25 double transactions per day. Still, I see there is usually one double transaction per day (that is per ~2500 transactions). I am quite confident this is a sever side issue as __createdAt times are very close to each other.

POS does not work on iPhone
For some reason, the application does not work on iPhone. The main page is displayed, user is correctly redirected to the Google's sign in page but after successful sing in, she is redirected back to the main page which does not proceed to the next page. I've briefly tested the issue on borrowed iPhone and found out it is caused by combination of AWS and Durandal. Pure AWS page works just fine and I have not found any indicia that Durandal does not work on iPhone. I believe I just need to borrow the iPhone for longer time to fix this issue.

Built-in * SSL certificate
The server is hosted on domain and uses built-in wildcard certificate for secure access. Although I have read several articles that using wildcard certificates is bad, I don't think it is bad in our case. It is a REST service where both endpoints are fixed. If you disagree, please let me know an example of real threat.

For reference:

Authentication token expiration
The authentication token expires approximately every month. Which is the reason why I still have not developed automatic re-authentication when the token expires. It hasn't had enough priority.

There is a way how to do it generally for all requests by using withFilter (usage example). Or I will just sign out and in every couple of days. Whatever will be easier to implement.

No tests for server side JavaScript
I have tests for client side JavaScript and for SQL queries but I do not have any tests for sever side JavaScript. The main reason is there was usually just a few lines of simple code so I did not bother to create a testing set up. But this is slowly changing so creating server side JavaScript tests climbs up to the top of my to-do list.