Skip to content
This repository has been archived by the owner on Oct 19, 2018. It is now read-only.

Hyperloop elevator (give me at least 30 stories please) pitch

Mitch VanDuyn edited this page Jan 9, 2017 · 1 revision

@catmando, @barriehadfield

Hyperloop elevator pitch

Hyperloop is an Isomorphic Web Application Framework based on Components, Operations, Models and Policies. Hyperloop wants you to enjoy quickly building modern interactive web applications. Hyperloop includes full access to the entire Rails ecosystem and front-end JavaScript libraries like React and jQuery - all using one great language - Ruby.

Hyperloop is here so you can write code that is directed toward solving the user's needs in the most straightforward manner, without redundant code, unnecessary APIs, or artificial separation between client and server.

This guide will take you through all the main Hyperloop architectural layers.

Models, ActiveRecord and Other Rails Gems

Hyperloop uses Rails ActiveRecord for data persistence. This allows easy integration with existing Rails apps. Other Rails gems such as ActionMailer can also be used in the standard fashion. This forms the inner core of your Hyperloop application.

Operations

Hyperloop recommends that only scopes, relations, and validations are described in model classes. All business logic can be encapsulated in reusable isomorphic Operations that do not complicate your Models or Components. Each Operation is a self-contained piece of logic that does one simple thing.

class AddToActingUsersWatchList < Hyperloop::Operation
  # Add a book to the current acting_user's watch list, and
  # send an initial email about the book.

  param :book, type: Book
  # Operations have access to the current 'acting_user'
  # so we do not need to pass it as a parameter.

  def execute
    return if acting_user.watch_list.include? params.book
    WatchListMailer.new_book_email\
      WatchList.create(user: acting_user,  book: params.book) 
  end
end

Pretty simple. Typically code like this might be found in a controller which makes it hard to reuse or in a model which makes maintenance difficult when business logic changes. Placing it in its own Operation makes it easy to maintain, reuse and test.

Of course, Operations can invoke other Operations:

class AddBookToBasket < Hyperloop::Operation
  # Add a book to the basket and add to users watchlist
  param :book, type: Book

  def execute
    acting_user.basket << book 
    AddToActingUsersWatchList(book: params.book)
  end
end

Components

You build your UI using React Components described as Ruby classes. Within your Components, you can display other components, change state, access models, or communicate with third party APIs. Typically you will want to use Operations to encapsulate these activities. Here is a simple example using our AddBookToBasket operation.

class BookList < React::Component::Base
  # Display each book in our catalog unless it's already in the cart basket.
  # When the user clicks on a book, add it to the Basket.
  render(UL) do
    Book.all.each do |book|
      LI { "Add #{book.name}" }.on(:click) do
        AddBookToBasket(book: book)
      end unless acting_user.basket.include? book
    end
  end
end

Notice how our component directly scopes the Book model and reads the name attribute. Models are dynamically synchronized to all connected and authorized clients using ActionCable, pusher.com or polling. The synchronization is completely automatic and magical to behold.

Authorization Policies

While communication between the client and server is automatic it does need to be authorized. This is accomplished by regulations which can be grouped into pundit style Policy classes. This allows your access rules to be described separately from your Models and Operations.

class BookPolicy
  regulate_broadcast do |policy|
    # allow the entire application to see all book attributes
    # except the 'unit_cost'.
    policy.send_all_but(:unit_cost).to(Application)
  end
  # but only acting_user's who are admins can make changes to Books
  allow_change(on: [:create, :update]) { acting_user.admin? }
end

class OperationPolicy
  # We need AddToActingUsersWatchList to execute on the server but
  # it can be invoked from the client if there is a logged in user.
  AddToActingUsersWatchList.execute_on_server { acting_user }
end

Summary: The Components, Operations, Models and Policies (COMP) Architecture

This structure is analogous to and replaces the older MVC architecture, but with a more logical and finer grained division of labor.

  • Components describe how the UI will display the current application state and how it will handle user actions. Using React Components automatically rerender parts of the display as state changes due to local or remote activities.
  • Operations encapsulate activities. In an MVC architecture operations end up either in controllers, models or some other secondary construct such as service objects, helpers, or concerns. Here they are first class objects.
  • Models can now focus on one thing and that is the structure of the data as it is persisted. Any business logic is moved to Operations.
  • Policies keep authorization logic out of models, and operations, and also allows the isomorphic transport mechanism to know what and when to communicate between client and server.

The only remaining function of controllers - acting as the HTTP end point - is automatically handled by the Hyperloop framework. Both Models and Operations will automatically work across the wire using the Policies to determine access rights, and reactive Components to update the display as data changes.

Benefits

  • The COMP architecture encapsulates functionality for clean, predictable, testable code
  • One language - Ruby everywhere - reduces complexity and lets developers build solutions quickly
  • Encourages client-side execution for distributed processing and a rich interactive user experience
  • Full power of Rails, React and the entire JavaScript universe
  • Transparent, automatic and secure client-server communication built into Models and Operations
Clone this wiki locally