[#8] plugin mechanism for custom handlers
This commit is contained in:
parent
e2ff4a48c7
commit
a1fde7a574
|
@ -1,30 +1,3 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
use Mix.Config
|
||||
|
||||
# This configuration is loaded before any dependency and is restricted
|
||||
# to this project. If another project depends on this project, this
|
||||
# file won't be loaded nor affect the parent project. For this reason,
|
||||
# if you want to provide default values for your application for
|
||||
# 3rd-party users, it should be done in your "mix.exs" file.
|
||||
|
||||
# You can configure your application as:
|
||||
#
|
||||
# config :poa_backend, key: :value
|
||||
#
|
||||
# and access this configuration in your application as:
|
||||
#
|
||||
# Application.get_env(:poa_backend, :key)
|
||||
#
|
||||
# You can also configure a 3rd-party app:
|
||||
#
|
||||
# config :logger, level: :info
|
||||
#
|
||||
|
||||
# It is also possible to import configuration files, relative to this
|
||||
# directory. For example, you can emulate configuration per environment
|
||||
# by uncommenting the line below and defining dev.exs, test.exs and such.
|
||||
# Configuration from the imported file will override the ones defined
|
||||
# here (which is why it is important to import them last).
|
||||
#
|
||||
# import_config "#{Mix.env}.exs"
|
||||
import_config "#{Mix.env}.exs"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
use Mix.Config
|
||||
|
||||
# configuration for custom handlers. The format is {custom_handler_name, module, args}
|
||||
config :poa_backend,
|
||||
:custom_handlers,
|
||||
[
|
||||
]
|
|
@ -0,0 +1,7 @@
|
|||
use Mix.Config
|
||||
|
||||
# configuration for custom handlers. The format is {custom_handler_name, module, args}
|
||||
config :poa_backend,
|
||||
:custom_handlers,
|
||||
[
|
||||
]
|
|
@ -0,0 +1,7 @@
|
|||
use Mix.Config
|
||||
|
||||
# configuration for custom handlers. The format is {custom_handler_name, module, args}
|
||||
config :poa_backend,
|
||||
:custom_handlers,
|
||||
[
|
||||
]
|
|
@ -1,14 +1,13 @@
|
|||
defmodule POABackend.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
# List all child processes to be supervised
|
||||
children = [
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
supervisor(POABackend.CustomHandler.Supervisor, [])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one, name: POABackend.Supervisor]
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
defmodule POABackend.CustomHandler do
|
||||
alias POABackend.Protocol.Message
|
||||
@moduledoc """
|
||||
A Custom Handler is responsible of handling data sent from Agents (i.e. REST over HTTP, WebSockets...) "speaking" the POA Protocol.
|
||||
|
||||
The main responsability is getting calls from Agents, transform the data into a [POABackend.Protocol.Message](POABackend.Protocol.Message.html#content)
|
||||
Struct and sending it to the receivers.
|
||||
|
||||
### Writing your custom Handler
|
||||
|
||||
You must to _use_ the POABackend.CustomHandler module. That will requires you to implement the function `child_spec/1` which will be
|
||||
called from the `POABackend.CustomHandler.Supervisor` and it must returns the child spec for the process you are going to spawn.
|
||||
|
||||
defmodule MyHandler do
|
||||
use POABackend.CustomHandler
|
||||
|
||||
def child_spec(options) do
|
||||
Plug.Adapters.Cowboy.child_spec(scheme: options[:scheme], plug: POABackend.CustomHandler.Rest.Router, options: [port: options[:port]])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
In this example we are initializing our CustomHandler for REST requests using the Cowboy Plug and defining the endpoints in the `POABackend.CustomHandler.Rest.Router`
|
||||
module.
|
||||
|
||||
### Configuring the handlers in the config file
|
||||
|
||||
So far we have created a Custom Handler but we didn't tell `poa_backend` to start it. In order to do it we have to define the new Handler in
|
||||
the config file.
|
||||
|
||||
config :poa_backend,
|
||||
:custom_handlers,
|
||||
[
|
||||
{:rest_custom_handler, POABackend.CustomHandler.Rest, [port: 4002]}
|
||||
]
|
||||
|
||||
Inside the `:custom_handlers` list we define the handlers we want to start. Each Handler is defined in a triple tuple where the first argument
|
||||
is the id for that handler, the second one is the Elixir module which implements the CustomHandler behaviour and the third one is a list for arguments
|
||||
which will be passed to the `child_spec/1` function as a parameter
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
This function will be called from the `POABackend.CustomHandler.Supervisor` in order
|
||||
to get the child specification for start the custom handler process.
|
||||
"""
|
||||
@callback child_spec(options :: list()) :: :supervisor.child_spec()
|
||||
|
||||
defmacro __using__(_opts) do
|
||||
quote do
|
||||
@behaviour POABackend.CustomHandler
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function dispatches the given Message to the appropiate receivers based on the Data Type.
|
||||
|
||||
The mapping between Data Types and Receivers is done in the config file.
|
||||
"""
|
||||
@spec send_to_receivers(Message.t) :: :ok
|
||||
def send_to_receivers(%Message{} = _message) do
|
||||
require Logger
|
||||
# this will be implemented when working with the receivers
|
||||
Logger.warn("To be implemented")
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
defmodule POABackend.CustomHandler.Supervisor do
|
||||
@moduledoc false
|
||||
|
||||
def start_link do
|
||||
Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
|
||||
end
|
||||
|
||||
def init(:ok) do
|
||||
require Logger
|
||||
# create the children from the config file
|
||||
custom_handlers = Application.get_env(:poa_backend, :custom_handlers)
|
||||
|
||||
children = for {name, module, options} <- custom_handlers do
|
||||
spec = apply(module, :child_spec, [options])
|
||||
|
||||
Logger.info("Custom handler #{name} started.")
|
||||
|
||||
spec
|
||||
end
|
||||
|
||||
opts = [strategy: :one_for_one]
|
||||
Supervisor.init(children, opts)
|
||||
end
|
||||
end
|
|
@ -35,13 +35,13 @@ defmodule POABackend.Protocol.Message do
|
|||
That keeps all the message data and metadata
|
||||
"""
|
||||
@type t :: %__MODULE__{
|
||||
agent_id: String.t(),
|
||||
agent_id: String.t() | nil,
|
||||
receivers: [atom()],
|
||||
data_type: POABackend.CustomProtocol.DataType.t(),
|
||||
message_type: POABackend.CustomProtocol.MessageType.t(),
|
||||
data_type: POABackend.CustomProtocol.DataType.t() | nil,
|
||||
message_type: POABackend.CustomProtocol.MessageType.t() | nil,
|
||||
assigns: %{atom() => any()},
|
||||
peer: {:inet.ip_address(), :inet.port_number()},
|
||||
data: Map.t()
|
||||
peer: {:inet.ip_address(), :inet.port_number()} | nil,
|
||||
data: Map.t() | nil
|
||||
}
|
||||
|
||||
@doc """
|
||||
|
|
8
mix.exs
8
mix.exs
|
@ -19,7 +19,7 @@ defmodule POABackend.MixProject do
|
|||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger, :cowboy, :plug],
|
||||
extra_applications: [:logger],
|
||||
mod: {POABackend.Application, []}
|
||||
]
|
||||
end
|
||||
|
@ -27,9 +27,6 @@ defmodule POABackend.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:cowboy, "~> 1.0.0"},
|
||||
{:plug, "~> 1.0"},
|
||||
|
||||
# Tests
|
||||
{:credo, "~> 0.9", only: [:dev, :test], runtime: false},
|
||||
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
|
||||
|
@ -51,6 +48,9 @@ defmodule POABackend.MixProject do
|
|||
POABackend.Protocol.Message,
|
||||
POABackend.Protocol.MessageType,
|
||||
POABackend.Protocol.DataType
|
||||
],
|
||||
"Custom Handler": [
|
||||
POABackend.CustomHandler
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -1,8 +1,10 @@
|
|||
%{
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"},
|
||||
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"credo": {:hex, :credo, "0.9.2", "841d316612f568beb22ba310d816353dddf31c2d94aa488ae5a27bb53760d0bf", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"},
|
||||
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [], [], "hexpm"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"excoveralls": {:hex, :excoveralls, "0.8.2", "b941a08a1842d7aa629e0bbc969186a4cefdd035bad9fe15d43aaaaaeb8fae36", [], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
defmodule CustomHandlerTest do
|
||||
use ExUnit.Case
|
||||
|
||||
defmodule MyCustomHandler do
|
||||
use POABackend.CustomHandler
|
||||
use GenServer
|
||||
|
||||
# CustomHandler Callbacks
|
||||
|
||||
def child_spec(options) do
|
||||
import Supervisor.Spec
|
||||
|
||||
worker(__MODULE__, [options[:caller_pid]])
|
||||
end
|
||||
|
||||
# GenServer Callbacks
|
||||
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init(caller_pid) do
|
||||
{:ok, caller_pid}
|
||||
end
|
||||
|
||||
def handle_info(:hello, caller_pid) do
|
||||
|
||||
# simulate we send something to the receivers
|
||||
|
||||
POABackend.CustomHandler.send_to_receivers(POABackend.Protocol.Message.new())
|
||||
|
||||
send(caller_pid, :world)
|
||||
{:noreply, caller_pid}
|
||||
end
|
||||
end
|
||||
|
||||
test "initialize a custom handler in the supervisor" do
|
||||
|
||||
Application.stop(:poa_backend)
|
||||
Application.put_env(:poa_backend, :custom_handlers, [{:my_ch, MyCustomHandler, [caller_pid: self()]}])
|
||||
Application.ensure_all_started(:poa_backend)
|
||||
|
||||
# sending a message and wait for the reply
|
||||
|
||||
CustomHandlerTest.MyCustomHandler
|
||||
|> Process.whereis()
|
||||
|> send(:hello)
|
||||
|
||||
assert_receive :world, 20_000
|
||||
|
||||
Application.stop(:poa_backend)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue