{"id":16375811,"url":"https://github.com/citymonstret/rorledning","last_synced_at":"2025-03-23T03:32:45.088Z","repository":{"id":41967677,"uuid":"282321044","full_name":"Citymonstret/Rorledning","owner":"Citymonstret","description":"Java service pipeline","archived":false,"fork":false,"pushed_at":"2023-02-27T17:57:06.000Z","size":88,"stargazers_count":5,"open_issues_count":6,"forks_count":2,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-18T17:14:04.024Z","etag":null,"topics":["eventbus","java","java-api","java-eventbus","java-events","java-services"],"latest_commit_sha":null,"homepage":"","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/Citymonstret.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"Sauilitired","patreon":"IntellectualSites","open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["https://paypal.me/Sauilitired"]}},"created_at":"2020-07-24T21:41:50.000Z","updated_at":"2021-05-30T06:22:46.000Z","dependencies_parsed_at":"2024-10-28T15:23:45.001Z","dependency_job_id":null,"html_url":"https://github.com/Citymonstret/Rorledning","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/Citymonstret%2FRorledning","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Citymonstret%2FRorledning/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Citymonstret%2FRorledning/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Citymonstret%2FRorledning/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Citymonstret","download_url":"https://codeload.github.com/Citymonstret/Rorledning/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245052633,"owners_count":20553162,"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":["eventbus","java","java-api","java-eventbus","java-events","java-services"],"created_at":"2024-10-11T03:22:17.330Z","updated_at":"2025-03-23T03:32:44.617Z","avatar_url":"https://github.com/Citymonstret.png","language":"Java","funding_links":["https://github.com/sponsors/Sauilitired","https://patreon.com/IntellectualSites","https://paypal.me/Sauilitired"],"categories":[],"sub_categories":[],"readme":"# Rörledning\n\n[![CodeFactor](https://www.codefactor.io/repository/github/sauilitired/rorledning/badge/master)](https://www.codefactor.io/repository/github/sauilitired/rorledning/overview/master)\n\nThis is a library that allows you to create services, that can have several different implementations.\nA service in this case, is anything that takes in a context, and spits out some sort of result, achieving\nsome pre-determined task.\n\nExamples of services would be generators and caches.\n\n## Links\n\n- Discord: https://discord.gg/KxkjDVg\n- JavaDoc: https://plotsquared.com/docs/rörledning/\n\n## Maven\n\nRörledning is available from [IntellectualSites](https://intellectualsites.com)' maven repository:\n\n```xml\n\u003crepository\u003e\n    \u003cid\u003eintellectualsites-snapshots\u003c/id\u003e\n    \u003curl\u003ehttps://mvn.intellectualsites.com/content/repositories/snapshots\u003c/url\u003e\n\u003c/repository\u003e\n```\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.intellectualsites\u003c/groupId\u003e\n    \u003cartifactId\u003ePipeline\u003c/artifactId\u003e\n    \u003cversion\u003e1.4.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Usage\n\n### ServicePipeline\n\nAll requests start in the `ServicePipeline`. To get an instance of the `ServicePipeline`, simply use\nthe service pipeline builder.\n\n**Example:**\n\n```java\nfinal ServicePipeline servicePipeline = ServicePipeline.builder().build();\n```\n\n### Service\n\nTo implement a service, simply create an interface that extends `Service\u003cContext, Result\u003e`.\nThe context is the type that gets pumped into the service (i.e, the value you provide), and the result\nis the type that gets produced by the service.\n\nThe pipeline will attempt to generate a result from each service, until a service produces a non-null result.\nThus, if a service cannot (or shouldn't) produce a result for a given context, it can simply return null.\n\nHowever, there's a catch to this. At least one service must always provide a result for every input.\nTo ensure that this is the case, a default implementation of the service must be registered together\nwith the service type. This implementation is not allowed to return null.\n\n**Examples:**\n\nExample Service:\n\n```java\npublic interface MockService extends Service\u003cMockService.MockContext, MockService.MockResult\u003e {\n\n    class MockContext {\n\n        private final String string;\n\n        public MockContext(@Nonnull final String string) {\n            this.string = string;\n        }\n\n        @Nonnull public String getString() {\n            return this.string;\n        }\n\n    }\n\n    class MockResult {\n\n        private final int integer;\n\n        public MockResult(final int integer) {\n            this.integer = integer;\n        }\n\n        public int getInteger() {\n            return this.integer;\n        }\n\n    }\n\n}\n```\n\nExample Implementation:\n\n```java\npublic class DefaultMockService implements MockService {\n\n    @Nullable @Override public MockResult handle(@Nonnull final MockContext mockContext) {\n        return new MockResult(32);\n    }\n\n}\n```\n\nExample Registration:\n\n```java\nservicePipeline.registerServiceType(TypeToken.of(MockService.class), new DefaultMockService());\n```\n\nExample Usage:\n\n```java\nfinal int result = servicePipeline.pump(new MockService.MockContext(\"Hello\"))\n                                  .through(MockService.class)\n                                  .getResult()\n                                  .getInteger();\n```\n\n### SideEffectService\n\nSome services may just alter the state of the incoming context, without generating any (useful) result.\nThese services should extend `SideEffectService`.\n\nSideEffectService returns a State instead of a result. The service may either accept a context, in\nwhich case the execution chain is interrupted. It can also reject the context, in which case the\nother services in the execution chain will get a chance to consume it.\n\n**Example:**\n\n```java\npublic interface MockSideEffectService extends SideEffectService\u003cMockSideEffectService.MockPlayer\u003e {\n\n    class MockPlayer {\n\n        private int health;\n\n        public MockPlayer(final int health) {\n            this.health = health;\n        }\n\n        public int getHealth() {\n            return this.health;\n        }\n\n        public void setHealth(final int health) {\n            this.health = health;\n        }\n\n    }\n\n}\n\npublic class DefaultSideEffectService implements MockSideEffectService {\n\n    @Nonnull @Override public State handle(@Nonnull final MockPlayer mockPlayer) {\n        mockPlayer.setHealth(0);\n        return State.ACCEPTED;\n    }\n\n}\n```\n\n### Asynchronous Execution\n\nThe pipeline results can be evaluated asynchronously. Simple use `getResultAsynchronously()`\ninstead of `getResult()`. By default, a single threaded executor is used. A different executor\ncan be supplied to the pipeline builder.\n\n### Filters\n\nSometimes you may not want your service to respond to certain contexts. Instead of always\nreturning null in those cases, filters can be used. These are simply predicates that take in your\ncontext type, and should be registered together with your implementation.\n\n**Example:**\n\nExample Filter:\n```java\npublic class FilteredMockService implements MockService, Predicate\u003cMockService.MockContext\u003e {\n\n    @Nullable @Override public MockResult handle(@Nonnull final MockContext mockContext) {\n        return new MockResult(999);\n    }\n\n    @Override public boolean test(final MockContext mockContext) {\n        return mockContext.getString().equalsIgnoreCase(\"potato\");\n    }\n\n}\n```\n\nExample Registration:\n\n```java\nfinal FilteredMockService service = new FilteredMockService();\nfinal List\u003cPredicate\u003cMockService.MockContext\u003e\u003e predicates = Collections.singletonList(service);\nservicePipeline.registerServiceImplementation(MockService.class, service, predicates);\n```\n\n### Forwarding\n\nSometimes it may be useful to use the result produced by a service as the context for another service.\nTo make this easier, the concept of forwarding was introduced. When using `getResult()`, one can instead\nuse `forward()`, to pump the result back into the pipeline.\n\n**Examples:**\n\n```java\nservicePipeline.pump(new MockService.MockContext(\"huh\"))\n               .through(MockService.class)\n               .forward()\n               .through(MockResultConsumer.class)\n               .getResult();\n```\n\nThis can also be done asynchronously:\n\n```java\nservicePipeline.pump(new MockService.MockContext(\"Something\"))\n               .through(MockService.class)\n               .forwardAsynchronously()\n               .thenApply(pump -\u003e pump.through(MockResultConsumer.class))\n               .thenApply(ServiceSpigot::getResult)\n               .get();\n```\n\n### Priority/Ordering\n\nBy default, all service implementations will be executed in first-in-last-out order. That is,\nthe earlier the implementation was registered, the lower the priority it gets in the execution chain.\n\nThis may not always be ideal, and it is therefore possibly to override the natural ordering\nof the implementations by using the \u0026#64;Optional annotation.\n\n**Example:**\n\n```java\n@Order(ExecutionOrder.FIRST)\npublic class MockOrderedFirst implements MockService {\n\n    @Nullable @Override public MockResult handle(@Nonnull final MockContext mockContext) {\n        return new MockResult(1);\n    }\n\n}\n\n@Order(ExecutionOrder.LAST)\npublic class MockOrderedLast implements MockService {\n\n    @Nullable @Override public MockResult handle(@Nonnull final MockContext mockContext) {\n        return new MockResult(2);\n    }\n\n}\n```\n\nNo matter in which order MockOrderedFirst and MockOrderedLast are added, MockOrderedFirst will be\nhandled before MockOrderedLast.\n\nThe default order for all services is `SOON`.\n\n### Annotated Methods\n\nYou can also implement services by using instance methods, like such:\n\n```java\n@ServiceImplementation(MockService.class)\npublic MockService.MockResult handle(@Nonnull final MockService.MockContext context) {\n    return new MockService.MockResult(context.getString().length());\n}\n```\n\nThe methods can also be annotated with the order annotation. Is is very important\nthat the method return type and parameter type match up wit the service context and\nresult types, or you will get runtime exceptions when using the pipeline. \n\nThese methods are registered in ServicePipeline, using `registerMethods(yourClassInstance);`\n\n### ConsumerService\n\nConsumer services effectively turns the service pipeline into an event bus. Each implementation\nwill get a chance to consume the incoming context, unless an implementation forcefully interrupts\nthe execution, by calling `ConsumerService.interrupt()`\n\n**Examples:**\n\n```java\npublic interface MockConsumerService extends ConsumerService\u003cMockService.MockContext\u003e {\n}\n\npublic class InterruptingMockConsumer implements MockConsumerService {\n\n    @Override public void accept(@Nonnull final MockService.MockContext mockContext) {\n        ConsumerService.interrupt();\n    }\n\n}\n\npublic class StateSettingConsumerService implements MockConsumerService {\n\n    @Override public void accept(@Nonnull final MockService.MockContext mockContext) {\n        mockContext.setState(\"\");\n    }\n\n}\n```\n\n### Partial Result Services\n\nSometimes you may need to get results for multiple contexts, but there is no guarantee\nthat a single service will be able to generate all the needed results. It is then possible\nto make use of `PartialResultService`.\n\nThe partial result service interface uses the `ChunkedRequestContext` class as the input, and\noutputs a map of request-response pairs.\n\n**Example:**\n\nExample Request Type:\n\n```java\npublic class MockChunkedRequest extends ChunkedRequestContext\u003cMockChunkedRequest.Animal, MockChunkedRequest.Sound\u003e {\n\n    public MockChunkedRequest(@Nonnull final Collection\u003cAnimal\u003e requests) {\n        super(requests);\n    }\n\n\n    public static class Animal {\n\n        private final String name;\n\n        public Animal(@Nonnull final String name) {\n            this.name = name;\n        }\n\n        @Nonnull public String getName() {\n            return this.name;\n        }\n    }\n\n    public static class Sound {\n\n        private final String sound;\n\n        public Sound(@Nonnull final String sound) {\n            this.sound = sound;\n        }\n\n        @Nonnull public String getSound() {\n            return this.sound;\n        }\n    }\n}\n```\n\nExample Service:\n```java\npublic interface MockPartialResultService extends\n    PartialResultService\u003cMockChunkedRequest.Animal, MockChunkedRequest.Sound, MockChunkedRequest\u003e {\n}\n```\n\nExample Implementations:\n```java\npublic class DefaultPartialRequestService implements MockPartialResultService {\n\n    @Nonnull @Override\n    public Map\u003cMockChunkedRequest.Animal, MockChunkedRequest.Sound\u003e handleRequests(\n        @Nonnull final List\u003cMockChunkedRequest.Animal\u003e requests) {\n        final Map\u003cMockChunkedRequest.Animal, MockChunkedRequest.Sound\u003e map = new HashMap\u003c\u003e(requests.size());\n        for (final MockChunkedRequest.Animal animal : requests) {\n            map.put(animal, new MockChunkedRequest.Sound(\"unknown\"));\n        }\n        return map;\n    }\n\n}\n\npublic class CompletingPartialResultService implements MockPartialResultService {\n\n    @Nonnull @Override public Map\u003cMockChunkedRequest.Animal, MockChunkedRequest.Sound\u003e handleRequests(\n        @Nonnull List\u003cMockChunkedRequest.Animal\u003e requests) {\n        final Map\u003cMockChunkedRequest.Animal, MockChunkedRequest.Sound\u003e map = new HashMap\u003c\u003e();\n        for (final MockChunkedRequest.Animal animal : requests) {\n            if (animal.getName().equals(\"cow\")) {\n                map.put(animal, new MockChunkedRequest.Sound(\"moo\"));\n            } else if (animal.getName().equals(\"dog\")) {\n                map.put(animal, new MockChunkedRequest.Sound(\"woof\"));\n            }\n        }\n        return map;\n    }\n\n}\n```\n\n### Exception Handling\n\nExceptions thrown during result retrieval and implementation filtering will be wrapped by\n`PipelineException`. You can use `PipelineException#getCause` to get the exception that was wrapped.\n\n**Example:**\n\n```java\ntry {\n    final Result result = pipeline.pump(yourContext).through(YourService.class).getResult();\n} catch (final PipelineException exception) {\n    final Exception cause = exception.getCause();\n}\n```\n\nYou may also make use of `ServicePipeline#getException(BiConsumer\u003cResult, Throwable\u003e)`. This method\nwill unwrap any pipeline exceptions before passing them to the consumer.\n\n**Example:**\n\n```java\npipeline.getResult((result, exception) -\u003e {\n    if (exception != null) {\n        exception.printStackTrace();\n    } else {\n        // consume result\n    }\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcitymonstret%2Frorledning","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcitymonstret%2Frorledning","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcitymonstret%2Frorledning/lists"}