{"id":16400835,"url":"https://github.com/whiskeysierra/switchboard","last_synced_at":"2025-07-08T11:02:51.852Z","repository":{"id":48513116,"uuid":"97943536","full_name":"whiskeysierra/switchboard","owner":"whiskeysierra","description":null,"archived":false,"fork":false,"pushed_at":"2023-06-14T22:44:02.000Z","size":1009,"stargazers_count":3,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-10T00:19:09.682Z","etag":null,"topics":["in-memory","java","message-routing","testing"],"latest_commit_sha":null,"homepage":null,"language":"Java","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/whiskeysierra.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-21T12:02:32.000Z","updated_at":"2021-07-22T01:32:09.000Z","dependencies_parsed_at":"2022-07-30T01:38:00.491Z","dependency_job_id":null,"html_url":"https://github.com/whiskeysierra/switchboard","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whiskeysierra%2Fswitchboard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whiskeysierra%2Fswitchboard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whiskeysierra%2Fswitchboard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whiskeysierra%2Fswitchboard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/whiskeysierra","download_url":"https://codeload.github.com/whiskeysierra/switchboard/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247192411,"owners_count":20899087,"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":["in-memory","java","message-routing","testing"],"created_at":"2024-10-11T05:28:46.807Z","updated_at":"2025-04-04T14:26:02.340Z","avatar_url":"https://github.com/whiskeysierra.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Switchboard: In-Process Message Router\n\n[![Switchboard](docs/switchboard.jpg)](https://www.flickr.com/photos/justininsd/7888302222/)\n\n[![Stability: Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html)\n![Build Status](https://github.com/whiskeysierra/switchboard/workflows/build/badge.svg)\n[![Coverage Status](https://img.shields.io/coveralls/whiskeysierra/switchboard/main.svg)](https://coveralls.io/r/whiskeysierra/switchboard)\n[![Code Quality](https://img.shields.io/codacy/grade/c6117b0da34448b4823c37d53cec9109/main.svg)](https://www.codacy.com/app/whiskeysierra/switchboard)\n[![Javadoc](http://javadoc.io/badge/io.github.whiskeysierra/switchboard.svg)](http://www.javadoc.io/doc/io.github.whiskeysierra/switchboard)\n[![Release](https://img.shields.io/github/release/whiskeysierra/switchboard.svg)](https://github.com/whiskeysierra/switchboard/releases)\n[![Maven Central](https://img.shields.io/maven-central/v/io.github.whiskeysierra/switchboard.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.whiskeysierra/switchboard)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/whiskeysierra/switchboard/main/LICENSE)\n\n\u003e **Switchboard** noun, /swɪtʃ bɔːɹd/: The electronic panel that is used to direct telephone calls to the desired recipient.\n\nAn in-process message router that helps block on asynchronous events.\n    \n- **Technology stack**: Java 11+\n- **Status**:  Beta, ported from an internal implementation that is used in production\n\n## Example\n\n```java\nserver.save(\"bob\");\nUser user = switchboard.subscribe(userCreated(\"bob\"), atLeastOnce(), ofSeconds(10)).get();\n```\n\n## Features\n\n- makes asynchronous interactions more easily\n- simple, extensible API\n    \n## Origin\n\nIn any non-trivial application you'll most probably seen a process like this.\n\n![Synchronous Interaction](http://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgU3luY2hyb25vdXMgSW50ZXJhY3Rpb24KCkNsaWVudC0-U2VydmVyOiBSZXF1ZXN0CgAKBi0-RGF0YWJhc2UAEAoACggAKAxzcG9uc2UALAkATwYADgs\u0026s=napkin)\n \nSince the call to the database is synchronous we can be sure it happened before the client got the response. Testing this case is therefore relatively \nstraightforward:\n\n```java\nResponse response = server.save(bob);\nassertThat(database.getUser(\"bob\"), is(not(nullValue())));\n```\n\nIt all gets more difficult when you start to do time-consuming tasks in an asynchronous fashion:\n \n![Asynchronous Interaction](http://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgQXN5bmNocm9ub3VzIEludGVyYWN0aW9uCgpDbGllbnQtPlNlcnZlcjogUmVxdWVzdAoACgYtPkRhdGFiYXNlAAgSADQGOiBSZXNwb25zZQoAIwgAQQwAFAc\u0026s=napkin)\n \nThe test case from above may no longer work, since we may get the response back from the server before the database finished its part.\n\nThe idea of *Switchboard* is to encapsulate the necessary event-based communication behind a small API that allows to write synchronous-style assertions for \nasynchronous messages.\n\nThe term *switchboard* refers to old-style telephone switchboards, i.e. big communication devices handled by a switchboard operator that allow to connect\nmultiple parties via telephone lines, usually one caller and one receiver.\n \nIn our case those parties are threads and they *talk* to each other by passing messages.\n\n## Dependencies\n\n- Java 11 or higher\n\n## Installation\n\nAdd the following dependency to your project:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.whiskeysierra\u003c/groupId\u003e\n    \u003cartifactId\u003eswitchboard\u003c/artifactId\u003e\n    \u003cversion\u003e${switchboard.version}\u003c/version\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\n## Usage\n\nAny communication via *Switchboard* consists of two parts: [Receiving](#receiving-messages) and [sending messages](#sending-messages).\n\n### Receiving messages\n\nYou receive messages by [subscribing](#subscriptions) to it. Subscriptions can be done either in a [blocking](#blocking) or [non-blocking](#non-blocking) \nfashion. Additionally one specifies a [*subscription mode*](#subscription-modes) to indicate how much messages are going to be consumed.\n\nThink of *blocking subscriptions* as *actively sitting in front of the phone and waiting* while *non-blocking* could be seen as having *call forwarding* from\nyour home to your cell so you can do something else, while waiting for a call.\n\n#### Subscriptions\n\nA subscription is basically a key that specifies your requirements:\n\n```java\nKey\u003cUserCreated, String\u003e userCreated(String name) {\n    return Key.of(UserCreated.class, name);\n}\n```\nReceiving messages in a non-blocking way is usually required if you need to subscribe to multiple different messages:\n\n```java\nFuture\u003cUser\u003e future = switchboard.subscribe(userCreated(\"bob\"), atLeastOnce(), ofSeconds(10));\n\nfuture.get(); // wait 10 seconds\nfuture.get(5, SECONDS); // wait at most 5 seconds\n```\n\n#### Subscription Modes\n\nWhen subscribing to message you can specify one of the following modes. They have different characteristics in terms of\ntermination and success conditions:\n\n| Mode            | Termination | Success  | \n|-----------------|-------------|----------|\n| `atLeast(n)`    | `m \u003e= n`    | `m \u003e= n` |\n| `atLeastOnce()` | `m \u003e= 1`    | `m \u003e= 1` |\n| `atMost(n)`     | `m \u003e n`     | `m \u003c= n` |\n| `atMostOnce()`  | `m \u003e 1`     | `m \u003c= 1` |\n| `exactlyOnce()` | `m \u003e 1`     | `m == 1` |\n| `never()`       | `m \u003e 0`     | `m == 0` |\n| `times(n)`      | `m \u003e n`     | `m == n` |\n\n**Note**: Be ware that only `atLeast(n)` and `atLeastOnce()` have conditions that allow early termination for success cases before the timeout is reached. All others will wait for the timeout to ensure its success condition holds true. \n\n### Sending messages\n\nYou send messages by placing a *Deliverable* on the switchboard, e.g. a [*message*](#message):\n\n```java\nvar key = Key.of(UserCreated.class, user.id);\nswitchboard.publish(message(key, user));\n```\n\nSwitchboard has an answering machine builtin. That means any message that arrives without anyone receiving it right away will be recorded and delivered as soon as at least one receiver starts listening. This is especially useful if your tests need to listen to multiple messages and their order is not guaranteed.\n\n```java\nswitchboard.publish(message);\n\nString string = switchboard.subscribe(key, atLeastOnce(), ofSeconds(10));\n```\n\nThe subscriber will get the message immediately upon subscription.\n\n## Getting Help\n\nIf you have questions, concerns, bug reports, etc., please file an issue in this repository's [Issue Tracker](../../issues).\n\n## Getting Involved/Contributing\n\nTo contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For\nmore details, check the [contribution guidelines](.github/CONTRIBUTING.md).\n\n## Alternatives\n\n- [Awaitility](https://github.com/awaitility/awaitility) is a small Java DSL for synchronizing asynchronous operations\n- [Guava's EventBus](https://github.com/google/guava/wiki/EventBusExplained)  \n   See [example implementation](src/test/java/switchboard/eventbus) with *at-least-once* semantics\n\n## Credits and references\n\n![Creative Commons License](http://i.creativecommons.org/l/by-nc-sa/2.0/80x15.png)\n[Meals at all Hours](https://www.flickr.com/photos/justininsd/7888302222/) by \n[Justin Brown](https://www.flickr.com/photos/justininsd/) is licensed under\n[Attribution-NonCommercial-ShareAlike 2.0 Generic](https://creativecommons.org/licenses/by-nc-sa/2.0/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhiskeysierra%2Fswitchboard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwhiskeysierra%2Fswitchboard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhiskeysierra%2Fswitchboard/lists"}