Early this year at Compass we decided to significantly redesign our core product with a focus on making it self-service. This juncture was the perfect opportunity for us to also evaluate the technical stack we were leveraging. This evaluation lead to some specific changes as outlined below.
It is now 6 months from our re-architecture and I wanted to discuss the outcomes from our move.
MongoDB to PostgreSQL
The problem we were finding with Mongo was that our data model was becoming a bit of a mess. As a reporting and benchmarking tool it is fundamental that Compass maintains a well structured database. One of the fundamental features of MongoDB (schemaless storage) was proving to be a significant pain point.
Having our storage engine define our schema has certainly been a positive decision. We often run algorithms over very flat data structures - as a result we found a relational data model actually mapped effectively to our problem space. Although there are certainly domains where persisting documents can increase productivity, this simply wasn’t the case for us.
Compass is made up of both Software Engineers and Data Scientists. Much of a Data Scientist’s job is often spent cleaning and analyzing data. Moving to PostgreSQL has made it significantly easier for our data science team to explore critical data. The improved consistency coupled with their comfortability with SQL has lead to more efficient turn around in problem understanding and feature specification.
As PostgreSQL comes equipped with some NoSQL features our move really has felt like the best of both worlds. With PostgreSQL you can get document database-like capabilities using the JSON data type, and key/value storage with the HSTORE module. As a result we have been able to make use of these features where appropriate.
Rails to Sinatra
The biggest problem with our existing Rails application was that it didn’t have a clear separation of concerns. At times the web layer was implemented as simple restful endpoints, at other times it was rendering complex views. With our move to Sinatra we wanted to address this inconsistency and have a clear separation between the client (Angular SPA) and our web API layer. Furthermore in building a single page application a lot of what Rails would provide is overkill.
Since moving to Sinatra we have found it excellent for building out a web API layer. Its flexibility gave us the control over what libraries we leverage in building out our application we wanted. Furthermore its lightweight nature and simple method for specifying routes has given the team a restful focus, encouraging the separation between web and client we were striving for.
One interesting positive side effect that we didn’t anticipate, is that leveraging a micro framework has helped our junior developers better understand web semantics. In having less ‘configuration magic’, ramp up time for our junior developers may be greater, however it has forced them to gain a deeper understanding of how web applications are built.
Six months on from our architectural changes and the team remains busy building out new features for Compass. Software is forever changing and I am sure our stack will have significant evolutions in the years to come. But as it stands the team remains happy with the architectural decisions made and is excited about evolving the platform. Moving to Sinatra gave us the clear separation between web and client we were looking for, and PostgreSQL the tighter structure and subsequent boost in productivity we needed.
One thing I notice with junior programmers is that they often get unnecessarily attached to their work. I am not suggesting that you shouldn’t take pride in your work. There is no doubt that pride in ones work is fundamental for any professional. But more I try to instill in junior developers that programming is a path of continual learning and the sooner you can let go and enjoy this process of learning, the quicker you will grow.
Unhealthy attachment to their code can result in negative behaviors. For example, an introverted perfectionist may beat himself or herself up when you try to point out potential improvements in their work. An overly strong personality may get defensive over their choices and ultimately block external input. There is no doubt that insecurity at work in any profession can result in negative reactions, so the problem isn’t limited to programming. However, the reason I think it is so important for programmers to transcend their insecurities is because the reality is you will be learning your entire career.
When I was junior developer I had the fortunate situation of having an excellent senior engineer help me take steps to lose my insecurities. As we would pair program he would highlight the simple fact that as programmers we simply “write some code and then delete some code.” This simple reduction of our work for some reason resonated with me. Further having someone who I looked up to professionally validate my skills did wonders for my confidence.
The Node ecosystem is very active, this made it easy to find tutorials and examples to get me started. The package manager npm made it simple to pull in libraries to accelerate my development. To build my blog I chose Express as my web application framework. For editing I used Sublime Text, and debugging node-inspector.
I really enjoyed working with Redis. Redis is an in-memory key-value store, however it is probably better described as a data structure server. What I most enjoyed about working with Redis is how it changed the way I thought about modelling my application’s data.
Rather than thinking of data in tables and relations, or objects and graphs, Redis forces you to think in hashes, lists and sets. I found this focus on basic data structures at the persistence level appealing as it influenced me to build a flat and simple model. One other appealing thing about Redis is it is quick. Below are the results of the redis-benchmark utility run on my macbook pro:
Having deployed to both Appharbor, and Azure I was excited about seeing how Heroku did its thing. Heroku was a pleasure to work with. It really is as simple as creating an account then git push. Heroku automatically detected that I was deploying a node application and my site was up. Pulling in Redis as an add on was equally effortless. Another thing that I really enjoyed about working on Heroku is its focus on interacting with the platform via the command line.
I am in the fortunate situation of working on a project where we make use of F# scripts to administer a production application. It’s a very compelling approach as it allows maintenance tasks to be carried out through domain objects, rather than modifying repositories directly. This ensures that when data maintenance is performed, all appropriate business rules are applied and data integrity is maintained.
For its persistence mechanism the application makes use of LINQ to SQL. Within the scripts we build adhoc queries using F# quotations that are subsequently transtated into LINQ expressions to query our repository. For example we make use of the F# power pack’s ToLinqExpression in the function below to convert a quotation into a consumable LINQ expression:
Recently when I upgraded the scripts to make use of the latest version of the F# power pack I found that previously working queries now caused our application to throw exceptions that LINQ to SQL had no supported translation to SQL. My investigation found that the cause of these errors were that the latest version of ToLinqExpression had been modified such that when a quotation made use of =, >, >=, <, <= or <> the created BinayExpression make use of a different method for its comparison. So for example if I have a binding like:
When converted to a LINQ expression the implementing method for the binary operation u.FirstName = “Bob” is set to use GenericEqualityIntrinsic whereas in the previous version it was set to use String.op_Equality. As a result the LINQ to SQL provider could no longer translate the expression tree into SQL.
Rewriting the expression tree
Having identified the issue, I decided to come up with a function to rewrite the problematic parts of the returned expression tree. Below is what I came up with.
For me, the ease by which I could come up with this rewrite highlighted two very powerful features of functional programming, namely pattern matching and recursion. Pattern matching is simply awesome. The simple syntax in modifyMethod to test for specific expression types makes expressing the program’s intent both clear and succinct. You can do all sorts of things with pattern matching, however, I won’t cover those details in this post. Similarly, routines on trees are often most easily expressed using recursion. This is natural because the tree is a recursive data type.
The command pattern describes a way to represent actions in an application. It is used to encapsulate actions so that they can be invoked at some later point. We often see collections of commands that specify steps of a process or operations that the user can choose from. Below is an object oriented way of implementing this pattern.
If we consider this pattern from a functional perspective, command objects are being used as functions. Indeed they are equivalent to functions. Thus it is arguable that the command pattern is more readily adopted in object oriented programming when there is a lack of support for higher order functions.
Object Oriented Example
Let’s look at a simple example. Say we are tasked to develop the software to control a robot by remote control. Following shows how we could implement this using the object oriented command pattern.
Below shows how we could implement the robot example drawing on functional constructs:
You will notice that in the functional solution there is no command interface defined. The reason for this is because F# has support for higher order functions. An object oriented way to understand a function is to think of it is an interface with a single method. Thus in a functional language like F# the single method command interface becomes unnecessary.
Another difference is that in the provided functional solution the data (i.e. location of the robot) is independent of the functions that transform the data (move and rotate). The reason this is adopted is because one of the principles of functional programming is referential transparency. That is, for a given set of inputs at any point in time, a referentially transparent expression will return the same result. The benefit is that referentially transparent expressions are deterministic by definition. In supporting this principle the functional solution above makes variables immutable, and rather than modifying arguments, the processing functions return new variables.
The command pattern is used to represent some behaviour which you know needs done. In a functional language the need for this object oriented pattern is diminished because these behaviours can be encapsulated and passed around as functions. Design patterns are an interesting topic as they provide solutions to commonly occurring problems in software design. However, I really enjoy it when a language or framework can appropriately abstract me away from boilerplate code. In fact I think Paul Graham sums it up best:
“When I see patterns in my programs, I consider it a sign of trouble. The shape of a program should reflect only the problem it needs to solve. Any other regularity in the code is a sign, to me at least, that I’m using abstractions that aren’t powerful enough - often that I’m generating by hand the expansions of some macro that I need to write.”