{"id":15044773,"url":"https://github.com/appform-io/signals","last_synced_at":"2025-04-10T00:43:01.147Z","repository":{"id":57731417,"uuid":"391415391","full_name":"appform-io/signals","owner":"appform-io","description":"Loosely coupled observer pattern implementation","archived":false,"fork":false,"pushed_at":"2022-05-23T05:53:07.000Z","size":81,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T00:42:54.861Z","etag":null,"topics":["apache2","design-patterns","java","java11","observer-pattern"],"latest_commit_sha":null,"homepage":"","language":"Java","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/appform-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-07-31T16:59:42.000Z","updated_at":"2024-05-28T08:36:44.000Z","dependencies_parsed_at":"2022-09-26T22:10:24.873Z","dependency_job_id":null,"html_url":"https://github.com/appform-io/signals","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appform-io%2Fsignals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appform-io%2Fsignals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appform-io%2Fsignals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appform-io%2Fsignals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appform-io","download_url":"https://codeload.github.com/appform-io/signals/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137998,"owners_count":21053775,"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":["apache2","design-patterns","java","java11","observer-pattern"],"created_at":"2024-09-24T20:51:01.188Z","updated_at":"2025-04-10T00:43:01.128Z","avatar_url":"https://github.com/appform-io.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Signals\n\n[![Build](https://github.com/appform-io/signals/actions/workflows/sonarcloud-checks.yml/badge.svg?branch=master)](https://github.com/appform-io/signals/actions/workflows/sonarcloud-checks.yml)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=appform-io_signals\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=appform-io_signals)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=appform-io_signals\u0026metric=sqale_rating)](https://sonarcloud.io/dashboard?id=appform-io_signals)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=appform-io_signals\u0026metric=reliability_rating)](https://sonarcloud.io/dashboard?id=appform-io_signals)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=appform-io_signals\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=appform-io_signals)\n[![javadoc](https://javadoc.io/badge2/io.appform.signals/signals/javadoc.svg)](https://javadoc.io/doc/io.appform.signals/signals)\n\nA loosely coupled observer system implementation inspired by boost::signals/glibmm signals.\n\n## Why?\n\nObservers are an important part of the arsenal for any software designer and provide extraction and loose coupling of\nside-effects from operations.\n\nTypical observers need a lot of boilerplate code to register and handle observer calls. Missing safety measures can\nbreak host logic. Also, depending on use case, we might want to invoke observers in sync/async, fire-forget modes etc.\n\nSignals provides a clean abstraction to implement observers with low overhead.\n\n## Getting Started\n\nThere are 3 basic steps involved in the process:\n\n1. Create a signal\n2. Connect signal handlers to signal\n3. Dispatch the signal when you want to trigger observers\n\n### Sample Code\n\nAdd the following dependency to `pom.xml`:\n\n```xml\n\n\u003cdependency\u003e\n  \u003cgroupId\u003eio.appform.signals\u003c/groupId\u003e\n  \u003cartifactId\u003esignals\u003c/artifactId\u003e\n  \u003cversion\u003e1.4\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nThe following section is some random java code to show how it works. Usecase: trigger a handler when a button is clicked\non a window.\n\n```java\n\n@Value\npublic class Button {\n    String title;\n\n    //1. Create a signal\n    ConsumingFireForgetSignal\u003cButton\u003e onClick = new ConsumingFireForgetSignal\u003c\u003e();\n\n    public Button(final String title) {\n        this.title = title;\n    }\n\n    void click() {\n        //3. dispatch signal when you want to trigger observers\n        onClick.dispatch(this);\n    }\n}\n\n@Value\npublic class Window {\n    Button b = new Button(\"Submit\");\n\n    public Window() {\n        //2. Connect signal handlers \n        b.getOnClick().connect(button -\u003e System.out.println(\"Button clicked: \" + button.getTitle()));\n    }\n}\n\npublic class ApplicationTest {\n    @Test\n    void testW() {\n        val window = new Window();\n        window.getButton().click(); // Will print -\u003e Button clicked: Submit\n    }\n}\n\n```\n\n## Signal\n\nA signal is the global container/actor that remembers registered handlers. the `dispatch` method can be used to trigger\na signal.\n\n### Signal Handlers\n\nThere are two types of Signal Handlers provided and used in the context of different type of signals (see below).\n\n* **SignalConsumer** - A signal handler that does not return the result of computations performed on the incoming parameter.\n  Equivalent to `Consumer\u003cT\u003e` in core java.\n* **SignalHandler** - A signal handler that returns the results of computations performed on the provided parameter.\n  This is equivalent to the `Function\u003cT,R\u003e` functional type in core java. Both these types can be coded as lambdas\n  during usage.\n\n### Response Combiners\n\nResponse combiners can `assimilate` data resulting from `SignalHandler` calls and can be used to accumulate data using\nthe `assimilate(param)` call and the final result is obtained form a final call to `result()`. The library provides\ntwo `\nResponseCombiner` types:\n\n* **ConsumingNoOpCombiner** - does nothing with the response\n* **LastValueResponseCombiner** - Called in a chain, this will store the last value it encounters\n\n### Error Handlers\n\nError Handlers are used to handle exceptions (duh!!) thrown by the SignalHandler calls. The default consumer\ncalled `LoggingTaskErrorHandler` logs the error and suppresses it. As a result, it makes sure that even if one handler\nin a chain fails, the rest of the chain continues to execute.\n\n## Type of signals\n\nThere are two basic type of signals:\n\n1. Consuming Signal\n2. Generating Signal\n\nBoth are implemented with various variants wrt how they call handlers. All classes provide a default constructor where\ndefaults (as mentioned below in respective sections) are set. Please use the corresponding provided builders (created\nusing build() static method call) to customise different aspects of the Signals.\n\n### Consuming Signals\n\nConsuming signals accept a `SignalConsumer` as handler and do not respond back with any responses. There are three types\nof consuming signals depending on the mode of handler dispatch. This represents observer patterns more closely and should suffice for most use-cases.\n\n* **ConsumingSyncSignal** - A Consuming `Signal` that fires handlers in the same thread waits for them to complete. This\n  is the closest to implementing a simple observer chain in your class. The calling function will obviously get blocked\n  till all handlers execute in series. Choose this when your handlers are lightweight.\n* **ConsumingParallelSignal** - A Consuming `Signal` that fires handlers in parallel and waits for them to complete.\n  This will execute handlers in parallel. Execution order cannot be guaranteed. The calling function will block till all\n  handlers have executed. Choose this if you need all handlers to complete and they are heavy thread-safe computations.\n* **ConsumingFireForgetSignal** - A Consuming `Signal` that fires handlers in parallel and does not wait for their\n  response. All handlers will be called on a thread-pool (by default a single thread different from the calling thread).\n  Use this when you do not need guarantees on execution completion of the handlers before moving on.\n* **ScheduledSignal** - A consuming `Signal` where the handler is called at specified intervals. All handlers will be\n  called on a thread-pool (by default a single thread different from the calling thread). Use this to setup regular\n  refresh jobs etc.\n\n#### Defaults used\n\n* **Exception Handler:** LoggingTaskErrorHandler\n* **Response Combiner:** ConsumingNoOpCombiner\n\n### Generating Signals\n\nGenerating signals accept handlers of type `SignalHandler` that returns response of processing. These results are\naccumulated using the `ResponseCombiner` and the final result is returned to the caller as a return value of\nthe `dispatch(data)` call. There are two types of Generating Signals based on the mode of execution of handlers.\n\n* **GeneratingSyncSignal** - A Generating `Signal` that fires handlers in the calling thread and waits for them to\n  complete. It returns the response as obtained for a call to `ResponseCombiner.result()`.\n\n* **GeneratingParallelSignal** - * A Generating `Signal` that fires handlers in parallel and waits for them to complete.\n  It returns the response as obtained for a call to `ResponseCombiner.result()`.\n\nGenerating handlers can be used to implement decision points etc in complicated workflows where the main processing\nhalts for side effects to complete and proceeds using the data generated by them\n\n### Named Handlers\n\nThere are use cases, where you might want to register handlers to a signal and de-register them later when you are no\nlonger interested in listening to dispatches.\n\nTo handle this, new methods `connect([groupId], name, handler)` and `disconnect([groupId], name)`\nmethods have been introduced. Connect and disconnect is available on all signal types.\n\n## Language Compatibility Level\n\nJava 8\n\n## License\n\nApache License 2.0\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappform-io%2Fsignals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappform-io%2Fsignals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappform-io%2Fsignals/lists"}