Skip to content

Rethinking HyperStack

Mitch VanDuyn edited this page Aug 2, 2018 · 5 revisions

Hyperloop evolved as layers of code, where each layer had some isomorphic capabilities.

HyperComponents can be prerendered, and so in that sense were isomorphic because they allowed the initial page view (typically handled by a Rails view for example) and the on going UI display to be handled by a common set of component classes.

HyperModels are representations of ActiveRecord models on the client, and so are the best example of what isomorphic code can do for you. The same constructs (for example Todo.all.count) work the same on the client and the server.

HyperOperations (via ServerOps) allow us to hide where code actually runs from the greater application. So invoking an operation is transparent to where the execution takes place.

Because of this we have always thought of the client and server functionality as being closely coupled, and have packaged the code this way.

Another reason for this, is that initially we used Rails and sprockets and features built into Opal to manage the isomorphic code modules. Thus one gem like hyper-operation magically had everything you needed for client and server.

However this view is no longer serving us well. We don't want to change any isomorphic coolness, but we need a better way to explain how things work, and an easier way to maintain the code.

So in HyperStack we are changing how we think about these features, and how they work together.

The core of HyperStack is the HyperStack.js NPM module. This is a complete client-side framework built on top of React. It gives you

  • HyperComponents - React component classes written in Ruby;
  • HyperStores - classes that wrap global states;
  • HyperOperations - a way to group logical pieces of business logic separate from the UI;
  • and HyperModels - a way to deal with data as classes with attributes and relationships.

Here is the thing: that stack right there is pretty nice, and for many projects especially those dealing with pre-existing APIs, it's all you need or want. It also means we are no longer dependent on Sprockets or even Rails, and we can use the features of the webpack tool chain like tree-shaking to reduce code size etc.

For example, assume you have some API that provides you with a simple set of CRUD end-points:

  • You can model the data as a set of HyperModel classes,
  • you can then create a couple of HyperOperations to take care of loading, and updating the server
  • and you can then write your Component classes in terms of your models and operations.

This is great, and we think it's going to be a much easier way to bring people along the HyperStack path, but how do we get back our cool isomorphic features?

To implement the isomorphic stuff we have to talk to some specific server or technology, and so we now expect to have some dependency on that technology, but because HyperStack.js (the client side of things) is independent of any server technology its now easy for us to plug in various "isomorphic handlers" (anybody got a better name.)

So for example for HyperStack 1.0 we will ship with a hyper-rails gem that bundles together 3 internal gems: hyper-component-rails, hyper-operation-rails, and hyper-model-active-record. The hyper-component-rails gem provides the ability to mount and prerender components from views and controllers, the hyper-operation-rails gem handles authentication, execution, and broadcasting of remote calls to ServerOps (a subclass of Operations), and the hyper-model-active-record gem handles persisting model data to active-record models. Note that because the HyperModel implementation depends on HyperOperations, it is independent of Rails and only dependent on the ActiveRecord ORM.

Hopefully, in the near future, we can remove the dependence on Rails, and instead have the server side be a rack application. Now we would have a hyper-component-rack, and hyper-operation-rack gem and hyper-model-active-record is all ready to go. Likewise, we can create a hyper-model-neojs gem with persists the model with NeoJS.

The specific contracts that each component on the server has to fill are narrow and well defined. For component rendering its a set of data-tags that links the prerendered components to the client rendered components; For operations its a single API end point plus the ability to push a single message type outbound. For models its the implementation of a couple of ServerOps, that read and transmit data. All can be well defined, and easy to make various "isomorphic handlers" for various server environments.

By first understanding HyperStack as a complete client side framework, and then optionally tightly integrating prerendering, remote operation execution, and model persistance, we give a much easier path to understanding.

Clone this wiki locally