Protein v0.7.0 Protein.Client View Source
Calls services in remote systems.
Usage
Here’s how your RPC client module may look like:
defmodule MyProject.RemoteRPC do
use Protein.Client
# 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,
mock_mod: __MODULE__.CreateUserMock
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.RemoteRPC, []),
]
opts = [strategy: :one_for_one, name: MyProject.Supervisor]
Supervisor.start_link(children, opts)
end
end
Having that, you can call your RPC as follows:
alias MyProject.RemoteRPC
alias MyProject.RemoteRPC.CreateUser.{Request, Response}
request = %Request{}
case RemoteRPC.call(request) do
{:ok, response = %Response{}} ->
# do stuff with response
{:error, errors}
# do stuff with errors
end
# ...or assume that a failure is out of the question
response = RemoteRPC.call!(request)
# ...or issue a push to non-responding service (recognized by lack of Response structure)
RemoteRPC.push(request)
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.ClientAPI
: functions for making client requests to remote services
Mocking for tests
Client call mocking is enabled by default for Mix.env == :test
. You can configure it explicitly
via the mocking_enabled
config flag as follows:
config :protein, mocking_enabled: true
You can add a mock module for your specific service to test/support
. The module should be the
mock_mod
on sample above (which by default is a service_mod
with the Mock
suffix). For
example, to mock the service sourced from create_user.proto
on example above, you may implement
the following module:
# test/support/my_project/remote_rpc/create_user_mock.ex
alias MyProject.RemoteRPC.CreateUser.{Request, Response}
defmodule MyProject.RemoteRPC.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
You can define multiple call
clauses in your mock and use pattern matching to create different
output based on varying input.
Mock bypasses the transport layer (obviously), but it still encodes/decodes your request protobuf just as regular client does and it still encodes/decodes the response from your mock. This ensures that your test structures are compilant with specific proto in use.
For non-responding services, mock modules are optional and will be executed only if defined. Otherwise, the client with mocking mode enabled will still encode the request, but then it will silently drop it without throwing an error.