Common API Documentation

JSON Response Builder

Building Successful JSON Response

include TreatanyoneCommonApi::JsonResponseBuilder

def index(event:,context:)
  posts = Post.all
  successful_message(posts)
end

Building Error JSON Response

When handling errors, this will also log it to CloudWatch logs, useful when debugging errors.

def index(event: context:)
  #...
rescue => e
  error_handling(:internal_server_error, e.message)
end

Supported HTTP status codes

  • :continue
  • :switching_protocols
  • :processing
  • :early_hints
  • :ok
  • :created
  • :accepted
  • :non_authoritative_information
  • :no_content
  • :reset_content
  • :partial_content
  • :multi_status
  • :already_reported
  • :im_used
  • :multiple_choices
  • :moved_permanently
  • :found
  • :see_other
  • :not_modified
  • :use_proxy
  • :(unused)
  • :temporary_redirect
  • :permanent_redirect
  • :bad_request
  • :payment_required
  • :forbidden
  • :not_found
  • :method_not_allowed
  • :not_acceptable
  • :proxy_authentication_required
  • :request_timeout
  • :conflict
  • :gone
  • :length_required
  • :precondition_failed
  • :payload_too_large
  • :uri_too_long
  • :unsupported_media_type
  • :range_not_satisfiable
  • :expectation_failed
  • :misdirected_request
  • :unprocessable_entity
  • :locked
  • :failed_dependency
  • :too_early
  • :upgrade_required
  • :precondition_required
  • :too_many_requests
  • :request_header_fields_too_large
  • :unavailable_for_legal_reasons
  • :internal_server_error
  • :not_implemented
  • :bad_gateway
  • :service_unavailable
  • :gateway_timeout
  • :http_version_not_supported
  • :variant_also_negotiates
  • :insufficient_storage
  • :loop_detected
  • :bandwidth_limit_exceeded
  • :not_extended
  • :network_authentication_required

Strong Parameters

Same strong parameters came from Rails version 6. You need to use set_params method to correctly map hash coming from request payload.

Example Usage:

def handler(event:, context:)
  set_params(event, root_key: :task)

  task = Task.new(task_params)
end

#...

private

def task_params
  _params.require(:task).permit(:client_id, :due_date, :task_type, :details, :practitioner_id, :active)
end

Handler

The advantage of using this handle method to create the greet method is that, handle transforms the event object to a Request object for easier handling of data. It also includes rescue blocks, that catches common errors and exceptions, and handle appropriate responses so you dont have to explicitly define rescue clauses when creating handler methods.

Example:

serverless.yml:

functions:
  sampleFunction:
    handler: sample_handler.SampleHandler.greet
    events:
    - http: GET /hello

sample_handler.rb:

module SampleHandler
  include TreatanyoneCommonApi::Handler

  handle :greet do |request, context|
    puts "Hello #{request.params[:name]}"
  end
end

Terminal:

 $ sls invoke local -f sampleFunction -d '{"body": {"name":"Sam Paul"} }'
 $ Hello Sam Paul

Kinesis Data Stream Publish Event

Created a standardized way of publishing events to Kinesis Data Stream

task = Task.first
# This will be equivalent to current_user or the current session
user_id = User.first.id
# Additional data related to event
data = { foo: "bar" }
client = TreatanyoneCommonApi::Kinesis.new("TaskCreated", task, user_id, {
  data: data,
  # The Domain type of event (e.g.: Task)
  aggregate_type: "Task"
})

# Sends the event to Kinesis Data Stream for processing
client.publish_event

Lambda Invocation

Created a standardized way of invoking lambda functions through SDK

As a helper method:

module ProviderBff
  module Resolvers
    class SomeType
      include TreatanyoneCommonApi::Lambda
      #...
      def resolve
        invocation_response = invoke_function(
          # Lambda ARN or Function Name
          function: "arn:aws:lambda:us-west-2:xxxxxxxx:function:some-service-function-name",
          payload: JSON.generate({ foo: "bar" })
        )
       # Do something with payload_data
       payload_data = JSON.parse(invocation_response.payload.read)
       #...
      end
    end
  end
end

As a class instance method (more configuration available):

# Region defaults to `us-west-2`
client = TreatanyoneCommonApi::Lambda::Client.new(region: "us-west-2")
client.invoke(
  function_name: "LAMBDA_ARN_OR_NAME",
  invocation_type: "RequestResponse",
  log_type: "None",
  client_context: "user_1234",
  payload: JSON.generate({foo: "bar"}),
  qualifier: nil
)

lambda_payload = client.lambda_payload

Invocation Types

  • Event
  • RequestResponse (default)
  • DryRun

Log Types

  • None (default)
  • Tail

Using Kinesis Publish and Invocation

It is important to always Publish your events before invoking a function, this will keep all events recorded via Audit Trail.

Example:

module ProviderBff
  module Resolvers
    class SomeType
      include TreatanyoneCommonApi::Lambda
      #...
      def resolve(id)
        task = Task.find(id)
        # This will be equivalent to current_user or the current session
        user_id = current_user.id
        # Additional data related to event
        data = { foo: "bar" }
        client = TreatanyoneCommonApi::Kinesis.new("TaskCreated", task, user_id, {
          data: data,
          # The Domain type of event (e.g.: Task)
          aggregate_type: "Task"
        })
        # Sends the event to Kinesis Data Stream for auditing
        client.publish_event

        invocation_response = invoke_function(
          # Lambda ARN or Function Name
          function: "arn:aws:lambda:us-west-2:xxxxxxxx:function:some-service-function-name",
          payload: JSON.generate(data)
        )
       # Do something with payload_data
       payload_data = JSON.parse(invocation_response.payload.read)
       #...
      end
    end
  end
end

Publishing gem

Added commands for local dev/testing

$ bin/setup

Starts up a docker container that runs a geminabox instance (local rubygems). Access the page through http://localhost:9292

$ bin/console

Starts a terminal session with treatanyone_common_api gem loaded for easier debugging and testing of functionalities.

$ rake publish:local

Build and publish gem to http://localhost:9292. Test with other repo (locally) by updating gemfile

# Another repo's Gemfile that uses treatanyone_common_api
source 'http://localhost:9292' do
  gem 'treatanyone_common_api', '>= 0.4.1'
end


$ gem uninstall treatanyone_common_api  # => uninstall previous version from remote
$ bundle install # Will install version from http://localhost:9292

Sending real-time notifications from backend to Websocket server:

Make sure to set ENV["WEBSOCKET_NOTIFY_ARN"] on your serverless config to be able to connect to Websocket server

Parameters:

user_uuids UUID of users to receive notifications (Users should be connected to the Websocket server to receive notifications) notification Hash containing metadata of the notification

include TreatanyoneCommonApi::Notifier

notify_users(user_uuids: ["ecfa7091-aab0-44fe-ba6d-9cb0b11012xx"], notification: {"message": "Hello World! This is a real time notification"})


Publishing SNS Events

require 'treatanyone_common_api'

module EspressoHandler
  extend TreatanyoneCommonApi::Handler

  Publisher = TreatanyoneCommonApi::Pubisher   # Alias. Optional
  Pubisher.topic_arn    = ENV['SNS_TOPIC_ARN'] # SNS Topic arn
  Pubisher.event_object = 'EspressoModel'      # Prefer UpperCamelCase format

  handle :create do |event, context|
    beans = Bean.find_by_type(event.params[:bean_type])
      .grind(:espresso)
      .scoop

    espresso = beans.brew(:double)

    publish_response = Pubisher.created(espresso.as_json)
    render(201, json: espresso)
  end

  handle :update do |event, context|
    # ...
    publish_response = Pubisher.updated(espresso.as_json)
    render(200, json: espresso)
  end

  handle :delete do |event, context|
    # ...
    publish_response = Pubisher.deleted(espresso.as_json)
    render(200, json: espresso)
  end
end