Isomorphic Server/Client Ruby with Opal Max Rozenoer November, 2015
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
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
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
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
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
Problem Server Pricing Calculator Supplier App iOS / Android Price: ???
For this reason, we want to be able to run the Pricing Calculator library locally on the client as a fallback. Problem
Problem Server Pricing Calculator Supplier App iOS / Android Pricing Calculator
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
Solution Isomorphic CodeServer Client Isomorphic code is code that runs on both the server and client.
So, what is our flow for developing isomorphic Ruby? It ships as a gem containing both Ruby code and JS code. Development Flow
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
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
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
Different Capabilities of Execution Context Supplier App Pricing Calculator (JS) Pricing Calculator (Ruby) Server Network Call Network Call
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
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
Runtime Context Layer Pricing Calculator (Ruby) Server Runtime Context Layer Supplier App Pricing Calculator (JS) Runtime Context Layer Simulated network call via pre- loaded data Network Call
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)
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.
Downsides (continued): ● Debugging is tough. Production JS exceptions from minified code are unreadable - need to write defensive code and raise your own exceptions!
That’s all, Folks! Max Rozenoer we’re email@example.com This presentation on SlideShare: bit.ly/gett_isomorphic