ismasan / demo-orders-api

Demo orders API to showcase Ruby and hypermedia

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Demo hypermedia API

A demo Sinatra REST API to showcase hypermedia ideas.

https://demo-orders-api.herokuapp.com

Super simple real-time dashboard at https://demo-orders-api.herokuapp.com/dashboard

This is a regular REST-style JSON API, with added links as described in this blog post.

You can issue requests to it directly.

# create an order
curl -H "Content-Type: application/json" -X POST https://demo-orders-api.herokuapp.com/orders

The real-time dashboard should show data being updated.

Ruby client usage

A Hypermedia-aware client can leverage links present in this API's responses to tell you what actions are available.

You can gem install bootic_client, a generic hypermedia client, and configure it to point to this API's Heroku host. The following script:

# hypermdia-console.rb

require 'bootic_client'
require 'bootic_client/strategies/strategy'
config = Struct.new(:api_root).new('https://demo-orders-api.herokuapp.com/')
CLIENT = BooticClient::Strategies::Strategy.new(config)

Can be launched into an IRB session with

irb -r ./hypermedia-console.rb

The CLIENT object is available.

root = CLIENT.root
# => #<BooticClient::Entity props: [] rels: [self, joe, orders, create_order] entities: []>

The root entity lists "rels" (links) available at the root endpoint

You can invoke the listed links to initiate workflows

order = root.create_order
# => #<BooticClient::Entity props: [id, status, total, created_on, updated_on] rels: [self, add_line_item, delete_order, place_order] entities: [line_items]>

The newly created order lists available properties, links and sub-entities.

You can inspect available links

order.rels[:self]
# => #<BooticClient::Relation {"href"=>"https://demo-orders-api.herokuapp.com/orders/6e80354f408b6c4fafc45ef39f6fa241", "name"=>"self"}>

... add line items to an order

order = order.add_line_item(name: 'iPhone 8', price: 100, units: 2)
order = order.add_line_item(name: 'Samsung Galaxy', price: 50, units: 1)
# => #<BooticClient::Entity props: [id, status, total, created_on, updated_on] rels: [self, add_line_item, delete_order, place_order] entities: [line_items]>

... iterate an order's line items

order.line_items.map(&:name)
# => ["iPhone 8", "Samsung Galaxy"]

... Iterates the first page of all orders

root.orders.map &:status
# => ["open", "open", "open", "completed", "completed", "open"]

... Iterate the entire set of orders (enumerate across all pages)

root.orders.full_set.map &:status
# => ["open", "open", "open", "completed", "completed", "open", "open", "open", "complete", ...]

API resources will include different links depending on their current status and capabilities. For example, an open order will include add_line_item and place_order links, but a placed order will not. Clients can use this knowledge to take appropiate actions depending on the absense or presence of links.

if order.can?(:place_order)
  order = order.place_order
end

Testing / dog-fooding

The same client used to consume this type of API can be used to test it.

Using Faraday's Rack adapter, we can configure the API client to talk directly to an in-memory Rack object - this API!, without the need to make actual HTTP requests.

This means we can feature-test hypermedia Rack APIs (Rails, Sinatra, etc) in pretty much the same way we use them.

# configure a client using the Rack adapter, and point it to your rack API class
# for Rspec put this in spec_helper.rb or other support files
def app
  Api
end

def client
  BooticClient::Strategies::Strategy.new(
    ClientConfig.new(
      "http://example.org"
    ),
    access_token: 'abc',
    faraday_adapter: [:rack, app]
  )
end

Now you can use the test client to navigate the API in-process, follow links, etc.

it 'creates orders' do
  root = client.root

  o1 = root.create_order
  o2 = root.create_order

  expect(o1.id).not_to be_nil
  expect(o1.status).to eq 'open'

  expect(root.orders.total_items).to eq 2
  expect(root.orders.map(&:id).sort).to eq [o2.id, o1.id].sort
end

it 'adds items to orders' do
  root = client.root

  order = root.create_order
  order = order.add_line_item(name: 'iPhone 8', price: 100, units: 2)
  order = order.add_line_item(name: 'Samsung Galaxy', price: 50, units: 1)

  expect(order.line_items.size).to eq 2
  expect(order.total).to eq 250
end

Install

git clone git@github.com:ismasan/demo-orders-api.git
cd demo-orders-api
bundle install

Create .env and setup Redis and Pusher credentials

cp env.example .env

Run

bundle exec foreman start
# localhost:5000

About

Demo orders API to showcase Ruby and hypermedia


Languages

Language:Ruby 83.4%Language:HTML 16.6%