2. Our R&D problems started when we decided to
remove a couple letters from the company name.
You’d think it would be easy! We used to be called
GetTaxi, you see - and we provided only on-
demand taxis. Then we changed the name to Gett,
and now provide many kinds of on-demand services
(salads, flowers, pizzas, etc).
Intro
3. To make it possible to launch these new services
quickly, we did a lot of work in the last year. Part of
this work had to do with how each order can be
priced, since the pricing business logic has become
a lot more complex. This is what this talk is about.
Intro
5. So, customer has our Customer app installed,
Supplier has our supplier app installed.
Customer makes an order for something, it arrives
to the Supplier app, Supplier delivers the goods (in
case of a taxi ride the goods are the Customer
him/herself :))
Intro
6. So, how do we determine the price of an order?
Well, there is a Pricing Calculator library with
complicated business logic which dynamically
calculates the price of the order based on its current
properties.
Intro
8. Normally this code runs on the server, and gets
pushed to or fetched by the client. However,
sometimes the client loses network connection right
at the end of an order which needs to be paid with
cash. Since in this case the supplier must
immediately collect the money from the client, the
ride must be priced right away, and without
network.
Problem
12. Initially, we ported this library to native
iOS/Android. But the library has complicated
business logic which changes often, so this
approach was costly, time-consuming and difficult
to maintain.
Problem
14. You probably heard of isomorphic code in the
context of Node.js web development (if not, see
isomorphic.net for more details).
Well, it’s easy there because in the context of
Node.js Javascript runs both on server and on the
client (browser).
Solution
15. But we love Ruby and want to develop and
maintain code in Ruby!
Alas, currently nothing beats Javascript when
executing on clients.
What to do? Opal: Ruby to Javascript compiler.
Solution
17. The Opal Open-source Project is a source-to-source
Ruby-to-Javascript compiler. It is similar to
Coffeescript in that it allows a web developer to
write her client code in something nicer than JS,
which then compiles to JS.
Unlike Coffeescript, Opal code needs a runtime to
run.
Opal
18. So, what is our flow for developing isomorphic
Ruby? It ships as a gem containing both Ruby code
and JS code.
Development Flow
20. Truth be told, I didn’t have the time to quite get
steps 4 and 5 above to work, but in theory it should
be like this :)
Now, this seems like a lot. But steps 2-6 are done for
us automatically using the amazing Guard gem, so
all we have to do is write Ruby code + Ruby specs.
Development Flow
22. In production, the Pricing Calculator gem is
deployed inside an App Server container (in our
case, Rails), which exposes several APIs.
Runtime Flow
24. One API simply serves the static Opal Pricing
Calculator lib file, which is loaded by the client at
the beginning of a session, and loaded in a JS
Runtime.
On Android, the JS runtime we use is J2V8, on iOS
it’s an SDK Framework called JavascriptCore.
Runtime Flow
28. Generally the server-side calculation is the authority,
and the client-side calculation is a fallback in case
server is not available.
But also, during a taxi ride in some cases we want to
display a constantly updating taxi meter, and don’t
want to make so many calls to the server, so the
client-side library is also used for that.
Runtime Flow
29. So now we got the same code running on server and
on client. Will it produce the same results?
Wait, the capabilities of the execution contexts in
which the code runs might be different. For one thing,
we assumed that the client execution context might
not have any network. What if the Pricing Calculator
needs to make network calls?
Execution Context
30. Different Capabilities of Execution Context
Supplier App
Pricing
Calculator
(JS)
Pricing
Calculator
(Ruby)
Server
Network Call
Network Call
31. To solve this problem, we don’t make any network
calls from the library itself.
Instead, the library assumes that the runtime context
will provide a layer that produces the resources the
library needs.
Execution Context
32. In case of server execution context, it simply makes a
network call to fetch the resources.
In case of client execution context, it pre-fetches data
from the server so that (in some cases) it’s able to
“simulate” a network call without any network.
Execution Context
34. Lessons Learned
● Solution is running in production for several
months
● Opal JS code is reliable and works well
Downsides:
○ Our JS lib weighs ~310Kb (60Kb zipped)
(80% is Opal base runtime)
35. Downsides (continued):
Isomorphic Ruby you write needs to be “Opal-
friendly”:
● Opal’s Ruby stdlib support is still far from
complete (e.g., “Time” has only a few
methods).
● Your Ruby needs to have no external gem
dependencies, since most gems are probably
not Opal-friendly.
36. Downsides (continued):
● Debugging is tough. Production JS exceptions
from minified code are unreadable - need to
write defensive code and raise your own
exceptions!
37. That’s all, Folks!
Max Rozenoer
we’re hiring!maxr@gett.com
This presentation on SlideShare: bit.ly/gett_isomorphic
Notas do Editor
The problems started when we decided to remove a couple letters from the company name. We used to be called GetTaxi, you see - and we provided only on-demand taxis. Then we changed the name to Gett, and now provide many kinds of on-demand services (salads, flowers, pizzas, etc). To make it possible to launch these new services quickly, we did a lot of work in the last year, and part of this work had to do with how each order can be priced, which is what this talk is about.
We package the Pricing Calculator as a gem that ships both Ruby and JS code. Development flow is driven by https://github.com/guard/guard .
Generally, the server-side calculation is the authority, and the client-side calculation is a fallback in case server is not available. However, during a taxi ride in some cases we want to display a constantly updating taxi meter, and don’t want to make so many calls to the server, so the client-side library is also used for that.
On Android, the JS runtime we use is J2V8, on iOS it’s an SDK Framework called JavascriptCore.
Generally, the server-side calculation is the authority, and the client-side calculation is a fallback in case server is not available. However, during a taxi ride in some cases we want to display a constantly updating taxi meter, and don’t want to make so many calls to the server, so the client-side library is also used for that.