{"id":13507563,"url":"https://github.com/undeadlabs/discovery","last_synced_at":"2026-02-18T21:02:44.483Z","repository":{"id":18004456,"uuid":"21021401","full_name":"undeadlabs/discovery","owner":"undeadlabs","description":"An OTP application for auto-discovering services with Consul","archived":false,"fork":false,"pushed_at":"2017-07-15T17:52:47.000Z","size":573,"stargazers_count":251,"open_issues_count":7,"forks_count":19,"subscribers_count":30,"default_branch":"master","last_synced_at":"2025-10-21T18:48:05.175Z","etag":null,"topics":[],"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/undeadlabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2014-06-20T01:31:30.000Z","updated_at":"2025-10-12T16:52:23.000Z","dependencies_parsed_at":"2022-09-11T18:50:44.750Z","dependency_job_id":null,"html_url":"https://github.com/undeadlabs/discovery","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/undeadlabs/discovery","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/undeadlabs%2Fdiscovery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/undeadlabs%2Fdiscovery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/undeadlabs%2Fdiscovery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/undeadlabs%2Fdiscovery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/undeadlabs","download_url":"https://codeload.github.com/undeadlabs/discovery/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/undeadlabs%2Fdiscovery/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29596125,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T20:59:56.587Z","status":"ssl_error","status_checked_at":"2026-02-18T20:58:41.434Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2024-08-01T02:00:36.221Z","updated_at":"2026-02-18T21:02:44.468Z","avatar_url":"https://github.com/undeadlabs.png","language":"Elixir","funding_links":[],"categories":["Cloud Infrastructure and Management"],"sub_categories":[],"readme":"# Discovery\n\n[![Build Status](https://travis-ci.org/undeadlabs/discovery.png?branch=master)](https://travis-ci.org/undeadlabs/discovery) [![Inline docs](http://inch-ci.org/github/undeadlabs/discovery.svg?branch=master)](http://inch-ci.org/github/undeadlabs/discovery)\n\nAn OTP application for auto-discovering services with [Consul](http://www.consul.io)\n\n## Requirements\n\n* Elixir 1.0.0 or newer\n\n## Installation\n\nAdd Discovery as a dependency in your `mix.exs` file\n\n```elixir\ndef application do\n  [applications: [:discovery]]\nend\n\ndefp deps do\n  [\n    {:discovery, \"~\u003e 0.5.0\"}\n  ]\nend\n```\n\nThen run `mix deps.get` in your shell to fetch the dependencies.\n\n\n## Usage\n\nThere are two parts for automatically interconnecting services.\n\n  * Services need to publish their status\n  * Services which care about others need to poll for the statuses of the services they care about\n\n### Publishing service status\n\nFirst, you'll need to install a [Consul Agent](http://www.consul.io/docs/agent/basics.html) on the machine which will be running the OTP application. This can be done manually, but I recommend the [Consul Cookbook](https://github.com/johnbellone/consul-cookbook) for [Chef](http://getchef.com).\n\nNext a [service definition](http://www.consul.io/docs/agent/services.html) must be defined with a TTL for an application to report it's status to.\n\n```json\n{\n  \"service\": {\n    \"name\": \"my_application\",\n    \"check\": {\n      \"ttl\": \"15s\"\n    },\n    \"tags\": [\n      \"otp_name:my_application@jamie.undeadlabs.com\"\n    ]\n  }\n}\n```\n\nThe TTL acts as a [dead man's trigger](https://www.youtube.com/watch?v=GyDEbV3zblA) where the service will be marked as unavailable if the OTP application hasn't sent a heartbeat within the allotted TTL.\n\nStart and supervise a `Discovery.Heartbeat` process in your OTP application to report your status to Consul.\n\n```elixir\ndefmodule MyApplication.Supervisor do\n  use Supervisor\n\n  @heartbeat_check \"service:my_application\"\n  @heartbeat_ttl   10\n\n  def start_link do\n    Supervisor.start_link(__MODULE__, [])\n  end\n\n  def init([]) do\n    children = [\n      worker(Discovery.Heartbeat, [@heartbeat_check, @heartbeat_ttl]),\n    ]\n    supervise(children, strategy: :one_for_one)\n  end\nend\n```\n\nThe value for `@heartbeat_check` is composed of two strings separated by a colon:\n\n  * The first string is the type of check that we're reporting our status for; in this case a service.\n  * The second string is the name of the check which was defined in the service definition above.\n\nThe value for `@heartbeat_ttl` a time in seconds for how often to check-in with Consul. I recommend setting this to a few seconds before the TTL configured in the service definition to allow for some breathing room and prevent false service outage blips.\n\nIf you want other OTP nodes to automatically discover and connect to you (more on that later) it is also important to note that a special tag has been added to the service definition above. Tags separated by a colon (`:`) are key/value pairs used by certain handlers. In this case the `Discovery.NodeConnector` will use the value of this key/value pair as the OTP node name to connect to another OTP node.\n\n### Polling for services\n\nThe flipside for broadcasting service status is listening for service status. For that, Discovery provides a poller process that can be started and supervised.\n\n```elixir\ndefmodule MyApplication.Supervisor do\n  use Supervisor\n\n  def start_link do\n    Supervisor.start_link(__MODULE__, [])\n  end\n\n  def init([]) do\n    children = [\n      worker(Discovery.Poller, [\"my_application\", Discovery.Handler.NodeConnect], id: MyApplication.MyPoller),\n    ]\n    supervise(children, strategy: :one_for_one)\n  end\nend\n```\n\nThe poller process will poll the given service health check and upon change, notify a handler process implementing `Discovery.Handler.Behaviour`. One or many handlers can be passed to the poller. In the above example a single handler, `Discovery.Handler.NodeConnector`, is registered with the poller.\n\n\u003e If you are supervising multiple pollers it is important to specify a value for `:id`. Not doing so will halt startup. This can be safely ignored if you do not intend to supervise more than one poller.\n\n#### Poller handler\n\nIn the previous section we passed the module `Discovery.Handler.NodeConnect` as an argument to `Discovery.Poller` when we supervised the poller. This is a poller handler.\n\nPoller handlers implement the behaviour `Discovery.Handler.Behaviour` which requires a single function to be implemented, `handle_services/2`. This function is called whenever the poller completes and passes the services it found when performing a health check as the first argument. The second argument is the state of the event handler.\n\n\u003e `Discovery.Handler.Behaviour` is actually using `GenEvent` under the hood\n\nDiscovery comes with two handlers\n\n  * `Discovery.Handler.NodeConnector` - automatically connects OTP nodes which have been discovered by a poller\n  * `Discovery.Handler.Generic` - executes an anonymous function with an arity of 1 with the found services\n\nMultiple handlers can be added and they can be added with or without arguments:\n\n```elixir\ndef init([]) do\n  children = [\n    worker(Discovery.Poller, [\"my_application\", [\n      Discovery.Handler.NodeConnect,\n      {MyApplication.MyHandler, [\"argument_1\", \"argument_2\"]}\n    ], id: MyApplication.MyPoller),\n  ]\n  supervise(children, strategy: :one_for_one)\nend\n```\n\nAn anonymous function can also act as a handler:\n\n```elixir\ndef init([]) do\n  children = [\n    worker(Discovery.Poller, [\"my_application\", \u0026my_function/1], id: MyApplication.MyPoller)\n  ]\n  supervise(children, strategy: :one_for_one)\nend\n\ndef my_function(services) do\n  # do something\nend\n```\n\n\u003e The generic handler `Discovery.Handler.Generic` is used under the hood if you provide an anonymous function as a handler.\n\n### Automatically connecting nodes (Handler.NodeConnect)\n\nThe node connector handler `Discovery.Handler.NodeConnect` will notify the registered `Discovery.NodeConnector` process of additional nodes and service status changes.\n\nThe `Discovery.NodeConnector` process will automatically connect and retry connections to other nodes when they become available. It will also sever connections when Consul reports those nodes as being no longer available.\n\n`Node.connect/1` will be be run for each registered service found by Consul. The OTP node name for each of these nodes is read from a tag written to the service definition (see above) in the form of `otp_name:\u003cname\u003e` where name is the OTP node name. So given the node name `my_application@jamie.undeadlabs.com` the service definition would contain a tag `otp_name:my_application@jamie.undeadlabs.com`.\n\n\u003e Ensure that the --name flag is set to the proper node name before starting your OTP application. This can be set in the `vm.args` file or passed to Elixir on the command line.\n\n### Selecting nodes\n\nNodes which have been automatically discovered and connected to via `Discovery.NodeConnector` can be filtered or selected via a hash value.\n\nListing all registered nodes which provide the given service:\n\n```Elixir\niex\u003e Discovery.nodes(\"my_application\")\n[:'my_application@jamie.undeadlabs.com']\n\niex\u003e Discovery.nodes(\"another_application\")\n[]\n```\n\nSelecting a node for a given hash value using a consistent hashing algorithm:\n\n```Elixir\nDiscovery.select(\"my_application\", \"hashValue\", fn\n  {:ok, node} -\u003e\n    # do something with node\n  {:error, {:no_servers, \"my_application\"}} -\u003e\n    # do something with error\nend)\n```\n\nA node can also be randomly selected if the atom `:random` is passed as the hash value:\n\n```Elixir\nDiscovery.select(\"my_application\", :random, fn(result) -\u003e IO.inspect result end)\n```\n\n## Authors\n\nJamie Winsor (\u003cjamie@vialstudios.com\u003e)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fundeadlabs%2Fdiscovery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fundeadlabs%2Fdiscovery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fundeadlabs%2Fdiscovery/lists"}