Skip to content

Hyperstack Goals, Design Principles, Architecture and Implementation

Mitch VanDuyn edited this page Sep 1, 2019 · 2 revisions

The goal of Hyperstack is to provide a highly productive set of runtime libraries and development tools for implementation of modern web and connected applications.

Hyperstack Design Principles

  • One language, everywhere.
  • Avoid repetitive and boilerplate code.
  • Use convention over configuration.
  • APIs that are internally consistent, and stable over time.
  • APIs that act like existing well known structures.
  • Friction free configuration and development tool chain.
  • Support for test driven development.
  • Good documentation, and a helpful community, both for users, and contributors.
  • A professional, well structured code base, complete test coverage, and automatic continuous integration.
  • Implementation should be as efficient both in speed and space as practical given the above.

Hyperstack Architecture

The Hyperstack Architecture is fundamentally independent of any particular language or framework, but it is easiest to describe in terms of the current key implementation dependencies: Ruby, React, and ActiveRecord.

  • Ruby: In order to meet the Hyperstack goals you need to write code in a single language that can run everywhere. In addition the language has to have enough meta-programming power to build complex but easy to use APIs. Currently the best (perhaps only) language to meet these requirements is Ruby.
  • React: The React javascript library allows UI components to be declared in a compact declarative manner, and allows components to communicate via state interactions using the Observer Pattern (more on this later.) Hyperstack includes a DSL that allows React components to be described as Ruby code (rather than React JSX.) A side effect of the choice of React is that it allows Hyperstack applications to directly use the thousands of existing React component libraries.
  • ActiveRecord: Any non-trivial application needs a way to persist and access data to a database. ActiveRecord provides a well understood ORM for doing this directly in Ruby code.

Each of the above could be replaced by some other technology. For example Ruby could be replaced by some other language that could target browser, server, desktop and mobile environments, and had sufficient meta-programming capability to build effective DSLs. React could be replaced by other implementations, and ActiveRecord could be replaced by other ORMs or non-sql ODM.

Clients, Servers and Isomorphic code

As a practical matter the execution environment of a Hyperstack application consists of a single central Server, and multiple remote Clients. The clients may be web browsers, desktop or mobile applications. However within the Hyperstack application, as much as practical, the programmer is allowed to ignore this distinction. This is achieved by making code Isomorphic, that is to be able to run on Server, Client or both.

This is the key to reducing boiler plate and redundant code. Isomorphic code takes three main forms in a Hyperstack application:

  • Isomorphic Rendering: The UI is described as a series of components. When run in the server environment the result is the initial HTML page that is sent to the client replacing for example Rails views. The same code when run on the client will continuously update as the system state changes. Thus rather than have (for example) a Rails view describing the initial page to display, and then having a second (typically complex) set of client code to keep the display updated, this is replaced with one piece of code (written in Ruby) that may be fewer lines of code than the Rails view by itself.

  • Isomorphic Operations: Hyperstack provides several classes of Operations that can be used to create Service Objects. These Operations my be designed to run on the Server, on the Client or both, depending on the needs of the application. Where the Operation actually runs is invisible to the code invoking the Operation. This eliminates a great deal of boiler plate code, and the need for APIs and controllers to communicate between the client and server. It also allows common algorithms to be shared on the client and server.

  • Isomorphic Models: Hyperstack provides an ActiveRecord compatible ORM that runs on the client, and is automatically synchronized with the the ActiveRecord data on the server. Thus the exact same code to describe and access models can be used on the client and server. There is no need to build and maintain complex APIs to access and persist server side data from the client.

The Observer Pattern

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

The observer pattern helps Hyperstack achieve its goals in number of ways:

  • It allows one-to-many dependencies and communication between objects. The objects may components of the UI, data base records distributed between client and server, or the clients and server itself.
  • It keeps state automatically synchronized across an open-ended number of dependent objects.
  • As much as practical not only is notification, but also subscribing (and un-subscribing) are automatic. All this allows a great deal of reduction of boiler plate code over traditional systems.

State

At the core of the client side code is the state management system which allows the observer pattern to be easily used within a Hyperstack application.

The state system allows any Ruby object to be observable, and allows observation and mutation of state to be managed through simple to use system primitives.

Components

The client side UI is built out of components which are small chunks of declarative code describing some region of the UI. Under the hood Hyperstack components are implemented as React components.

Components interact with each other and the outside world by receiving events, and observing state changes in other objects. When something changes a component will update its internal state, which will cause the component to re-render and re-display. The underlying React system will update only the portions of the display effected by the state change.

Using isomorphic pre-rendering, the component tree may also be rendered on the server and delivered to the client as ready to display HTML code, saving the need to define extra views for the initial page load.

Access Control and Policies

Through Isomorphic Models, persisted data access can be easily and transparently distributed between the server and clients. The control of who and when data can be accessed is governed by a set of regulations described in Policy classes. Regulations, for example, define which users can see what data, who can create data, and who can modify or delete it. Access to Operations is controlled internal to each operation, by interacting with a special acting_user parameter.

The Hyperstack Gems

Currently Hyperstack is realized as set of cooperating Ruby Gems. The Ruby gems that make up the 'stack are as follows:

  • Hyperstack::State: Observer Pattern state management with automatic subscription to event streams
  • Hyperstack::Component: DSL to describe React components in Ruby
  • Hyperstack::Operation: Classes to build promise based service objects that are runnable "across the wire"
  • Hyperstack::Model: An ORM based on ActiveRecord that allows direct access to server side data on the client.

Each gem in the stack depends on the gems below it, and in simple systems the upper levels can be omitted if unused.

In addition there are the following Hyperstack development support gems:

  • hyper-spec: Adds additional features to rspec to allow for isomorphic testing
  • hyper-trace: Simple way to get debugging trace information

Finally there is an integrated i18n package that works with the Rails i18n gem.