Protein v0.7.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:
Protein.RouterAPI
: macros for defining a list of services and transport optionsProtein.ServerAPI
: functions for handling service callsProtein.ClientAPI
: functions for making client requests to remote services
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