{"id":27993951,"url":"https://github.com/erleans/erleans","last_synced_at":"2025-05-08T19:04:16.328Z","repository":{"id":20036663,"uuid":"83915041","full_name":"erleans/erleans","owner":"erleans","description":"Erlang Orleans","archived":false,"fork":false,"pushed_at":"2025-02-02T12:39:53.000Z","size":281,"stargazers_count":286,"open_issues_count":10,"forks_count":27,"subscribers_count":23,"default_branch":"main","last_synced_at":"2025-05-08T19:03:54.921Z","etag":null,"topics":["distributed-actors","distributed-systems","erlang","orleans"],"latest_commit_sha":null,"homepage":null,"language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erleans.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}},"created_at":"2017-03-04T18:24:41.000Z","updated_at":"2025-04-25T06:20:38.000Z","dependencies_parsed_at":"2022-07-23T14:32:27.522Z","dependency_job_id":null,"html_url":"https://github.com/erleans/erleans","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erleans%2Ferleans","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erleans%2Ferleans/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erleans%2Ferleans/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erleans%2Ferleans/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erleans","download_url":"https://codeload.github.com/erleans/erleans/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253133137,"owners_count":21859111,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["distributed-actors","distributed-systems","erlang","orleans"],"created_at":"2025-05-08T19:04:15.613Z","updated_at":"2025-05-08T19:04:16.260Z","avatar_url":"https://github.com/erleans.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"Erleans\n=====\n\n[![Common Test](https://github.com/erleans/erleans/actions/workflows/ct.yml/badge.svg)](https://github.com/erleans/erleans/actions/workflows/ct.yml)[![codecov](https://codecov.io/gh/erleans/erleans/branch/main/graph/badge.svg)](https://codecov.io/gh/erleans/erleans)\n\nErleans is a framework for building distributed applications in Erlang and Elixir based on [Microsoft Orleans](https://dotnet.github.io/orleans/).\n\n## Requirements\n\n[Rebar3](http://rebar3.org/) 3.24.0 or above or [Elixir](https://elixir-lang.org/) 1.18+. \n\n## Components\n\n### Grains\n\nStateful grains are backed by persistent storage and referenced by a primary key set by the grain. An activation of a grain is a single Erlang process in on an Erlang node (silo) in an Erlang cluster. Activation placement is handled by Erleans and communication is over standard Erlang distribution. If a grain is sent a message and does not have a current activation one is spawned.\n\nGrain state is persisted through a database provider with an always increasing change id or etag. If the change id or etag has been by another activation the activation attempting to save state will stop.\n\nActivations are registered through\n[global](https://www.erlang.org/doc/apps/kernel/global.html) by default.\n\n### Stateless Grains\n\nStateless grains have no restriction on the number of activations and do not persist state to a database.\n\nStateless grain activations are pooled through [gproc](https://github.com/uwiger/gproc/).\n\n### Reminders (TODO)\n\nTimers that are associated with a grain, meaning if a grain is not active but a reminder for that grain ticks the grain is activated at that time and the reminder is delivered.\n\n### Observers (TODO)\n\nProcesses can subscribe to grains to receive notifications for grain specific\nevents. If a grain supports observers a group is created through\n[pg](https://www.erlang.org/doc/apps/kernel/pg.html).\n\n### Providers\n\nInterface that must be implemented for any persistent store to be used for grains.\n\n[Streams](https://github.com/erleans/erleans_streams) have a provider type as well for providing a pluggable stream layer.\n\n## Differences from gen_server\n\nNo starting or linking, a grain is activated when it is sent a request if an activation is not currently running.\n\n### Grain Placement\n\n* `prefer_local`: If an activation does not exist this causes the new activation to be on the same node making the request.\n* `random`: Picks a random node to create any new activation of a grain.\n* `stateless`: Stateless grains are always local. If no local activation to the request exists one is created up to a default maximum value.\n* `{stateless, Max :: integer()}`: Allows for up to `Max` number of activations for a grain to exist per node. A new activation, up until `Max` exist on the node, will be created for a request if an existing activation is not currently busy.\n\n### Erlang Example\n\nThe grain implementation `test_grain` is found in `test/`:\n\n```erlang\n-module(test_grain).\n\n-behaviour(erleans_grain).\n\n...\n\nplacement() -\u003e\n    prefer_local.\n\nprovider() -\u003e\n    in_memory.\n\ndeactivated_counter(Ref) -\u003e\n    erleans_grain:call(Ref, deactivated_counter).\n\nactivated_counter(Ref) -\u003e\n    erleans_grain:call(Ref, activated_counter).\n\nnode(Ref) -\u003e\n    erleans_grain:call(Ref, node).\n\nstate(_) -\u003e\n    #{activated_counter =\u003e 0,\n      deactivated_counter =\u003e 0}.\n\nactivate(_, State=#{activated_counter := Counter}) -\u003e\n    {ok, State#{activated_counter =\u003e Counter+1}, #{}}.\n```\n\n```erlang\n$ rebar3 as test shell\n...\n\u003e Grain1 = erleans:get_grain(test_grain, \u003c\u003c\"grain1\"\u003e\u003e).\n\u003e test_grain:activated_counter(Grain1).\n{ok, 1}\n```\n\n## Elixir Example\n\n``` elixir\ndefmodule ErleansElixirExample do\n  use Erleans.Grain,\n    placement: :prefer_local,\n    provider: :postgres,\n    state: %{:counter =\u003e 0}\n\n  def get(ref) do\n    :erleans_grain.call(ref, :get)\n  end\n\n  def increment(ref) do\n    :erleans_grain.cast(ref, :increment)\n  end\n\n  def handle_call(:get, from, state = %{:counter =\u003e counter}) do\n    {:ok, state, [{:reply, from, counter}]}\n  end\n\n  def handle_cast(:increment, state = %{:counter =\u003e counter}) do\n    new_state = %{state | :counter =\u003e counter + 1}\n    {:ok, new_state, [:save_state]}\n  end\nend\n```\n\n``` elixir\n$ mix deps.get\n$ mix compile\n$ iex --sname a@localhost -S mix\n\niex(a@localhost)1\u003e ref = Erleans.get_grain(ErleansElixirExample, \"somename\")\n...\niex(a@localhost)2\u003e ErleansElixirExample.get(ref)\n0\niex(a@localhost)3\u003e ErleansElixirExample.increment(ref)\n:ok\niex(a@localhost)4\u003e ErleansElixirExample.get(ref)\n1\n```\n\n## Contributing\n\n### Running Tests\n\n```\n$ epmd -daemon\n$ rebar3 ct\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferleans%2Ferleans","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferleans%2Ferleans","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferleans%2Ferleans/lists"}