Protein v0.10.0 Protein.Server View Source

Responds to service calls from remote systems.

Usage

Here’s how your RPC server module may look like:

defmodule MyProject.MyRPC do
  use Protein.Server

  # then, declare services with a convention driven config
  proto :create_user

  # ...or with custom proto file name (equivalent of previous call above)
  proto Path.expand("./proto/create_user.proto", __DIR__)

  # ...or with a completely custom config (equivalent of previous calls above)
  service proto: [from: Path.expand("./proto/create_user.proto", __DIR__)],
          service_name: "create_user",
          proto_mod: __MODULE__.CreateUser
          request_mod: __MODULE__.CreateUser.Request,
          response_mod: __MODULE__.CreateUser.Response,
          service_mod: __MODULE__.CreateUserService
end

Make sure to add it to the supervision tree in application.ex as follows:

defmodule MyProject.Application do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec

    children = [
      supervisor(MyProject.Repo, []),
      supervisor(MyProject.Web.Endpoint, []),
      # ...
      supervisor(MyProject.MyRPC, []),
    ]

    opts = [strategy: :one_for_one, name: MyProject.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Macros and functions

By invoking use Protein.Client, you include the following in your client module:

The inclusion of Protein.ClientAPI basically means that every Protein server also includes its own client. This gives a free, useful tool for calling the server. It comes at no cost since client side of things shares the transport and service config with the server. It also won’t consume extra resources and won’t spawn connection processes until the first client call.

Serving

By default, the actual server process is not started. In order to start it, you can either invoke the Mix.Tasks.Protein.Server task or set the serve config flag to true.

Defining services

After implementing the server and adding services/protos to it, you need to implement a module set via the service_mod option. The service should implement a call/1 function that consumes the request structure. In case of responding services (recognized by presence of Response structure), the call/1 function must either return one of supported resolution or rejection values, as presented on the example below. In case of non-responding services (recognized by lack of Response structure), the return value doesn’t matter.

# test/support/my_project/my_rpc/create_user_service.ex

alias MyProject.MyRPC.CreateUser.{Request, Response}

defmodule MyProject.MyRPC.CreateUserMock do
  # with default response
  def call(request = %Request{) do
    :ok
  end

  # ...or with specific response
  def call(request = %Request{}) do
    {:ok, %Response{}}
  end

  # ...or with default error
  def call(request = %Request{}) do
    :error
  end

  # ...or with specific error code
  def call(request = %Request{}) do
    {:error, :something_happened}
  end

  # ...or with specific error message
  def call(request = %Request{}) do
    {:error, "Something went wrong"}
  end

  # ...or with error related to specific part of the request
  def call(request = %Request{}) do
    {:error, {:specific_arg_error, struct: "user", struct: "images", repeated: 0}}
  end

  # ...or with multiple errors (all above syntaxes are supported)
  def call(request = %Request{}) do
    {:error, [
      :something_happened,
      "Something went wrong",
      {:specific_arg_error, struct: "user", struct: "images", repeated: 0}
    ]}
  end
end