{"id":18300389,"url":"https://github.com/taymyr/lagom-extensions","last_synced_at":"2025-04-05T13:36:06.287Z","repository":{"id":44553051,"uuid":"148376599","full_name":"taymyr/lagom-extensions","owner":"taymyr","description":"Utilities for Lagom framework","archived":false,"fork":false,"pushed_at":"2023-06-16T09:53:46.000Z","size":263,"stargazers_count":11,"open_issues_count":2,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-21T05:32:48.890Z","etag":null,"topics":["java","kotlin","lagom"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/taymyr.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}},"created_at":"2018-09-11T20:32:10.000Z","updated_at":"2023-05-18T22:44:47.000Z","dependencies_parsed_at":"2024-11-05T15:12:20.794Z","dependency_job_id":"f0307e1c-8a19-4b70-967c-ffdae2500b4c","html_url":"https://github.com/taymyr/lagom-extensions","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taymyr%2Flagom-extensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taymyr%2Flagom-extensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taymyr%2Flagom-extensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taymyr%2Flagom-extensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/taymyr","download_url":"https://codeload.github.com/taymyr/lagom-extensions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247342710,"owners_count":20923642,"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":["java","kotlin","lagom"],"created_at":"2024-11-05T15:12:11.759Z","updated_at":"2025-04-05T13:36:05.905Z","avatar_url":"https://github.com/taymyr.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Gitter](https://img.shields.io/badge/chat-gitter-purple.svg)](https://gitter.im/taymyr/taymyr)\n[![Gitter_RU](https://img.shields.io/badge/chat-russian%20channel-purple.svg)](https://gitter.im/taymyr/taymyr_ru)\n[![Build Status](https://app.travis-ci.com/taymyr/lagom-extensions.svg?branch=master)](https://app.travis-ci.com/taymyr/lagom-extensions)\n[![Javadocs](https://www.javadoc.io/badge/org.taymyr.lagom/lagom-extensions-java_2.12.svg)](https://www.javadoc.io/doc/org.taymyr.lagom/lagom-extensions-java_2.12)\n[![Codecov](https://codecov.io/gh/taymyr/lagom-extensions/branch/master/graph/badge.svg)](https://codecov.io/gh/taymyr/lagom-extensions)\n[![Maven Central](https://img.shields.io/maven-central/v/org.taymyr.lagom/lagom-extensions-java_2.12.svg)](https://search.maven.org/search?q=a:lagom-extensions-java_2.12%20AND%20g:org.taymyr.lagom)\n\n# Lagom Java API Extensions\n\nThis library is an extension of Lagom Java/Scala DSL.\n\n_Note: We try not to change the API, but before the release of stable version `1.0.0` API may be changed._\n\n## Versions compatibility\n\n| Lagom Extensions | Lagom            | Scala          |\n|------------------|------------------|----------------|\n| 0.+              | 1.5.+ \u003cbr\u003e 1.6.+ | 2.12 \u003cbr\u003e 2.13 |\n\n## Features\n\n### Message Protocols (Java \u0026#10003; / Scala \u0026#10007; )\n\n`MessageProtocols` have constants for most used message protocols (`application/json`, `application/json; charset=utf-8`, etc).\nSee [Javadoc](https://www.javadoc.io/doc/org.taymyr.lagom/lagom-extensions-java_2.12) for more information.\n\n### Response Headers (Java \u0026#10003; / Scala \u0026#10007; )\n\n`ResponseHeaders` have constants of `ResponseHeader` and utilities functions for instantiation `Pair\u003cResponseHeader, T\u003e`.\n\nCode example:\n\n```java\n// Lagom\n(headers, request) -\u003e { \n    ...\n    return completedFuture(\n        new Pair\u003c\u003e(\n            ResponseHeader.OK.withProtocol(MessageProtocol.fromContentTypeHeader(Optional.of(\"application/json\"))), \n            result\n        )\n    );\n};\n\n// Lagom Extensions\n(headers, request) -\u003e { \n    ...\n    return completedFuture(okJson(result));\n};\n```\n\n### Simple Kafka Producer (Java \u0026#10003; / Scala \u0026#10007; )\n\nAt this moment Lagom (1.4.+) doesn't provide any framework-level API to produce records to topics declared in subscriber-only service descriptors. \nIn such cases, we need to use the underlying [Alpakka Kafka](https://doc.akka.io/docs/akka-stream-kafka/current/home.html) directly to publish.\n\n1. It is useful to place `TopicDescriptor` in the subscriber-only service descriptor.\n\n```java\npublic interface FooTopicService extends Service {\n\n    TopicDescriptor\u003cFooTopicRecord\u003e FOO_TOPIC = TopicDescriptor.of(\"foo-topic\", FooTopicRecord.class);\n    \n    Topic\u003cString\u003e fooTopic();\n\n    @Override\n    default Descriptor descriptor() {\n        return named(\"foo-topic-service\")\n            .withTopics(topic(FOO_TOPIC.getId(), this::fooTopic))\n            .withAutoAcl(true);\n    }\n}\n```\n\nAt the topic call declaration you may also specify `.withProperties(KafkaProperties.partitionKeyStrategy, ...)` to support topic record key generation (see [Lagom docs](https://www.lagomframework.com/documentation/current/java/MessageBrokerApi.html#Partitioning-topics)).\n\n2. You should inject `SimpleTopicProducersRegistry` and register producers for the declared topics (other details are intentionally omitted)\n\n```java\npublic class BarServiceImpl implements BarService {\n\n    private SimpleTopicProducersRegistry registry;\n\n    @Inject\n    public BarServiceImpl(FooTopicService fooTopicService, SimpleTopicProducersRegistry registry) {\n        this.registry = registry.register(fooTopicService);\n    }\n}\n```\n\n3. Now you able to retrieve producer for the desired topic from the registry and to publish record easily.\n\n```java\n@Override\npublic ServiceCall\u003cFooTopicRecord, NotUsed\u003e publishToFoo() {\n    return fooTopicRecord -\u003e\n            registry.get(FooTopicService.FOO_TOPIC).publish(fooTopicRecord)\n                .thenApply( x -\u003e NotUsed.getInstance() );\n}\n```\n\n4. `SimpleTopicProducer` relies on `akka.kafka.producer` config by default (see [Akka producer](https://doc.akka.io/docs/akka-stream-kafka/current/producer.html#settings), [Akka source](https://doc.akka.io/japi/akka/2.5/akka/stream/javadsl/Source.html#queue(int,akka.stream.OverflowStrategy))).\nYou also may provide a separate config for each topic producer. In that case, config path should be `\u003ctopic-name\u003e.producer` instead of `akka.kafka.producer`.\n\n```HOCON\nfoo-topic.producer {\n  # Tuning parameter of how many sends that can run in parallel.\n  parallelism = 100\n\n  # Duration to wait for `KafkaConsumer.close` to finish.\n  close-timeout = 60s\n  \n  # Fully qualified config path which holds the dispatcher configuration\n  # to be used by the producer stages. Some blocking may occur.\n  # When this value is empty, the dispatcher configured for the stream\n  # will be used.\n  use-dispatcher = \"akka.kafka.default-dispatcher\"\n\n  # The time interval to commit a transaction when using the `Transactional.sink` or `Transactional.flow`\n  eos-commit-interval = 100ms\n\n  # Size of buffer in element count\n  buffer-size = 100\n\n  # Strategy that is used when incoming elements cannot fit inside the buffer.\n  # Possible values: \"dropHead\", \"backpressure\", \"dropBuffer\", \"dropNew\", \"dropTail\", \"fail\".\n  overflow-strategy = \"dropHead\"\n\n  # Minimum (initial) duration until the child actor will started again, if it is terminated.\n  min-backoff = 3s\n\n  # The exponential back-off is capped to this duration.\n  max-backoff = 30s\n\n  # After calculation of the exponential back-off an additional random delay based on this factor is added,\n  # e.g. 0.2 adds up to 20% delay. In order to skip this additional delay pass in 0.\n  random-factor = 0.2\n\n  # Properties defined by org.apache.kafka.clients.producer.ProducerConfig\n  # can be defined in this configuration section.\n  kafka-clients {\n  }\n}\n```\n\n5. Also you can use a `serviceName` property for lookup bootstrap servers by `ServiceLocator` of _Lagom_. \nAnd you can customize the name of topic by property `topic-name` (it can be useful for using naming convensions for difference environments). \n\n```HOCON\nfoo-topic {\n  serviceName = \"kafka_native\"\n  \n  topic-name = \"foo-topic-envXY\"\n}\n```\n\n### Logging requests/responses of strict client HTTP calls with `ConfiguredAhcWSClient`\nUnfortunately out-of-the-box Lagom doesn't support request/response logging for client strict HTTP calls. \n`ConfiguredAhcWSClient` is a simple custom implementation of the `play.api.libs.ws.WSClient` which Lagom uses to perform the strict client HTTP calls. \nIt allows you to enable request/response (including the body) logging. It can be enabled in your `application.conf` as follows:\n```hocon\nconfigured-ahc-ws-client.logging.enabled = true\n```\nAlso, you can exclude some URLs by specifying a list of matching regexps.\n```hocon\nconfigured-ahc-ws-client.logging.skip-urls = [\"(foo|bar)\\\\.acme\\\\.com/some/path\"]\n```\nEnjoy!\n\n### ServiceCall running on coroutines (Java \u0026#10007; / Scala \u0026#10007; / Kotlin \u0026#10003;)\nUsing `CoroutineService` you can make requests using coroutines.\nExample:\n```kotlin\nclass TestService @Inject constructor(actorSystem: ActorSystem) : Service, CoroutineService {\n    \n    override val dispatcher: CoroutineDispatcher = actorSystem.dispatcher.asCoroutineDispatcher()\n    \n    private fun testMethod(): ServiceCall\u003cNotUsed, String\u003e = serviceCall { \n        \"Hello, from coroutine!\"\n    }\n\n    override fun descriptor(): Descriptor {\n        return Service.named(\"test-service\")\n            .withCalls(\n                Service.restCall\u003cNotUsed, String\u003e(Method.GET, \"/test\", TestService::testMethod.javaMethod)\n            )\n    }\n}\n```\nYou must define the `CoroutineDispatcher` on which the coroutines will run. Basically, you need to use akka default execution context.\n`CoroutineSecuredService` allows you to execute authorized requests from `org.pac4j.lagom`. Example:\n```kotlin\nclass TestService @Inject constructor(actorSystem: ActorSystem) : Service, CoroutineSecuredService {\n\n    override fun getSecurityConfig(): Config {\n        TODO(\"Return security config\")\n    }\n\n    override val dispatcher: CoroutineDispatcher = actorSystem.dispatcher.asCoroutineDispatcher()\n\n    private fun testMethod(): ServiceCall\u003cNotUsed, String\u003e = authenticatedServiceCall { request, profile -\u003e\n        \"Hello, from coroutine!\"\n    }\n\n    override fun descriptor(): Descriptor {\n        return Service.named(\"test-service\")\n            .withCalls(\n                Service.restCall\u003cNotUsed, String\u003e(Method.GET, \"/test\", TestService::testMethod.javaMethod)\n            )\n    }\n}\n```\nIt is also possible to set the coroutine context. To do this, you need to override the value of the `context` property.\nThis allows you to set `CoroutineContext.Element`.\nExample of changing the name of a coroutine:\n```kotlin\nclass TestService @Inject constructor(actorSystem: ActorSystem) : Service, CoroutineService {\n    \n    override val dispatcher: CoroutineDispatcher = actorSystem.dispatcher.asCoroutineDispatcher()\n\n    override val context: CoroutineContext = CoroutineName(\"custom-coroutine-name\")\n\n    private fun testMethod(): ServiceCall\u003cNotUsed, String\u003e = serviceCall {\n        \"Hello, from coroutine!\"\n    }\n\n    override fun descriptor(): Descriptor {\n        return Service.named(\"test-service\")\n            .withCalls(\n                Service.restCall\u003cNotUsed, String\u003e(Method.GET, \"/test\", TestService::testMethod.javaMethod)\n            )\n    }\n}\n```\n### The Cache API using coroutines (Java \u0026#10007; / Scala \u0026#10007; / Kotlin \u0026#10003;)\n\n`org.taymyr.lagom.kotlindsl.cache.AsyncCacheApi` allows using methods from `play.cache.AsyncCacheApi` along with suspend functions.\nTo use, you need to call the `org.taymyr.lagom.kotlindsl.cache.AsyncCacheApiKt#suspend`\n\nExample:\n```kotlin\nclass TestCache @Inject constructor(playCache: play.cache.AsyncCacheApi) {\n\n    private val cacheApi = playCache.suspend()\n\n    suspend fun cacheSomeData(someData: String) {\n        cacheApi.set(\"key\", someData)\n        cacheApi.set(\"key\", Duration.ofSeconds(10), someData)\n        cacheApi.getOrElseUpdate(\"key\") { someData }\n        cacheApi.getOrElseUpdate(\"key\", Duration.ofSeconds(10)) { someData }\n        val cacheValue = cacheApi.get\u003cString\u003e(\"key\")\n        cacheApi.remove(\"key\")\n        cacheApi.removeAll()\n    }\n}\n```\nSupported cache implementations:\n* [play-caffeine](https://github.com/playframework/playframework/tree/master/cache/play-caffeine-cache)\n* [play-redis](https://github.com/KarelCemus/play-redis)\n\n### Json-serializer that uses kotlinx-serialization (Java \u0026#10007; / Scala \u0026#10007; / Kotlin \u0026#10003;)\nUsing `KotlinJsonSerializer` you can serialize/deserialize service responses using [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization).\nSerializable classes must be annotated with `kotlinx.serialization.Serializable`, otherwise `IllegalArgumentException` exception will be thrown when the service starts.\nFor create `KotlinJsonSerializer`, you need to use the function `KotlinJsonSerializer.serializer`.\nTo set the message serializer, you need to use the extension function `withKotlinJsonSerializer` for `Descriptor`.\nBut this function is not intended for parameterized types, since Lagom will use one serializer for all variants.\nTherefore, using `withKotlinJsonSerializer` with parameterized types will throw an `UnsupportedOperationException`.\nFor parameterized types(and not only), you need to use the extension functions `withRequestKotlinJsonSerializer`, `withResponseKotlinJsonSerializer` for `Descriptor.Call`.\n\nExample:\n```kotlin\n@Serializable\ndata class TestData(\n    val field1: String,\n    val field2: Int\n)\n\n@Serializable\ndata class TestGenericData\u003cT\u003e(val data: T)\n\nval json = Json { ignoreUnknownKeys = true }\n\ninterface TestService : Service {\n\n    fun testSerialization(): ServiceCall\u003cTestData, TestData\u003e\n\n    fun testGenericSerialization(): ServiceCall\u003cTestGenericData\u003cTestData\u003e, TestGenericData\u003cTestData\u003e\u003e\n\n    override fun descriptor(): Descriptor = named(\"test-service\").withCalls(\n        restCall\u003cTestData, TestData\u003e(\n            Method.POST,\n            \"/api/test/serialization\",\n            TestService::testSerialization.javaMethod\n        ),\n        restCall\u003cTestGenericData\u003cTestData\u003e, TestGenericData\u003cTestData\u003e\u003e(\n            Method.POST,\n            \"/api/test/serialization/generic\",\n            TestService::testGenericSerialization.javaMethod\n        ).withRequestKotlinJsonSerializer(json)\n            .withResponseKotlinJsonSerializer(json),\n    ).withKotlinJsonSerializer\u003cTestData\u003e(json)\n}\n\n```\n\n## How to use\n\nAll **released** artifacts are available in the [Maven central repository](https://search.maven.org/search?q=a:lagom-extensions-java_2.12%20AND%20g:org.taymyr.lagom).\nJust add a `lagom-extensions` to your service dependencies:\n\n* **SBT**\n\n```scala\nlibraryDependencies += \"org.taymyr.lagom\" %% \"lagom-extensions-java\" % \"X.Y.Z\"\n```\n\n* **Maven**\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eorg.taymyr.lagom\u003c/groupId\u003e\n  \u003cartifactId\u003elagom-extensions-java_${scala.binary.version}\u003c/artifactId\u003e\n  \u003cversion\u003eX.Y.Z\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nAll **snapshot** artifacts are available in the [Sonatype snapshots repository](https://oss.sonatype.org/content/repositories/snapshots/org/taymyr/lagom).\nThis repository must be added in your build system. \n\n* **SBT**\n\n```scala\nresolvers ++= Resolver.sonatypeRepo(\"snapshots\")\n```\n\n* **Maven**\n```xml\n\u003crepositories\u003e\n  \u003crepository\u003e\n    \u003cid\u003esnapshots-repo\u003c/id\u003e\n    \u003curl\u003ehttps://oss.sonatype.org/content/repositories/snapshots\u003c/url\u003e\n    \u003creleases\u003e\u003cenabled\u003efalse\u003c/enabled\u003e\u003c/releases\u003e\n    \u003csnapshots\u003e\u003cenabled\u003etrue\u003c/enabled\u003e\u003c/snapshots\u003e\n  \u003c/repository\u003e\n\u003c/repositories\u003e\n``` \n\n## Contributions\n\nContributions are very welcome.\n\n## License\n\nCopyright © 2018-2020 Digital Economy League (https://www.digitalleague.ru/).\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this project except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaymyr%2Flagom-extensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftaymyr%2Flagom-extensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaymyr%2Flagom-extensions/lists"}