{"id":28194252,"url":"https://github.com/monkey-projects/mailman","last_synced_at":"2025-05-16T13:11:59.693Z","repository":{"id":274825422,"uuid":"924195888","full_name":"monkey-projects/mailman","owner":"monkey-projects","description":"Simple event routing library","archived":false,"fork":false,"pushed_at":"2025-05-12T15:09:39.000Z","size":129,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-12T16:24:31.699Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/monkey-projects.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}},"created_at":"2025-01-29T15:31:58.000Z","updated_at":"2025-05-12T15:09:43.000Z","dependencies_parsed_at":"2025-02-17T11:20:43.144Z","dependency_job_id":"0d954f31-fa3b-4062-89b5-b9b64b09c0d7","html_url":"https://github.com/monkey-projects/mailman","commit_stats":null,"previous_names":["monkey-projects/mailman"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkey-projects%2Fmailman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkey-projects%2Fmailman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkey-projects%2Fmailman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkey-projects%2Fmailman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monkey-projects","download_url":"https://codeload.github.com/monkey-projects/mailman/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254535804,"owners_count":22087399,"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":[],"created_at":"2025-05-16T13:11:43.064Z","updated_at":"2025-05-16T13:11:59.688Z","avatar_url":"https://github.com/monkey-projects.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mailman\n\nA Clojure library that provides a simple way to handle events in a generic manner.\nThe goal is to be able to declare event handling similar to HTTP routing libraries,\nsuch as [reitit](https://github.com/metosin/reitit).\n\nThe core library is implementation agnostic, but it provides some sensible defaults\nand an in-memory implementation of a simple event broker, meant for development and\ntesting purposes.\n\nThe handler functions are supposed to be simple 1-arity functions that take an event\nas argument, and return a sequence of resulting events that should be posted back to\nthe broker, or `nil` if no events should be posted.\n\nMailman provides additional helper functions to create routers, add interceptors\n(using [Pedestal](http://pedestal.io)) or set up custom matches and invokers.  See\nbelow for more on what these are.\n\n## Usage\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.monkeyprojects/mailman-core.svg)](https://clojars.org/com.monkeyprojects/mailman-core)\n\nInclude the core library in your `deps.edn`:\n```clojure\n{:deps {com.monkeyprojects/mailman-core {:mvn/version \"\u003cversion\u003e\"}}}\n```\n\nOr with Leiningen:\n```clojure\n(defproject ...\n  :dependencies [[com.monkeyprojects/mailman-core \"\u003cversion\u003e\"]])\n```\n\nThe core library provides the protocols and basic functionality.  There is also an\nin-memory implementation of a broker, for testing and development purposes.\n\nFunctionality is spread over several namespaces, but the core is in `monkey.mailman.core`.\nSimilar to a HTTP-style application, you define your \"routes\", which map event types to\nhandlers.  The default configuration assumes events have a `:type` property that is\nused to match the available event handlers.\n\nYou can declare are router function, that takes an event, invokes the matching handlers,\nand returns the results.\n\n```clojure\n(require '[monkey.mailman.core :as mm])\n\n(def routes {:event-1 [(constantly ::first)]\n             :event-2 [(constantly ::second)]})\n\n(def router (mm/router routes))\n\n(router {:type :event-1})\n;; =\u003e returns [{:event {:type :event-1} :handler \u003chandler-fn\u003e :result ::first}]\n```\n\nThe event handlers are 1-arity functions that take the context as argument.  The return\nvalue is added to the resulting structure, along with the input event and the handler\nfunction itself.  It's up to the event broker implementation to process this result,\nbut the intention is that when the result is an event, it is re-dispatched.\n\nThe context is a structure that contains at least an `:event`, but it may contain\nmore, see **interceptors** below.\n\n## Handlers\n\nA handler is a function that takes a context, and returns a result structure.  This\nstructure consists of the input event, the handler and a result.  To make setting\nup handlers easier, a protocol exists called `ToHandler`.  It is responsible for\nconverting its argument in a handler record.  This is then converted into a handler\nfunction by the router.\n\nImplementations exist that convert a simple function or a map into a handler.\nThe function is wrapped and its return value converted to the aforementioned result\nstructure.\n\n### Map Handlers\n\nMap handlers are useful if you want to provide some more detail to the handler.\nAt the very least it requires a `:handler` key, that provides the handler function,\nwhich takes a context, and returns some result (probably new events to dispatch).\n\nBut you can also add interceptors to them.\n\n### Interceptors\n\nOne of the most useful things in standard HTTP libraries are the interceptors.\nThey provide a more flexible way of converting input and output for handlers than\nthe standard Ring-style function wrapping.  In *Mailman*, we opted to use the\ninterceptors provided by [Pedestal](http://pedestal.io).  Some default interceptors\nhave been provided in the `monkey.mailman.interceptors` namespace.\n\nThe last interceptor is usually the one that actually invokes the handler function,\nand converts the result.  This is the `handler-interceptor`.  There is also a\n`sanitize-result` interceptor, that converts the handler return value into a\nsequence of events, and drops any non-event things.\n\nBut you can also provide your own interceptors.  See the [Pedestal interceptor\ndocumentation](http://pedestal.io/pedestal/0.7/guides/what-is-an-interceptor.html)\nfor details on how to do that.\n\nAnd in order to use all these as a regular handler, there is the `interceptor-handler`\nfunction, that wraps a handler so interceptors are execute correctly.  Note that this\nis all done for you by the router.\n\nSo in order to create a router with interceptors, you could write something like this:\n```clojure\n(def routes\n  {:my/event-type {:handler my-handler\n                   :interceptors [log-events]}})\n\n(def router (mm/router routes {:interceptors [(mm/sanitize-result)]}))\n```\n\nAs shown, you can also provide top-level interceptors in the options map to the router.\nThese are combined with the route-specific interceptors.  The route-specific interceptors\nare appended to the global interceptors, so they have \"priority\" so to speak.\n\n## Customization\n\nThe `router` function accepts an optional second argument, which is an options map\nthat allows you to override some default behaviour of the router.  You can specify\na custom `matcher`, and also an `invoker`.  See below for more.\n\n### Route Matchers\n\nSince Mailman is all about allowing freedom, but providing sensible defaults, the route\nmatching is set up in a similar fashion.  It's actually a protocol (calls `RouteMatcher`)\nand by default it sets up the routes as a map, and matches by event `:type`.  Each map\nvalue can have multiple possible handlers.\n\n`RouteMatcher` provides two functions: `compile-routes` and `find-handlers`.  The former\ntakes the routes as provided to the `router` function, and converts them into a format\nmore suitable to the matcher.  The latter looks up handlers for an incoming event.\n\nFunctions also implement the protocol, and by default they leave the routes untouched\n(so `compile-routes` is a dummuy function) and just invokes the function to find\nhandlers.\n\nAnd you can also specify your own matcher by implementing the protocol.  Override the\ndefault matcher by specifying the `:matcher` key in the options map when calling\n`router`.\n\n```clojure\n(defrecord CustomMatcher []\n  mm/RouteMatcher\n  (compile-routes [this routes]\n    ;; Prepare routes for the finder\n    ...)\n\n  (find-handlers [this routes evt]\n    ;; Actually find handlers in the compiled routes\n    ...))\n\n(mm/router routes {:matcher (-\u003eCustomMatcher)})\n```\n\n### Invokers\n\nBy default, handler invocation is done by just invoking each handler in sequence.\nBut you can specify your own invoker, for instance to do async event handling so\none handler cannot block another one.  An invoker is a 2-arity function that takes\nthe handlers, as returned by `find-handlers`, and the event to handle.  It is\nsupposed to return a list of results, which is then passed back to the broker.\n\n### Replacing Interceptors\n\nSince interceptors are often used to implement side-effects, they can be difficult to\ndeal with in unit tests.  *Mailman* offers the possibility to replace interceptors in\na router using the `replace-interceptors` function.  It takes a previously created\nrouter and returns a new router with all interceptors that bear the same `:name` as one\nof the given interceptors replaced.\n\n```clojure\n(def my-interceptor\n  {:name ::my-interceptor\n   :enter (fn [ctx]\n            (assoc ctx ::my-result (some-side-effecting-function!)))})\n\n(def router (mm/router [[:test/event [{:handler some-handler\n                                       :interceptors [my-interceptor]}]]]))\n\n;; Create a new interceptor with the same name but with fake effects\n(def fake-interceptor\n  {:name ::my-interceptor ; use the same name\n   :enter (fn [ctx]\n            (assoc ctx ::my-result ::fake-result))})\n\n(def test-router (mm/replace-interceptors router [fake-interceptor]))\n\n(test-router {:type :test/event})\n;; =\u003e [{:result {::my-result ::fake-result}}]\n```\n\nThe original router remains unchanged, the new router applies the same compilation and\nuses the same matcher and invoker as the original.\n\n## Brokers\n\nMailman defines several protocols for how to post and receive events.  The `EventPoster`\nis responsible for sending out events.  The `post-events` function can take a sequence\nof events to post.\n\nIt's counterpart is `EventReceiver` which allows to actively poll for events, using\n`pull-events` or to register a listener using `add-listener`.  The listener is invoked\nfor each received message, and is a 1-arity function, presumably created using `router`.\n\nMailman provides a default in-memory broker implementation that implements both of these\nprotocols.\n\n```clojure\n(require '[monkey.mailman.mem :as mb])\n\n(def broker (mb/make-memory-broker))\n\n;; Post a single event\n(mm/post-events broker [{:type ::test-event :message \"This is a test event\"}])\n\n;; Pull it\n(mm/pull-events broker 1)\n;; Returns a list holding the previously posted event\n```\n\n`pull-events` takes an additional argument indicating how many events you want to\nreceive.  If `nil` is given, it will pull all available events.  There is a shorthand\nfunction available when you want to pull one event: `pull-next`.\n\n## Listeners\n\nOften you don't want to have to manually pull in events.  For this, you can register\na listener instead, using `add-listener`.\n\n```clojure\n;; Register the previously created router as a listener\n(def l (mm/add-listener broker router))\n\n;; You can unregister the listener as well\n(mm/unregister-listener l)\n```\n\nThe listener is also a protocol, called `Listener`, which is again dependent on your\nbroker implementation.\n\n## Implementations\n\nCurrently, *Mailman* provides these implementations for its protocols:\n\n - `manifold`, based on the excellent [manifold](https://github.com/clj-commons/manifold) async library.\n - `jms`, that builds upon the [monkey-jms](https://github.com/monkey-projects/monkey-jms) library to connect to a JMS broker for messaging.\n - `nats`, which uses [Monkey Projects Nats](https://github.com/monkey-projects/nats) to connect to a [NATS](https://nats.io) broker for messaging.  See [more details here](nats/README.md).\n\nThe [manifold lib](manifold) provides an in-memory broker, similar to the one provided in the\ncore, but it's built upon Manifold streams.  Furthermore, it provides some functions\nto work with async event handlers, whereas all core functions all assume your handlers\nwork synchronously.\n\nThe [JMS lib](jms) uses JMS 3.0 to connect to a broker using AMQP protocol.  It is able\nto use queues or topics (possibly durable) for posting and receiving events.  Events are\nby default encoded using `edn`, but this can be overridden.\n\n## LICENSE\n\n[GPLv3.0 license](LICENSE)\n\nCopyright (c) 2025 by [Monkey Projects](https://www.monkey-projects.be)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonkey-projects%2Fmailman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonkey-projects%2Fmailman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonkey-projects%2Fmailman/lists"}