Building apps data-first with Sinatra
(Why, after 12 years with the Ruby stack, I like Sinatra more.)
I built my first Rails app in 2009, and have used it periodically for both websites and apps ever since.
Sinatra intrigued me from the beggining; it was a raw approach that felt "closer to the metal". I used Sinatra to learn about REST and hypermedia design, especially as I matured as a web developer. This proved to be very useful for filling in the gaps left after working with Rails, with its priority given to pre-configured options.
Recently, I used Sinatra to build a prototype. The thought was to code a proof-of-concept web app in Sinatra, then migrate to Rails once the complexity has climbed above a certain threshold.
As the app grows, however, I want to stay with Sinatra. Here are the reasons why.
Sinatra is declarative
The entry point for a Sinatra app is the route, which you must define yourself. This makes control flow clear and logical: when I hit this URL, this action happens. HTTP, and not the framework, is in the driver's seat and I like that.
No generators
I have to construct my forms manually in HTML. "Why would anyone want to do that?" — you may ask. Why not save yourself some time by having this "boilerplate" generated?
Firstly, I am used to hand-writing native HTML (as I think any web developer should be).
Second, to me HTML is not "boilerplate". It's the ultimate source of truth because:
- it is universal across back-end frameworks
- it is universal across devices
- it is the interface between user actions and application code.
The process of data creation by the user underpins all web applications. When coding a web app, I want this universal mechanism to be transparent and non-abstract. This means explicit form action attributes and form inputs.
To me, procedural Ruby code does not look good in an HTML file. Statements like form_for
and fields_for
obscure the underlying markup. For some, it saves time because they don't have to hand-write the forms. My time is saved when I don't have to parse out these statements visually to understand what they do, and to debug them.
Sinatra is data-driven
Rails is a very object-oriented framework, and this is reflected in its workflow:
- You run
bin/rails generate
. - Rails produces a number of files centered around a model — the model itself, the controller for CRUD actions, the views, and the database migrations.
What files get generated will depend on the argument you provide to the generate
command (model
, controller
, or scaffold
). But the underlying design principle is that the model (the object) is used to derive everything else.
However, the app I'm building can be described as "data-driven templates". Data D
embedded within template T
is used to generate, with varying degree of user input, data D'
and template T'
.
It is functional transformations on data, happening via HTML templates, driven by logic provided by Ruby code.
This app is so close the data-flow model of programming that it could just as well be implemented in a spreadsheet (if one were to ignore its UX aspects).
This means that most of the time, I'm not at all concerned with finding object-level abstractions and containers for my data. I'm concerned with defining data sets and the user actions that produce one dataset from another.
No information model mismatch
This is a continuation of the previous point.
It is well known that object-oriented systems introduce what is called object-relational mismatch into the design of web applications.
Without getting into definitions, this means that mapping an object's attributes to rows in a relational databse (and back again) comes with a loss of data model integrity.
However, I'm beginning to think that there is at least one other type of mismatch caused by object-orientation — this time with regards to web architecture design. It's the information model mismatch.
OO systems assume that any data are necessarily the attributes of an object. So before you can build any data model or do any data transformation, you must first define your objects to serve as containers for that data.
This assumption is wrong. Data can, and often does, exist prior to and outside of objects. Data is stored in CSV files, JSON, relational databases, and within data structures inside Ruby files.
Hence, bottom-up (data-driven) design should be the natural first step when it comes to building a web application.
Natural, unless your perspective is tainted by object-orientation. In the latter, you must first design abstract containers for the data you might not even have properly defined. That is a detour from the essence of programming. Objects are abstractions while data is concrete. Not all data fits into a Person object, or a Task object, or an Activity object — at least in the initial stages.
Conclusion
Sinatra is declarative and procedural, but feels very functional. It's data-driven.
For these reasons I don't really miss Rails.
This is not some crusade against Rails. Rails' focus on giving the developer the most productive environment possible is admirable, and has resulted in many valuable innovations through the years. Its ecosystem is active and full of dedicated contributors. The community is friendly and entrepreneurial.
I might switch to Rails eventually; or, I might decide to proceed with Sinatra while managing and minimizing the increasing complexity in a purposeful way.
The point is this: if you're an experienced developer, I encourage you to give the data-driven style a try. Ask yourself: if you had no framework, but just data and user actions, how would your design process change?
Would you be able to:
- translate business requirements into code more efficiently?
- close in on the core aspects of your design more directly?
- prototype, stage throw-away experiments, and change direction more nimbly?
You might find this exercise valuable.