{"id":15062833,"url":"https://github.com/tolitius/envoy","last_synced_at":"2025-04-13T00:48:16.121Z","repository":{"id":62434951,"uuid":"72600543","full_name":"tolitius/envoy","owner":"tolitius","description":"a gentle touch of Clojure to Hashicorp's Consul","archived":false,"fork":false,"pushed_at":"2024-12-12T22:42:17.000Z","size":3916,"stargazers_count":71,"open_issues_count":2,"forks_count":12,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-13T00:48:06.843Z","etag":null,"topics":["clojure","consul"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tolitius.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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}},"created_at":"2016-11-02T03:27:32.000Z","updated_at":"2024-12-12T22:42:21.000Z","dependencies_parsed_at":"2024-04-12T12:36:20.061Z","dependency_job_id":"4cd581c7-58cd-4e2e-b48b-801e14a2dc05","html_url":"https://github.com/tolitius/envoy","commit_stats":{"total_commits":115,"total_committers":7,"mean_commits":"16.428571428571427","dds":"0.27826086956521734","last_synced_commit":"900f79692265cd684a61fca99418ca637a9f86d0"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fenvoy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fenvoy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fenvoy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fenvoy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tolitius","download_url":"https://codeload.github.com/tolitius/envoy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248650437,"owners_count":21139672,"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":["clojure","consul"],"created_at":"2024-09-24T23:47:12.651Z","updated_at":"2025-04-13T00:48:16.094Z","avatar_url":"https://github.com/tolitius.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Diplomatic rank\n\n_[source](https://en.wikipedia.org/wiki/Diplomatic_rank#Historical_ranks.2C_1815-1961):_\n\n\u003e _The rank of Envoy was short for \"Envoy Extraordinary and Minister Plenipotentiary\", and was more commonly known as Minister. For example, the Envoy Extraordinary and Minister Plenipotentiary of the United States to the French Empire was known as the \"United States Minister to France\" and addressed as \"Monsieur le Ministre.\"_\n\n\u003chr/\u003e\n\n[![Clojars Project](https://clojars.org/tolitius/envoy/latest-version.svg)](http://clojars.org/tolitius/envoy)\n\n- [How to play](#how-to-play)\n- [Map to Consul](#map-to-consul)\n- [Consul to Map](#consul-to-map)\n  - [Reading with an offset](#reading-with-an-offset)\n- [Watch for key/value changes](#watch-for-keyvalue-changes)\n  - [Watch nested keys](#watch-nested-keys)\n  - [Watching the Watcher](#watching-the-watcher)\n- [Consul CRUD](#consul-crud)\n  - [Adding to Consul](#adding-to-consul)\n  - [Reading from Consul](#reading-from-consul)\n  - [Deleting from Consul](#deleting-from-consul)\n- [Clone and Teleport](#clone-and-teleport)\n  - [Copy](#copy)\n  - [Move](#move)\n- [Merging Configurations](#merging-configurations)\n- [Sessions and Locks](#sessions-and-locks)\n- [Options](#options)\n  - [Serializer](#serializer)\n- [License](#license)\n\n## How to play\n\nIn order to follow all the docs below, bring envoy in:\n\n```clojure\n$ make repl\ndev=\u003e (require '[envoy.core :as envoy :refer [stop]])\nnil\n```\n\n## Map to Consul\n\nSince most Clojure configs are EDN maps, you can simply push the map to Consul with preserving the hierarchy:\n\n```clojure\ndev=\u003e (def m {:hubble\n                    {:store \"spacecraft://tape\"\n                     :camera\n                      {:mode \"color\"}\n                     :mission\n                      {:target \"Horsehead Nebula\"}}})\n\ndev=\u003e (envoy/map-\u003econsul \"http://localhost:8500/v1/kv\" m)\nnil\n```\n\ndone.\n\nyou should see Consul logs confirming it happened:\n\n```bash\n2016/11/02 02:04:13 [DEBUG] http: Request PUT /v1/kv/hubble/mission/target? (337.69µs) from=127.0.0.1:39372\n2016/11/02 02:04:13 [DEBUG] http: Request GET /v1/kv/hubble?recurse\u0026index=2114 (4m41.723665304s) from=127.0.0.1:39366\n2016/11/02 02:04:13 [DEBUG] http: Request PUT /v1/kv/hubble/camera/mode? (373.246µs) from=127.0.0.1:39372\n2016/11/02 02:04:13 [DEBUG] http: Request PUT /v1/kv/hubble/store? (1.607247ms) from=127.0.0.1:39372\n```\n\nand a visual:\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"doc/img/map-to-consul.png\"\u003e\u003c/p\u003e\n\n## Consul to Map\n\nIn case a Clojure map with config read from Consul is needed it is just `consul-\u003emap` away:\n\n```clojure\ndev=\u003e (envoy/consul-\u003emap \"http://localhost:8500/v1/kv/hubble\")\n{:hubble\n {:camera {:mode \"color\"},\n  :mission {:target \"Horsehead Nebula\"},\n  :store \"spacecraft://tape\"}}\n```\n\nyou may notice it comes directly from \"the source\" by looking at Consul logs:\n\n```bash\n2016/11/02 02:04:32 [DEBUG] http: Request GET /v1/kv/hubble?recurse (76.386µs) from=127.0.0.1:54167\n```\n\n### Reading with an offset\n\nYou may also read from consul at a certain `:offset` by specifying it in options.\n\nLet's say we need to get everything that lives under the `hubble/mission`:\n\n```clojure\ndev=\u003e (envoy/consul-\u003emap \"http://localhost:8500/v1/kv\" {:offset \"hubble/mission\"})\n{:target \"Horsehead Nebula\"}\n```\n\nSpecifying an offset is really useful for multiple environments or teams living in the same consul / acl.\n\n(!) One thing to note is that an `offset` should start from the root and should be used with no data prefix in URL:\n\ni.e. this is good:\n```clojure\n(envoy/consul-\u003emap \"http://localhost:8500/v1/kv\" {:offset \"/hubble/mission\"})\n```\n\nbut this is not:\n```clojure\n(envoy/consul-\u003emap \"http://localhost:8500/v1/kv/hubble\" {:offset \"mission\"})\n```\n\nthis has to do with the fact that Consul always returns data with a prefix e.g.: `{:hubble {:mission {:target \"Horsehead Nebula\"}}}`, hence just \"mission\" would not be enough to strip it, but \"hubble/mission\" would.\n\n## Watch for key/value changes\n\nWatching for kv changes with envoy _does not require_ to run a separate Consul Agent client or Consul Template and boils down to a simple function:\n\n```clojure\n(watch-path path fun)\n```\n\n`fun` is going to be called with a new value each time the `path`'s value is changed.\n\n```clojure\ndev=\u003e (def store-watcher (envoy/watch-path \"http://localhost:8500/v1/kv/hubble/store\"\n                                                 #(println \"watcher says:\" %)))\n```\n\ncreates a `envoy.core.Watcher` and echos back the current value:\n\n```clojure\n#'dev/store-watcher\nwatcher says: {:hubble/store spacecraft}\n```\n\nit is an `envoy.core.Watcher`:\n\n```clojure\ndev=\u003e store-watcher\n#object[envoy.core.Watcher 0x72a190f0 \"envoy.core.Watcher@72a190f0\"]\n```\n\nthat would print to REPL, since that's the function provided `#(println \"watcher says:\" %)`, every time the key `hubble/store` changes.\n\nlet's change it to \"Earth\":\n\u003cp align=\"center\"\u003e\u003cimg src=\"doc/img/store-update.png\"\u003e\u003c/p\u003e\n\nonce the \"UPDATE\" button is clicked REPL will notify us with a new value:\n\n```clojure\nwatcher says: {:hubble/store Earth}\n```\n\nsame thing if it's changed with `envoy/put`:\n\n```clojure\ndev=\u003e (envoy/put \"http://localhost:8500/v1/kv/hubble/store\" \"spacecraft tape\")\nwatcher says: {:hubble/store spacecraft tape}\n{:opts {:body \"spacecraft tape\", :method :put, :url \"http://localhost:8500/v1/kv/hubble/store\"}, :body \"true\", :headers {:content-length \"4\", :content-type \"application/json\", :date \"Wed, 02 Nov 2016 03:22:41 GMT\"}, :status 200}\n```\n\n`envoy.core.Watcher` is stoppable:\n\n```clojure\ndev=\u003e (stop store-watcher)\n\"stopping\" \"http://localhost:8500/v1/kv/hubble/store\" \"watcher\"\ntrue\n```\n\n### Watch Nested Keys\n\nIn case you need to watch a hierarchy of keys (with all the nested keys), you can set a watcher on a local root key:\n\n```clojure\ndev=\u003e (def hw (envoy/watch-path \"http://localhost:8500/v1/kv/hubble\"\n                                      #(println \"watcher says:\" %)))\n```\n\nnotice this watcher is on the top most / root `/hubble` key.\n\nIn this case _only the nested keys which values are changed_ will trigger notifications.\n\nLet's say we went to `hubble/mission` and changed it from \"Horsehead Nebula\" to \"Butterfly Nebula\":\n\n```clojure\nwatcher says: {:hubble/mission Butterfly Nebula}\n```\n\nIt can be stopped as any other watcher:\n\n```clojure\ndev=\u003e (stop hw)\n\"stopping\" \"http://localhost:8500/v1/kv/hubble?recurse\" \"watcher\"\ntrue\n```\n\n### Watching the Watcher\n\nThere is a [more visual example](https://github.com/tolitius/hubble) of envoy watchers that propagate notifications all the way to the browser:\n\n\u003cimg src=\"doc/img/hubble-mission.jpg\" width=\"100%\"\u003e\n\nNotification listner is just a function really, hence it can get propagated anywhere intergalactic computer system can reach.\n\n## Consul CRUD\n\n### Adding to Consul\n\nThe map from above can be done manually by \"puts\" of course:\n\n```clojure\ndev=\u003e (envoy/put \"http://localhost:8500/v1/kv/hubble/mission\" \"Horsehead Nebula\")\n{:opts {:body \"Horsehead Nebula\", :method :put, :url \"http://localhost:8500/v1/kv/hubble/mission\"}, :body \"true\", :headers {:content-length \"4\", :content-type \"application/json\", :date \"Wed, 02 Nov 2016 02:57:40 GMT\"}, :status 200}\n\ndev=\u003e (envoy/put \"http://localhost:8500/v1/kv/hubble/store\" \"spacecraft\")\n{:opts {:body \"spacecraft\", :method :put, :url \"http://localhost:8500/v1/kv/hubble/store\"}, :body \"true\", :headers {:content-length \"4\", :content-type \"application/json\", :date \"Wed, 02 Nov 2016 02:58:13 GMT\"}, :status 200}\n\ndev=\u003e (envoy/put \"http://localhost:8500/v1/kv/hubble/camera/mode\" \"color\")\n{:opts {:body \"color\", :method :put, :url \"http://localhost:8500/v1/kv/hubble/camera/mode\"}, :body \"true\", :headers {:content-length \"4\", :content-type \"application/json\", :date \"Wed, 02 Nov 2016 02:58:36 GMT\"}, :status 200}\n```\n\n### Reading from Consul\n\n```clojure\ndev=\u003e (envoy/get-all \"http://localhost:8500/v1/kv/hubble\")\n{:hubble/camera/mode \"color\",\n :hubble/mission \"Horsehead Nebula\",\n :hubble/store \"spacecraft://tape\"}\n\ndev=\u003e (envoy/get-all \"http://localhost:8500/v1/kv/hubble/store\")\n{:hubble/store \"spacecraft\"}\n```\n\nin case there is no need to convert keys to keywords, it can be disabled:\n\n```clojure\ndev=\u003e (envoy/get-all \"http://localhost:8500/v1/kv/\" {:keywordize? false})\n{\"hubble/camera/mode\" \"color\",\n \"hubble/mission\" \"Horsehead Nebula\",\n \"hubble/store\" \"spacecraft://tape\"}\n```\n\n### Deleting from Consul\n\n```clojure\ndev=\u003e (envoy/delete \"http://localhost:8500/v1/kv/hubble/camera\")\n{:opts {:method :delete, :url \"http://localhost:8500/v1/kv/hubble/camera?recurse\"}, :body \"true\", :headers {:content-length \"4\", :content-type \"application/json\", :date \"Wed, 02 Nov 2016 02:59:26 GMT\"}, :status 200}\n\ndev=\u003e (envoy/get-all \"http://localhost:8500/v1/kv/hubble\")\n{:hubble/mission \"Horsehead Nebula\", :hubble/store \"spacecraft://tape\"}\n```\n\n## Clone and Teleport\n\nIt is often the case when configuration trees need to be copied or moved from one place to another, under a new root or a new nested path. `envoy` can do it with `copy` and `move` commands.\n\n### Copy\n\nCopying configuration from one place to another is done with a `copy` command:\n\n\u003e =\u003e _```(envoy/copy kv-path from to)```_\n\nLet's say we need to copy Hubble's mission (i.e. a \"sub\" config) under a new root \"dev\", so it lives under \"/dev/hubble/mission\" instead:\n\n```clojure\ndev=\u003e (envoy/copy \"http://localhost:8500/v1/kv\" \"/hubble/mission\" \"/dev/hubble/mission\")\n```\n\ndone. Let's read from this new \"dev\" root to make sure the mission is there:\n\n```clojure\ndev=\u003e (envoy/consul-\u003emap \"http://localhost:8500/v1/kv/dev\")\n{:dev {:hubble {:mission {:target \"Horsehead Foo\"}}}}\n```\n\ngreat.\n\nWe can of course copy the whole \"hubble\"'s config under \"dev\":\n\n```clojure\ndev=\u003e (envoy/copy \"http://localhost:8500/v1/kv\" \"/hubble\" \"/dev/hubble\")\n```\n\n```clojure\ndev=\u003e (envoy/consul-\u003emap \"http://localhost:8500/v1/kv/dev\")\n{:dev {:hubble {:camera {:mode \"color\"}, :mission {:target \"Horsehead Nebula\"}, :store \"spacecraft tape\"}}}\n```\n\n`copy` is really handy when you need to copy configurations between environments, or just need to copy some nested portion of the config.\n\n### Move\n\nA `move` command is exactly the same as the `copy`, but, as you would expect, it deletes the source after the copy is done.\n\n\u003e =\u003e _```(envoy/move kv-path from to)```_\n\nThe Hubble's development work is finished, and we are switching to work on the Kepler telescope. Let's say most of the configuration may be reused, so we'll just move Hubble's config to Kepler:\n\n```clojure\ndev=\u003e (envoy/move \"http://localhost:8500/v1/kv\" \"/hubble\" \"/kepler\")\n```\n\ndone.\n\nOh, but we'll need \"dev\" and \"qa\" environments for Kepler's development. Let's move it again to live under \"dev\" root:\n\n```clojure\ndev=\u003e (envoy/move \"http://localhost:8500/v1/kv\" \"/kepler\" \"/dev/kepler\")\n```\n\nand \"copy\" this config to \"qa\" before editing it:\n\n```clojure\ndev=\u003e (envoy/copy \"http://localhost:8500/v1/kv\" \"/dev/kepler\" \"/qa/kepler\")\n```\n\nLet's look at Kepler's Consul universe:\n\n```clojure\ndev=\u003e (envoy/consul-\u003emap \"http://localhost:8500/v1/kv\")\n\n{:dev\n {:kepler\n  {:mission {:target \"Horsehead Nebula\"},\n   :camera {:mode \"color\"},\n   :store \"spacecraft tape\"}},\n :qa\n {:kepler\n  {:mission {:target \"Horsehead Nebula\"},\n   :camera {:mode \"color\"},\n   :store \"spacecraft tape\"}}}\n```\n\nNiice, universe awaits...\n\n## Merging Configurations\n\nOften there is an internal configuration some parts of which need to be overridden with values from Consul. Envoy has `merge-with-consul` function that does just that:\n\n```clojure\n(envoy/merge-with-consul config \"http://localhost:8500/v1/kv/hubble\")\n```\n\nwill deep merge (with nested kvs) config with a map it'll read from Consul.\n\nIn case a Consul space is protected by a token, or any other options need to be passed to Consul to read the overrides, they can be added in an optional map:\n\n```clojure\n(envoy/merge-with-consul config\n                         \"http://localhost:8500/v1/kv/hubble\"\n                         {:token \"7a0f3b39-8871-e16e-2101-c1b30a911883\"})\n```\n\n## Sessions and Locks\n\nConsul provides a [session mechanism](https://www.consul.io/docs/dynamic-app-config/sessions) which can be used to build distributed locks.\n\nSessions can be created, deleted, listed, listed for a node, etc. Once the session is created locks can be acquired for these sessions.\n\nConsul [session API](https://www.consul.io/api-docs/session) have multiple parameters, do read the Consul docs in case you need to deviate from defaults.\n\nAn example we'll look at here will:\n\n* create two sessions with TTL 45 seconds (`moon` and `mars`)\n* acquire a `start-stage-one-booster` lock for the `moon` session\n* try to acquire the same lock with `mars` _(fail)_\n* wait until the first, `moon` session expires\n* renew the `mars` session to make sure we can still use it after the `moon` expires\n* try to acquire the same lock with the `mars` session again _(succeed)_\n\n```clojure\n(require '[envoy.session :as es])\n```\n\n```clojure\n=\u003e (def moon (es/create-session \"http://localhost:8500\" {:name \"fly-me-to-the-moon\" :ttl \"45s\"}))\n#'user/moon\n\n=\u003e (def mars (es/create-session \"http://localhost:8500\" {:name \"fly-me-to-the-mars\" :ttl \"45s\"}))\n#'user/mars\n\n=\u003e moon\n{:id \"3f230917-90c9-6b5e-f579-43d854ba9cfe\"}\n\n=\u003e mars\n{:id \"371445e6-d74f-9524-de06-8622fa4344cd\"}\n```\n\nsessions are created and Consul returned session ids to refer to these sessions by.\n\nlet's list these sessions:\n\n```clojure\n=\u003e (es/list-sessions \"http://localhost:8500\")\n[{:service-checks nil,\n  :modify-index 18157665,\n  :name \"fly-me-to-the-mars\",\n  :behavior \"release\",\n  :node \"pluto\",\n  :ttl \"45s\",\n  :id \"371445e6-d74f-9524-de06-8622fa4344cd\",\n  :create-index 18157665,\n  :lock-delay 15000000000,\n  :node-checks [\"serfHealth\"]}\n {:service-checks nil,\n  :modify-index 18157655,\n  :name \"fly-me-to-the-moon\",\n  :behavior \"release\",\n  :node \"pluto\",\n  :ttl \"45s\",\n  :id \"3f230917-90c9-6b5e-f579-43d854ba9cfe\",\n  :create-index 18157655,\n  :lock-delay 15000000000,\n  :node-checks [\"serfHealth\"]}]\n```\n\nnow let's acquire a `start-stage-one-booster` lock with the `moon` session:\n\n```clojure\n=\u003e (es/acquire-lock \"http://localhost:8500\" {:task \"start-stage-one-booster\"\n                                             :session-id (moon :id)})\ntrue\n```\n\n\"true\" means Consul said the lock was successfully acquired.\n\nnow let's try to acquire the same lock with the `mars` session:\n\n```clojure\n=\u003e (es/acquire-lock \"http://localhost:8500\" {:task \"start-stage-one-booster\"\n                                             :session-id (mars :id)})\nfalse\n```\n\nooops, can't touch this: since this lock is already acquired by another session.\n\nwhile we are waiting on the `moon` session to expire (that \"45s\" TTL), let's renew the `mars` session so it outlives the `moon` one:\n\n```clojure\n=\u003e (es/renew-session \"http://localhost:8500\" {:uuid (mars :id)})\n[{:service-checks nil,\n  :modify-index 18157665,\n  :name \"fly-me-to-the-mars\",\n  :behavior \"release\",\n  :node \"pluto\",\n  :ttl \"45s\",\n  :id \"371445e6-d74f-9524-de06-8622fa4344cd\",\n  :create-index 18157665,\n  :lock-delay 15000000000,\n  :node-checks [\"serfHealth\"]}]\n```\n\nafter waiting for over 45 seconds, let's try to acquire the same lock with the `mars` session again:\n\n```clojure\n=\u003e (es/acquire-lock \"http://localhost:8500\" {:task \"start-stage-one-booster\"\n                                             :session-id (mars :id)})\ntrue\n```\n\ngreat success!\n\nby the way we also get a visual:\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"doc/img/mars-lock.png\"\u003e\u003c/p\u003e\n\nall the params and options can still be used with session api.\n\nfor example to pass a \"data center\" and an \"ACL token\":\n\n```clojure\n=\u003e (es/create-session \"http://localhost:8500\" {:dc \"asteroid-belt\" :name \"fly-me-to-the-moon\" :ttl \"45s\"}\n                                              {:token \"73e5a965-40af-9c70-a817-b065b6ef82db\"})\n```\n\nthe reason they are in two maps is because a `dc` is part of create session API parameters, whereas a token is a general Consul param.\n\n## Options\n\nAll commands take an optional map of parameters. These parameters will get converted into Consul [KV Store Endpoints](https://www.consul.io/api/kv.html#parameters) params. Thus making all of the KV Store Endpoint params supported.\n\nFor example, in case keys are protected by ACL, you can provide a token:\n\n```clojure\ndev=\u003e (envoy/consul-\u003emap \"http://localhost:8500/v1/kv\"\n                               {:token \"4c308bb2-16a3-4061-b678-357de559624a\"})\n\n{:hubble {:mission \"Butterfly Nebula\", :store \"spacecraft://ssd\"}}\n```\n\nor a _token_ and a _datacenter_:\n\n```clojure\ndev=\u003e (envoy/consul-\u003emap \"http://localhost:8500/v1/kv\"\n                               {:token \"63aaa731-b124-40ef-9425-978aba612a1d\"\n                                :dc \"phloston\"})\n\n{:hubble {:mission \"Ghost of Jupiter\", :store \"spacecraft://tape\"}}\n```\n\nor any other Consul supported parameters.\n\n### Serializer\n\nBy default envoy will serialize and deserialize data in EDN format. Which usually is quite transparent, since EDN map gets written and read from Consul as a nested key value structure which is just that: a map.\n\nThere are cases where values are sequences: i.e. `{:hosts [\"foo1.com\", \"foo2.com\"]}` in which case they will still be serialized and deserialized as EDN by default, however it might be harder to consume these EDN sequences from languages other than Clojure which do not speak EDN natively.\n\nWhile there are libraries for other languages that support EDN\n\n* Java: https://github.com/danboykis/trava\n* Go: https://github.com/go-edn/edn\n* Ruby: https://github.com/relevance/edn-ruby\n* Python: https://github.com/swaroopch/edn_format\n* etc.\n\nenvoy would allow to specify other serializers via a `:serializer` option:\n\n```clojure\ndev=\u003e (def config {:system {:hosts [\"foo1.com\", \"foo2.com\", {:a 42}]}})\n#'dev/config\n\ndev=\u003e (envoy/map-\u003econsul \"http://localhost:8500/v1/kv\" config {:serializer :json})\nnil\n\ndev=\u003e (envoy/consul-\u003emap \"http://localhost:8500/v1/kv/system\" {:serializer :json})\n{:system {:hosts [\"foo1.com\" \"foo2.com\" {:a 42}]}}\n```\n\nIf `{:serializer :json}` option is provided sequence values will be stored in Consul as JSON:\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"doc/img/json-serialization.png\"\u003e\u003c/p\u003e\n\nwhich can be consumed from other languages without the need to know about EDN.\n\n## License\n\nCopyright © 2023 tolitius\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Fenvoy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftolitius%2Fenvoy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Fenvoy/lists"}