{"id":32167901,"url":"https://github.com/ohhi-vn/easy_rpc","last_synced_at":"2026-03-11T04:06:02.818Z","repository":{"id":280234199,"uuid":"941388269","full_name":"ohhi-vn/easy_rpc","owner":"ohhi-vn","description":"Help dev can work easily with rpc in Elixir cluster","archived":false,"fork":false,"pushed_at":"2026-03-08T05:17:05.000Z","size":84,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-08T10:26:01.074Z","etag":null,"topics":["cluster","disport","hex-package","public","rpc"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ohhi-vn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-03-02T06:55:06.000Z","updated_at":"2026-03-02T23:01:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"8a1a443a-edf0-4388-9660-12204999a02c","html_url":"https://github.com/ohhi-vn/easy_rpc","commit_stats":null,"previous_names":["ohhi-vn/easy_rpc"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/ohhi-vn/easy_rpc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ohhi-vn%2Feasy_rpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ohhi-vn%2Feasy_rpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ohhi-vn%2Feasy_rpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ohhi-vn%2Feasy_rpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ohhi-vn","download_url":"https://codeload.github.com/ohhi-vn/easy_rpc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ohhi-vn%2Feasy_rpc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30370399,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T21:41:54.280Z","status":"online","status_checked_at":"2026-03-11T02:00:07.027Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cluster","disport","hex-package","public","rpc"],"created_at":"2025-10-21T15:40:36.234Z","updated_at":"2026-03-11T04:06:02.812Z","avatar_url":"https://github.com/ohhi-vn.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Docs](https://img.shields.io/badge/api-docs-green.svg?style=flat)](https://hexdocs.pm/easy_rpc)\n[![Hex.pm](https://img.shields.io/hexpm/v/easy_rpc.svg?style=flat\u0026color=blue)](https://hex.pm/packages/easy_rpc)\n\n# EasyRpc\n\nA library that makes it easy to wrap a remote procedure call (RPC) as a local function.\nEasyRpc uses Erlang's `:erpc` module under the hood and adds retry, timeout, and error-handling support on top.\n\nEach function can carry its own options, or inherit global options declared at the module level.\nEasyRpc works seamlessly with [ClusterHelper](https://hex.pm/packages/cluster_helper) for dynamic Elixir clusters.\n\n*Note: Collab between human \u0026 AI.*\n\n## Installation\n\nAdd `easy_rpc` to your dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:easy_rpc, \"~\u003e 0.7.0\"}\n  ]\nend\n```\n\n---\n\n## Two Usage Approaches\n\n| | `DefRpc` | `RpcWrapper` |\n|---|---|---|\n| Functions declared | In module via `defrpc` macro | In config file |\n| Node config loaded | At **runtime** per call | At **compile time** |\n| Cluster topology changes | ✅ Picked up automatically | Requires recompile |\n| Best for | Explicit control, dynamic clusters | All-in-config, stable topology |\n\n---\n\n## Usage — `DefRpc` (declarative macros)\n\n### 1. Add node config\n\n```elixir\n# config/config.exs  (or runtime.exs for runtime topology)\nconfig :my_app, :remote_defrpc,\n  nodes: [:\"remote@127.0.0.1\"],\n  # or: nodes: {ClusterHelper, :get_nodes, [:remote_api]},\n  select_mode: :round_robin,\n  sticky_node: true\n```\n\n\u003e `:round_robin` and `:sticky_node` are tracked **per process**.\n\n### 2. Declare functions\n\n```elixir\ndefmodule MyApp.Remote do\n  use EasyRpc.DefRpc,\n    otp_app: :my_app,\n    config_name: :remote_defrpc,\n    module: RemoteNode.Interface,\n    timeout: 1_000\n\n  defrpc :get_data\n  defrpc :put_data, args: 1\n  defrpc :clear, args: 2, as: :clear_data, private: true\n  defrpc :put_data, args: [:name], as: :put_with_retry, retry: 3, sleep_before_retry: 200, timeout: 1_000\nend\n```\n\n**`defrpc` options:**\n\n| Option                | Description                                           |\n|-----------------------|-------------------------------------------------------|\n| `:args`               | Arity as integer, `[]` (zero), or list of named atoms |\n| `:as` / `:new_name`   | Override the generated function name                  |\n| `:private`            | Generate as `defp` (default: `false`)                 |\n| `:retry`              | Override global retry count                           |\n| `:sleep_before_retry` | Milliseconds to wait before each retry (default: `0`) |\n| `:timeout`            | Override global timeout (ms or `:infinity`)           |\n| `:error_handling`     | Override global error-handling flag                   |\n\n---\n\n## Usage — `RpcWrapper` (config-driven)\n\nAll function and node information is declared in config.\nFunctions are generated at compile time.\n\n### 1. Add config\n\n```elixir\n# config/config.exs\nconfig :my_app, :data_wrapper,\n  nodes: [:\"node1@host\", :\"node2@host\"],\n  # or: nodes: {ClusterHelper, :get_nodes, [:data]},\n  error_handling: true,\n  select_mode: :random,\n  module: TargetApp.Interface.Api,\n  timeout: 5_000,\n  retry: 3,\n  sleep_before_retry: 500,       # wait 500 ms between retries\n  functions: [\n    # {function_name, arity}\n    # {function_name, arity, options}\n    {:get_data, 1},\n    {:put_data, 1, [error_handling: false]},\n    {:clear, 2, [new_name: :clear_data, retry: 3, sleep_before_retry: 100]},\n    {:clear_all, 0, [new_name: :reset, private: true]}\n  ]\n```\n\n### 2. Use in your module\n\n```elixir\ndefmodule MyApp.DataHelper do\n  use EasyRpc.RpcWrapper,\n    otp_app: :my_app,\n    config_name: :data_wrapper\n\n  def process_remote() do\n    case get_data(\"key\") do\n      {:ok, data}     -\u003e data\n      {:error, reason} -\u003e {:error, reason}\n    end\n  end\nend\n\n# Or call directly:\n{:ok, result} = MyApp.DataHelper.get_data(\"my_key\")\n```\n\n---\n\n## Node Selection Strategies\n\nConfigure via `select_mode:` in your config:\n\n| Strategy       | Description                                                     |\n|----------------|-----------------------------------------------------------------|\n| `:random`      | Randomly picks a node on each call (default)                    |\n| `:round_robin` | Circular distribution, tracked per process                      |\n| `:hash`        | Consistent hashing on args — same args always hit the same node |\n\n### Sticky Nodes\n\n```elixir\nconfig :my_app, :api,\n  nodes: [:node1@host, :node2@host],\n  select_mode: :random,\n  sticky_node: true   # process pins to first selected node\n```\n\n### Dynamic Node Discovery\n\n```elixir\nconfig :my_app, :api,\n  nodes: {ClusterHelper, :get_nodes, [:backend]},\n  select_mode: :round_robin\n```\n\n---\n\n## Error Handling\n\n### Without error handling (default — raises on error)\n\n```elixir\nuser = MyApi.get_user(123)\n```\n\n### With error handling (returns tagged tuples)\n\n```elixir\ncase MyApi.get_user(123) do\n  {:ok, user}                    -\u003e process(user)\n  {:error, %EasyRpc.Error{} = e} -\u003e Logger.error(EasyRpc.Error.format(e))\nend\n```\n\nEnable globally in config or per function:\n\n```elixir\nconfig :my_app, :api, error_handling: true\n\n# or per defrpc:\ndefrpc :get_user, args: 1, error_handling: true\n```\n\n---\n\n## Retry Logic\n\n```elixir\n# Global retry\nconfig :my_app, :api, retry: 3\n\n# Per-function\ndefrpc :critical_op, args: 1, retry: 5\n```\n\n\u003e When `retry \u003e 0`, `error_handling` is automatically enabled — retried calls\n\u003e always return `{:ok, result} | {:error, %EasyRpc.Error{}}`.\n\n---\n\n## Sleep Before Retry\n\nBy default EasyRpc retries immediately after a failure. Use `sleep_before_retry`\nto add a fixed delay (in milliseconds) between attempts. This is useful for\ngiving a remote node time to recover, or for reducing thundering-herd pressure\non a flapping service.\n\n```elixir\n# Global — all retries in this config wait 500 ms\nconfig :my_app, :api,\n  retry: 3,\n  sleep_before_retry: 500\n\n# Per-function override\ndefrpc :critical_op, args: 1, retry: 5, sleep_before_retry: 200\n```\n\nThe sleep happens **between** attempts — there is no delay before the first\ncall, and no delay after the final failure.\n\n```\nattempt 1 → fails → sleep 500 ms\nattempt 2 → fails → sleep 500 ms\nattempt 3 → fails → sleep 500 ms\nattempt 4 → fails → return {:error, ...}\n```\n\n\u003e `sleep_before_retry` requires a non-negative integer. The default is `0`\n\u003e (no sleep). Setting it without also setting `retry` has no effect.\n\n---\n\n## Timeout Configuration\n\n```elixir\n# Global\nconfig :my_app, :api, timeout: 5_000\n\n# Per-function\ndefrpc :long_op,      args: 1, timeout: 30_000\ndefrpc :health_check,          timeout: 500\ndefrpc :no_limit,              timeout: :infinity\n```\n\n---\n\n## Examples\n\nSee the [lib_examples repository](https://github.com/ohhi-vn/lib_examples/tree/main/easy_rpc) for complete, runnable examples.\n\n---\n\n## AI Agents \u0026 MCP Support\n\nSync usage rules from deps into your repo for AI agent support:\n\n```bash\nmix usage_rules.sync AGENTS.md --all \\\n  --link-to-folder deps \\\n  --inline usage_rules:all\n```\n\nStart the MCP server:\n\n```bash\nmix tidewave\n```\n\nConfigure your agent to connect to `http://localhost:4113/tidewave/mcp` (change port in `mix.exs` if needed).\nSee [Tidewave docs](https://hexdocs.pm/tidewave/) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fohhi-vn%2Feasy_rpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fohhi-vn%2Feasy_rpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fohhi-vn%2Feasy_rpc/lists"}