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 *.azurewebsites.net SSL certificate
The server is hosted on azurewebsites.net 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.

Saturday, August 9, 2014

Lessons, Surprises, Mistakes III. - AMS

Next set of lessons, surprises and mistakes; this time about Azure Mobile Services (AMS).

Download location
Maybe I am blind but I cannot find a download link for the actual version of MobileServices.Web.js script. WinJs versions are easily available via nuget. Digging into tutorials I have found a location with all Web versions. Comparing it with nuget page, the latest available Web version is 1.2.2 (although WinJs is already at 1.2.3):
http://ajax.aspnetcdn.com/ajax/mobileservices/MobileServices.Web-1.2.2.min.js

Update 24.1.2015: There is a changelog from which you can see that at this moment, the latest Javascript SDK is 1.2.5.

Shared Scripts
There is a possibility to manually upload a script to shared folder on server:
azure mobile script upload shared/helper.js
You can then use it via require in other scripts:
var helper = require('../shared/helper');

Unfortunately, the scripts there are periodically deleted. It turned out that manual upload is not supported and you can only use this feature if you enable source control. See more info on forum.

Stuck scheduled tasks
I've managed to get the scheduled AMS task into state where it ignored all my updates. It ran the old script version no matter what I did. Even deleting the task and creating a new one with different name did not help. I asked on forum and got quickly an advice how to work around the issue by restarting the whole mobile services.

Wrong authorization
Although I require an authorized user when accessing all AMS endpoints, it took me a while to spot an error in my implementation. I did not refuse cashier operations from unknown users. You couldn't do it from UI, of course. But any script kiddie could download the POS, get the application id, authorize with any google account, and send as many transactions/shifts as wanted. The fix was simple - only allow server operations to known users.

SQL Tables Initialization
I let AMS to create my tables. It was a mistake. Only when I started to have performance issues with getting report data, I realized what types are used for the data. For example, as JavaScript does not have integers but only floats, all integer values were stored in float columns. Not very fast and index-able… I have changed type of some columns but next time I will create proper SQL init script.

SQL query optimizations
I had not written complex SQL query for a long time so the first version of the report query was quite embarrassing. There were too many inner queries and other issues. After a consultation with my friend, I speeded up the query by
  • Use null id trick to select the whole row with max value
  • Use union all instead of union because all sub-queries are distinct
  • Converting float columns to integer columns
  • Index on date column did not help because server still clustered index on __createdAt column which has very similar content to the date columns