Endpoint

  • Last updated 25 Jun 20

The endpoint gem is the missing link between your routing framework (such as Rails or Hanami) and your business code, which could be an operation, a workflow invocation, or just some vanilla Ruby/Rails code component. The endpoint gem does work best with a TRB-component but is not limited to it.

These docs, as of early July 2020, are still work-in-progress, and so is the endpoint gem. Bear with us, in a few weeks this will be stable and production-ready.

Nevertheless, we need your help. Endpoint is basically a collection of best-practices, so input how you are/would be using it is needed!

Overview

Notes: before_action :authenticate_api!, Rack-mess, respond_to? The idea is to maintain as little code as possible while staying predictive and no guessing

An endpoint links your routing with your business code. The idea is that your controllers are pure HTTP routers, calling the respective endpoint for each action. From there, the endpoint takes over, handles authentication, policies, executing the domain code, interpreting the result, and providing hooks to render a response.

In a Rails controller, a controller action could look as follows.

class DiagramsController < ApplicationController
  endpoint :create, [:is_logged_in?, :can_add_diagram?]

  def create
    endpoint :create do |ctx|
      return redirect_to diagram_path(ctx[:diagram].id)
    end

    render :form
  end
end

This is just an example, there are many ways to use an endpoint, to an extent where it takes over rendering for you, etc etc.

An API controller action, where the rendering is done automatically in the adapter, could look much simpler.

class API::V1::DiagramsController < ApplicationController
  endpoint :create, [:is_logged_in?, :can_add_diagram?]

  def create
    endpoint :create
  end
end

Architecture

Keep in mind that there’s one endpoint instance per controller action. Each of your application’s function owns its very own endpoint that is configured/wired for that very case.

An endpoint is separated into two parts: protocol and adapter.

The adapter is where authentication, policy checks, and eventually your domain logic happen. While the authentication part is specific to the environment (it might deserialize a user from a web cookie, or parse an XML header for authentication details)(1), the remaining steps are independent. The protocol communicates its outcome through well-defined termini. (1 this might be changed so that protocols are environment-independent)

The failure terminus is (currently!) interpreted as “invalid data”, or a 422, since most operations will end in a failure in that case.

Your actual code will replace the domain_activity step. If your operation doesn’t have a not_found output, this can be manually wired, so your endpoint can also run “legacy operations”.

The adapter knows nothing about what happened in the protocol. Its job is to prepare everything for the response. It’s up to you whether or not your adapter already renders a document or returns a HTTP code.

Currently, we have very simple adapters, and one that uses Representable/Roar to render documents automatically.

  • controllers are configuration containers for input and rendering behavior (failure case, success!)

  • Endpoints are just a suggestion following a best-practice. You can and should rewire them using the Wiring API if you find the existing pattern not suiting your needs.
  • The separation of protocol and adapter makes it super simple to reuse adapters for many protocols.
  • Certain steps, like authentication can easily be replaced with a subprocess from authentication gems, like Tyrant or Devise. Again, you can use the Wiring API.

    endpoint =
      Trailblazer::Endpoint.build(
        domain_activity: activity,
        protocol: Trailblazer::Endpoint::Protocol,
        adapter:  Trailblazer::Endpoint::Adapter::Web,
    ) do
      # this block is executed in the context of the {Protocol}
    
      step Subprocess(Tyrant::Auth::Cookie), replace: :authenticate, inherit: true # replace the template's authentication with yours
      {}
    end