From 1a3acfc437c5a2d86fd0e265acf719e7b1f11359 Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Thu, 2 Jan 2025 15:50:58 +0100 Subject: [PATCH 1/9] feat: @Profile annotation to enable/disable components --- .../java/akkajavasdk/SdkIntegrationTest.java | 73 ++++++++++++------- .../user/ProdCounterEntity.java | 29 ++++++++ .../user/TestCounterEntity.java | 29 ++++++++ .../META-INF/akka-javasdk-components.conf | 2 + .../src/test/resources/application.conf | 1 + .../akka/javasdk/annotations/Profile.java | 31 ++++++++ .../scala/akka/javasdk/impl/SdkRunner.scala | 25 ++++++- .../scala/akka/javasdk/impl/Settings.scala | 6 +- 8 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/ProdCounterEntity.java create mode 100644 akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/TestCounterEntity.java create mode 100644 akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java diff --git a/akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java b/akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java index ed51bb45e..7ca30bdba 100644 --- a/akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java +++ b/akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java @@ -14,6 +14,8 @@ import akkajavasdk.components.eventsourcedentities.counter.Counter; import akkajavasdk.components.eventsourcedentities.counter.CounterEntity; import akkajavasdk.components.keyvalueentities.customer.CustomerEntity; +import akkajavasdk.components.keyvalueentities.user.ProdCounterEntity; +import akkajavasdk.components.keyvalueentities.user.TestCounterEntity; import akkajavasdk.components.keyvalueentities.user.User; import akkajavasdk.components.keyvalueentities.user.UserEntity; import akkajavasdk.components.keyvalueentities.user.UserSideEffect; @@ -23,6 +25,7 @@ import org.hamcrest.core.IsEqual; import org.hamcrest.core.IsNull; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,6 +41,7 @@ @ExtendWith(Junit5LogCapturing.class) public class SdkIntegrationTest extends TestKitSupport { + @Override protected TestKit.Settings testKitSettings() { // here only to show how to set different `Settings` in a test. @@ -45,6 +49,29 @@ protected TestKit.Settings testKitSettings() { .withTopicOutgoingMessages(CUSTOMERS_TOPIC); } + @Test + public void verifyIfComponentIsActiveBasedOnProfile() { + + var result = await(componentClient.forKeyValueEntity("test") + .method(TestCounterEntity::get) + .invokeAsync()); + + assertThat(result).isEqualTo(100); + } + + @Test + public void verifyIfComponentIsDisabledBasedOnProfile() { + + + var exc = Assertions.assertThrows(IllegalArgumentException.class, () -> { + await(componentClient.forKeyValueEntity("test") + .method(ProdCounterEntity::get) + .invokeAsync()); + }); + + assertThat(exc.getMessage()).contains("Unknown entity type [prod-counter]"); + } + @Test public void verifyEchoActionWiring() { @@ -65,41 +92,41 @@ public void verifyEchoActionWiring() { public void verifyHierarchyTimedActionWiring() { timerScheduler.startSingleTimer("wired", ofMillis(0), componentClient.forTimedAction() - .method(HierarchyTimed::stringMessage) - .deferred("hello")); + .method(HierarchyTimed::stringMessage) + .deferred("hello")); Awaitility.await() - .atMost(20, TimeUnit.SECONDS) - .untilAsserted(() -> { - var value = StaticTestBuffer.getValue("hierarchy-action"); - assertThat(value).isEqualTo("hello"); - }); + .atMost(20, TimeUnit.SECONDS) + .untilAsserted(() -> { + var value = StaticTestBuffer.getValue("hierarchy-action"); + assertThat(value).isEqualTo("hello"); + }); } @Test public void verifyTimedActionListCommand() { timerScheduler.startSingleTimer("echo-action", ofMillis(0), componentClient.forTimedAction() - .method(EchoAction::stringMessages) - .deferred(List.of("hello", "mr"))); + .method(EchoAction::stringMessages) + .deferred(List.of("hello", "mr"))); Awaitility.await() - .atMost(20, TimeUnit.SECONDS) - .untilAsserted(() -> { - var value = StaticTestBuffer.getValue("echo-action"); - assertThat(value).isEqualTo("hello mr"); - }); + .atMost(20, TimeUnit.SECONDS) + .untilAsserted(() -> { + var value = StaticTestBuffer.getValue("echo-action"); + assertThat(value).isEqualTo("hello mr"); + }); timerScheduler.startSingleTimer("echo-action", ofMillis(0), componentClient.forTimedAction() - .method(EchoAction::commandMessages) - .deferred(List.of(new EchoAction.SomeCommand("tambourine"), new EchoAction.SomeCommand("man")))); + .method(EchoAction::commandMessages) + .deferred(List.of(new EchoAction.SomeCommand("tambourine"), new EchoAction.SomeCommand("man")))); Awaitility.await() - .atMost(20, TimeUnit.SECONDS) - .untilAsserted(() -> { - var value = StaticTestBuffer.getValue("echo-action"); - assertThat(value).isEqualTo("tambourine man"); - }); + .atMost(20, TimeUnit.SECONDS) + .untilAsserted(() -> { + var value = StaticTestBuffer.getValue("echo-action"); + assertThat(value).isEqualTo("tambourine man"); + }); } @Test @@ -195,9 +222,6 @@ public void verifyFindCounterByValue() { } - - - @Test public void verifyUserSubscriptionAction() { @@ -221,7 +245,6 @@ public void verifyUserSubscriptionAction() { } - @Test public void verifyActionWithMetadata() { diff --git a/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/ProdCounterEntity.java b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/ProdCounterEntity.java new file mode 100644 index 000000000..df4a8fdd3 --- /dev/null +++ b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/ProdCounterEntity.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021-2024 Lightbend Inc. + */ + +package akkajavasdk.components.keyvalueentities.user; + +import akka.javasdk.annotations.ComponentId; +import akka.javasdk.annotations.Profile; +import akka.javasdk.keyvalueentity.KeyValueEntity; +import akka.javasdk.keyvalueentity.KeyValueEntityContext; + +@ComponentId("prod-counter") +@Profile("prod") +public class ProdCounterEntity extends KeyValueEntity { + private final String entityId; + + public ProdCounterEntity(KeyValueEntityContext context) { + this.entityId = context.entityId(); + } + + @Override + public Integer emptyState() { + return 100; + } + + public Effect get() { + return effects().reply(currentState()); + } +} diff --git a/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/TestCounterEntity.java b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/TestCounterEntity.java new file mode 100644 index 000000000..cde3d9cf4 --- /dev/null +++ b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/TestCounterEntity.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021-2024 Lightbend Inc. + */ + +package akkajavasdk.components.keyvalueentities.user; + +import akka.javasdk.annotations.ComponentId; +import akka.javasdk.annotations.Profile; +import akka.javasdk.keyvalueentity.KeyValueEntity; +import akka.javasdk.keyvalueentity.KeyValueEntityContext; + +@ComponentId("test-counter") +@Profile("test") +public class TestCounterEntity extends KeyValueEntity { + private final String entityId; + + public TestCounterEntity(KeyValueEntityContext context) { + this.entityId = context.entityId(); + } + + @Override + public Integer emptyState() { + return 100; + } + + public Effect get() { + return effects().reply(currentState()); + } +} diff --git a/akka-javasdk-tests/src/test/resources/META-INF/akka-javasdk-components.conf b/akka-javasdk-tests/src/test/resources/META-INF/akka-javasdk-components.conf index 990278add..0c0030f31 100644 --- a/akka-javasdk-tests/src/test/resources/META-INF/akka-javasdk-components.conf +++ b/akka-javasdk-tests/src/test/resources/META-INF/akka-javasdk-components.conf @@ -32,6 +32,8 @@ akka.javasdk { "akkajavasdk.components.keyvalueentities.user.AssignedCounterEntity", "akkajavasdk.components.keyvalueentities.hierarchy.TextKvEntity", "akkajavasdk.components.views.AllTheTypesKvEntity" + "akkajavasdk.components.keyvalueentities.user.TestCounterEntity" + "akkajavasdk.components.keyvalueentities.user.ProdCounterEntity" ] view = [ "akkajavasdk.components.views.user.UsersByEmailAndName", diff --git a/akka-javasdk-tests/src/test/resources/application.conf b/akka-javasdk-tests/src/test/resources/application.conf index e7446a237..68247a851 100644 --- a/akka-javasdk-tests/src/test/resources/application.conf +++ b/akka-javasdk-tests/src/test/resources/application.conf @@ -1,2 +1,3 @@ # Using a different port to not conflict with parallel tests akka.javasdk.testkit.http-port = 39391 +akka.javasdk.profiles.active = test diff --git a/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java b/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java new file mode 100644 index 000000000..be41ebb29 --- /dev/null +++ b/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021-2024 Lightbend Inc. + */ + +package akka.javasdk.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Set profiles to a component. This can be used to enable or disable components based on the configuration. + * A profile expression allows to set more than one profile, for example "prod & gcp". + * + * Configuration which profiles are active can be set in the application.conf configuration file e.g. + * + * akka.javasdk.profiles.active = prod,gcp + * + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Profile { + /** + * The set of profiles for which the annotated component should be enabled. + */ + String[] value(); +} diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala index 06a49a5f2..7b9a97bea 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala @@ -8,6 +8,7 @@ import java.lang.reflect.Constructor import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.util.concurrent.CompletionStage + import scala.annotation.nowarn import scala.concurrent.ExecutionContext import scala.concurrent.Future @@ -17,6 +18,7 @@ import scala.jdk.FutureConverters._ import scala.jdk.OptionConverters.RichOptional import scala.reflect.ClassTag import scala.util.control.NonFatal + import akka.Done import akka.actor.typed.ActorSystem import akka.annotation.InternalApi @@ -94,11 +96,13 @@ import io.opentelemetry.context.{ Context => OtelContext } import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level - import java.util import java.util.Optional + import scala.jdk.OptionConverters.RichOption +import akka.javasdk.annotations.Profile + /** * INTERNAL API */ @@ -354,6 +358,24 @@ private final class Sdk( } } + private def hasCorrectProfile(clz: Class[_]): Boolean = { + if (clz.hasAnnotation[Profile]) { + val profiles = clz.getAnnotation(classOf[Profile]).value + if (profiles.isEmpty || profiles.forall(sdkSettings.profiles.contains)) { + true + } else { + logger.info( + "Ignoring component [{}] as it has the a profiles [{}], active profiles [{}]", + clz.getName, + profiles.mkString(", "), + sdkSettings.profiles.mkString(", ")) + false + } + } else { + true + } + } + // command handlers candidate must have 0 or 1 parameter and return the components effect type // we might later revisit this, instead of single param, we can require (State, Cmd) => Effect like in Akka def isCommandHandlerCandidate[E](method: Method)(implicit effectType: ClassTag[E]): Boolean = { @@ -417,6 +439,7 @@ private final class Sdk( componentClasses .filter(hasComponentId) + .filter(hasCorrectProfile) .foreach { case clz if classOf[EventSourcedEntity[_, _]].isAssignableFrom(clz) => val componentId = clz.getAnnotation(classOf[ComponentId]).value diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala index 19617081b..121d75448 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala @@ -23,7 +23,8 @@ private[impl] object Settings { devModeSettings = Option.when(sdkConfig.getBoolean("dev-mode.enabled"))( DevModeSettings( serviceName = sdkConfig.getString("dev-mode.service-name"), - httpPort = sdkConfig.getInt("dev-mode.http-port")))) + httpPort = sdkConfig.getInt("dev-mode.http-port"))), + profiles = Option(sdkConfig.getString("profiles.active")).fold(Seq.empty[String])(_.split(",").toSeq)) } final case class DevModeSettings(serviceName: String, httpPort: Int) @@ -36,4 +37,5 @@ private[impl] object Settings { private[impl] final case class Settings( cleanupDeletedEventSourcedEntityAfter: Duration, cleanupDeletedKeyValueEntityAfter: Duration, - devModeSettings: Option[DevModeSettings]) + devModeSettings: Option[DevModeSettings], + profiles: Seq[String]) From 27cf33c22077fe78cb92c432398f5cd2dc19c7d0 Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Fri, 3 Jan 2025 16:09:01 +0100 Subject: [PATCH 2/9] updating reference --- akka-javasdk/src/main/resources/reference.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/akka-javasdk/src/main/resources/reference.conf b/akka-javasdk/src/main/resources/reference.conf index 67f30f081..ca968095d 100644 --- a/akka-javasdk/src/main/resources/reference.conf +++ b/akka-javasdk/src/main/resources/reference.conf @@ -92,4 +92,6 @@ akka.javasdk { collector-endpoint = ${?COLLECTOR_ENDPOINT} } } + # The comma separated active profiles to use, used to enable or disable certain components + profiles.active = "" } From 060aaab807b6a89aca385cb23d4d15a2f118fbb3 Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Tue, 7 Jan 2025 17:15:26 +0100 Subject: [PATCH 3/9] PR comments --- .../src/main/java/akka/javasdk/annotations/Profile.java | 6 ++++-- .../src/main/scala/akka/javasdk/impl/SdkRunner.scala | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java b/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java index be41ebb29..bcb0ffdfe 100644 --- a/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java +++ b/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java @@ -15,9 +15,10 @@ * A profile expression allows to set more than one profile, for example "prod & gcp". * * Configuration which profiles are active can be set in the application.conf configuration file e.g. - * + *
{@code
  * akka.javasdk.profiles.active = prod,gcp
- * 
+ * }
+ * 
* */ @Target(ElementType.TYPE) @@ -26,6 +27,7 @@ public @interface Profile { /** * The set of profiles for which the annotated component should be enabled. + * The component will be enabled if all the profiles are active. */ String[] value(); } diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala index 7b9a97bea..a18c8b3e5 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala @@ -365,7 +365,7 @@ private final class Sdk( true } else { logger.info( - "Ignoring component [{}] as it has the a profiles [{}], active profiles [{}]", + "Ignoring component [{}] as it requires profiles [{}] to be activated, but only [{}] are active", clz.getName, profiles.mkString(", "), sdkSettings.profiles.mkString(", ")) From daeadb9d727fc9cba874144e2caf80e1a8c3acb7 Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Thu, 9 Jan 2025 10:22:21 +0100 Subject: [PATCH 4/9] feat: component exclusion config --- .../java/akkajavasdk/SdkIntegrationTest.java | 18 +++++++--- .../user/ProdCounterEntity.java | 2 -- .../user/StageCounterEntity.java | 27 +++++++++++++++ .../user/TestCounterEntity.java | 2 -- .../META-INF/akka-javasdk-components.conf | 1 + .../src/test/resources/application.conf | 1 + .../akka/javasdk/annotations/Profile.java | 33 ------------------- .../src/main/resources/reference.conf | 4 +-- .../scala/akka/javasdk/impl/SdkRunner.scala | 32 +++++++----------- .../scala/akka/javasdk/impl/Settings.scala | 5 +-- 10 files changed, 58 insertions(+), 67 deletions(-) create mode 100644 akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/StageCounterEntity.java delete mode 100644 akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java diff --git a/akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java b/akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java index 7ca30bdba..5758353d7 100644 --- a/akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java +++ b/akka-javasdk-tests/src/test/java/akkajavasdk/SdkIntegrationTest.java @@ -15,6 +15,7 @@ import akkajavasdk.components.eventsourcedentities.counter.CounterEntity; import akkajavasdk.components.keyvalueentities.customer.CustomerEntity; import akkajavasdk.components.keyvalueentities.user.ProdCounterEntity; +import akkajavasdk.components.keyvalueentities.user.StageCounterEntity; import akkajavasdk.components.keyvalueentities.user.TestCounterEntity; import akkajavasdk.components.keyvalueentities.user.User; import akkajavasdk.components.keyvalueentities.user.UserEntity; @@ -50,7 +51,7 @@ protected TestKit.Settings testKitSettings() { } @Test - public void verifyIfComponentIsActiveBasedOnProfile() { + public void verifyIfComponentIsActiveBasedOnConfig() { var result = await(componentClient.forKeyValueEntity("test") .method(TestCounterEntity::get) @@ -60,16 +61,23 @@ public void verifyIfComponentIsActiveBasedOnProfile() { } @Test - public void verifyIfComponentIsDisabledBasedOnProfile() { + public void verifyIfComponentIsDisabledBasedOnConfig() { - - var exc = Assertions.assertThrows(IllegalArgumentException.class, () -> { + var exc1 = Assertions.assertThrows(IllegalArgumentException.class, () -> { await(componentClient.forKeyValueEntity("test") .method(ProdCounterEntity::get) .invokeAsync()); }); - assertThat(exc.getMessage()).contains("Unknown entity type [prod-counter]"); + assertThat(exc1.getMessage()).contains("Unknown entity type [prod-counter]"); + + var exc2 = Assertions.assertThrows(IllegalArgumentException.class, () -> { + await(componentClient.forKeyValueEntity("test") + .method(StageCounterEntity::get) + .invokeAsync()); + }); + + assertThat(exc2.getMessage()).contains("Unknown entity type [stage-counter]"); } diff --git a/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/ProdCounterEntity.java b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/ProdCounterEntity.java index df4a8fdd3..ef308aca3 100644 --- a/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/ProdCounterEntity.java +++ b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/ProdCounterEntity.java @@ -5,12 +5,10 @@ package akkajavasdk.components.keyvalueentities.user; import akka.javasdk.annotations.ComponentId; -import akka.javasdk.annotations.Profile; import akka.javasdk.keyvalueentity.KeyValueEntity; import akka.javasdk.keyvalueentity.KeyValueEntityContext; @ComponentId("prod-counter") -@Profile("prod") public class ProdCounterEntity extends KeyValueEntity { private final String entityId; diff --git a/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/StageCounterEntity.java b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/StageCounterEntity.java new file mode 100644 index 000000000..88e707095 --- /dev/null +++ b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/StageCounterEntity.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021-2024 Lightbend Inc. + */ + +package akkajavasdk.components.keyvalueentities.user; + +import akka.javasdk.annotations.ComponentId; +import akka.javasdk.keyvalueentity.KeyValueEntity; +import akka.javasdk.keyvalueentity.KeyValueEntityContext; + +@ComponentId("stage-counter") +public class StageCounterEntity extends KeyValueEntity { + private final String entityId; + + public StageCounterEntity(KeyValueEntityContext context) { + this.entityId = context.entityId(); + } + + @Override + public Integer emptyState() { + return 100; + } + + public Effect get() { + return effects().reply(currentState()); + } +} diff --git a/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/TestCounterEntity.java b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/TestCounterEntity.java index cde3d9cf4..795587220 100644 --- a/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/TestCounterEntity.java +++ b/akka-javasdk-tests/src/test/java/akkajavasdk/components/keyvalueentities/user/TestCounterEntity.java @@ -5,12 +5,10 @@ package akkajavasdk.components.keyvalueentities.user; import akka.javasdk.annotations.ComponentId; -import akka.javasdk.annotations.Profile; import akka.javasdk.keyvalueentity.KeyValueEntity; import akka.javasdk.keyvalueentity.KeyValueEntityContext; @ComponentId("test-counter") -@Profile("test") public class TestCounterEntity extends KeyValueEntity { private final String entityId; diff --git a/akka-javasdk-tests/src/test/resources/META-INF/akka-javasdk-components.conf b/akka-javasdk-tests/src/test/resources/META-INF/akka-javasdk-components.conf index 0c0030f31..047a9543d 100644 --- a/akka-javasdk-tests/src/test/resources/META-INF/akka-javasdk-components.conf +++ b/akka-javasdk-tests/src/test/resources/META-INF/akka-javasdk-components.conf @@ -33,6 +33,7 @@ akka.javasdk { "akkajavasdk.components.keyvalueentities.hierarchy.TextKvEntity", "akkajavasdk.components.views.AllTheTypesKvEntity" "akkajavasdk.components.keyvalueentities.user.TestCounterEntity" + "akkajavasdk.components.keyvalueentities.user.StageCounterEntity" "akkajavasdk.components.keyvalueentities.user.ProdCounterEntity" ] view = [ diff --git a/akka-javasdk-tests/src/test/resources/application.conf b/akka-javasdk-tests/src/test/resources/application.conf index 68247a851..b026b7ea6 100644 --- a/akka-javasdk-tests/src/test/resources/application.conf +++ b/akka-javasdk-tests/src/test/resources/application.conf @@ -1,3 +1,4 @@ # Using a different port to not conflict with parallel tests akka.javasdk.testkit.http-port = 39391 akka.javasdk.profiles.active = test +akka.javasdk.components.exclude = "akkajavasdk.components.keyvalueentities.user.ProdCounterEntity,akkajavasdk.components.keyvalueentities.user.StageCounterEntity" diff --git a/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java b/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java deleted file mode 100644 index bcb0ffdfe..000000000 --- a/akka-javasdk/src/main/java/akka/javasdk/annotations/Profile.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2021-2024 Lightbend Inc. - */ - -package akka.javasdk.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Set profiles to a component. This can be used to enable or disable components based on the configuration. - * A profile expression allows to set more than one profile, for example "prod & gcp". - * - * Configuration which profiles are active can be set in the application.conf configuration file e.g. - *
{@code
- * akka.javasdk.profiles.active = prod,gcp
- * }
- * 
- * - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Profile { - /** - * The set of profiles for which the annotated component should be enabled. - * The component will be enabled if all the profiles are active. - */ - String[] value(); -} diff --git a/akka-javasdk/src/main/resources/reference.conf b/akka-javasdk/src/main/resources/reference.conf index ca968095d..163b7a9f2 100644 --- a/akka-javasdk/src/main/resources/reference.conf +++ b/akka-javasdk/src/main/resources/reference.conf @@ -92,6 +92,6 @@ akka.javasdk { collector-endpoint = ${?COLLECTOR_ENDPOINT} } } - # The comma separated active profiles to use, used to enable or disable certain components - profiles.active = "" + # The comma separated list of FQCNs of components to exclude from running + akka.javasdk.components.exclude = "" } diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala index a18c8b3e5..dd0e2e4e1 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala @@ -7,6 +7,8 @@ package akka.javasdk.impl import java.lang.reflect.Constructor import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method +import java.util +import java.util.Optional import java.util.concurrent.CompletionStage import scala.annotation.nowarn @@ -15,6 +17,7 @@ import scala.concurrent.Future import scala.concurrent.Promise import scala.jdk.CollectionConverters._ import scala.jdk.FutureConverters._ +import scala.jdk.OptionConverters.RichOption import scala.jdk.OptionConverters.RichOptional import scala.reflect.ClassTag import scala.util.control.NonFatal @@ -85,8 +88,8 @@ import akka.runtime.sdk.spi.SpiWorkflow import akka.runtime.sdk.spi.StartContext import akka.runtime.sdk.spi.TimedActionDescriptor import akka.runtime.sdk.spi.UserFunctionError -import akka.runtime.sdk.spi.views.SpiViewDescriptor import akka.runtime.sdk.spi.WorkflowDescriptor +import akka.runtime.sdk.spi.views.SpiViewDescriptor import akka.stream.Materializer import com.typesafe.config.Config import com.typesafe.config.ConfigFactory @@ -96,12 +99,6 @@ import io.opentelemetry.context.{ Context => OtelContext } import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level -import java.util -import java.util.Optional - -import scala.jdk.OptionConverters.RichOption - -import akka.javasdk.annotations.Profile /** * INTERNAL API @@ -358,19 +355,11 @@ private final class Sdk( } } - private def hasCorrectProfile(clz: Class[_]): Boolean = { - if (clz.hasAnnotation[Profile]) { - val profiles = clz.getAnnotation(classOf[Profile]).value - if (profiles.isEmpty || profiles.forall(sdkSettings.profiles.contains)) { - true - } else { - logger.info( - "Ignoring component [{}] as it requires profiles [{}] to be activated, but only [{}] are active", - clz.getName, - profiles.mkString(", "), - sdkSettings.profiles.mkString(", ")) - false - } + private def isNotExcluded(clz: Class[_]): Boolean = { + val componentName = clz.getName + if (sdkSettings.excludedComponents.contains(componentName)) { + logger.info("Ignoring component [{}] as it is excluded in the configuration", clz.getName) + false } else { true } @@ -439,7 +428,7 @@ private final class Sdk( componentClasses .filter(hasComponentId) - .filter(hasCorrectProfile) + .filter(isNotExcluded) .foreach { case clz if classOf[EventSourcedEntity[_, _]].isAssignableFrom(clz) => val componentId = clz.getAnnotation(classOf[ComponentId]).value @@ -569,6 +558,7 @@ private final class Sdk( private val viewDescriptors: Seq[SpiViewDescriptor] = componentClasses .filter(hasComponentId) + .filter(isNotExcluded) .collect { case clz if classOf[View].isAssignableFrom(clz) => ViewDescriptorFactory(clz, serializer, sdkExecutionContext) } diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala index 121d75448..e9e6aefa8 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala @@ -24,7 +24,8 @@ private[impl] object Settings { DevModeSettings( serviceName = sdkConfig.getString("dev-mode.service-name"), httpPort = sdkConfig.getInt("dev-mode.http-port"))), - profiles = Option(sdkConfig.getString("profiles.active")).fold(Seq.empty[String])(_.split(",").toSeq)) + excludedComponents = + Option(sdkConfig.getString("components.exclude")).fold(Seq.empty[String])(_.split(",").toSeq)) } final case class DevModeSettings(serviceName: String, httpPort: Int) @@ -38,4 +39,4 @@ private[impl] final case class Settings( cleanupDeletedEventSourcedEntityAfter: Duration, cleanupDeletedKeyValueEntityAfter: Duration, devModeSettings: Option[DevModeSettings], - profiles: Seq[String]) + excludedComponents: Seq[String]) From 7d381d3f53b0dbcc1585d77b92650162ee454aac Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Thu, 9 Jan 2025 11:53:57 +0100 Subject: [PATCH 5/9] improvements after code review --- .../scala/akka/javasdk/impl/SdkRunner.scala | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala index dd0e2e4e1..7d70ee9ff 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala @@ -355,13 +355,13 @@ private final class Sdk( } } - private def isNotExcluded(clz: Class[_]): Boolean = { + private def isExcluded(clz: Class[_]): Boolean = { val componentName = clz.getName if (sdkSettings.excludedComponents.contains(componentName)) { logger.info("Ignoring component [{}] as it is excluded in the configuration", clz.getName) - false - } else { true + } else { + false } } @@ -416,6 +416,7 @@ private final class Sdk( // collect all Endpoints and compose them to build a larger router private val httpEndpointDescriptors = componentClasses .filter(Reflect.isRestEndpoint) + .filterNot(isExcluded) .map { httpEndpointClass => HttpEndpointDescriptorFactory(httpEndpointClass, httpEndpointFactory(httpEndpointClass)) } @@ -425,10 +426,11 @@ private final class Sdk( private var workflowDescriptors = Vector.empty[WorkflowDescriptor] private var timedActionDescriptors = Vector.empty[TimedActionDescriptor] private var consumerDescriptors = Vector.empty[ConsumerDescriptor] + private var viewDescriptors = Vector.empty[SpiViewDescriptor] componentClasses .filter(hasComponentId) - .filter(isNotExcluded) + .filterNot(isExcluded) .foreach { case clz if classOf[EventSourcedEntity[_, _]].isAssignableFrom(clz) => val componentId = clz.getAnnotation(classOf[ComponentId]).value @@ -547,6 +549,9 @@ private final class Sdk( consumerDestination(consumerClass), timedActionSpi) + case clz if classOf[View].isAssignableFrom(clz) => + viewDescriptors :+= ViewDescriptorFactory(clz, serializer, sdkExecutionContext) + case clz if Reflect.isRestEndpoint(clz) => // handled separately because ComponentId is not mandatory @@ -555,14 +560,6 @@ private final class Sdk( logger.warn("Unknown component [{}]", clz.getName) } - private val viewDescriptors: Seq[SpiViewDescriptor] = - componentClasses - .filter(hasComponentId) - .filter(isNotExcluded) - .collect { - case clz if classOf[View].isAssignableFrom(clz) => ViewDescriptorFactory(clz, serializer, sdkExecutionContext) - } - // these are available for injecting in all kinds of component that are primarily // for side effects // Note: config is also always available through the combination with user DI way down below From 1ca5d92d0b369fa521320f9a0cebc1ee66fa4a0b Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Thu, 9 Jan 2025 13:33:43 +0100 Subject: [PATCH 6/9] fixing reference conf --- akka-javasdk/src/main/resources/reference.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-javasdk/src/main/resources/reference.conf b/akka-javasdk/src/main/resources/reference.conf index 163b7a9f2..cb9efbb13 100644 --- a/akka-javasdk/src/main/resources/reference.conf +++ b/akka-javasdk/src/main/resources/reference.conf @@ -93,5 +93,5 @@ akka.javasdk { } } # The comma separated list of FQCNs of components to exclude from running - akka.javasdk.components.exclude = "" + components.exclude = "" } From 1b4f16af2ccfb53d681484bc2484532e5b088799 Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Thu, 9 Jan 2025 15:03:26 +0100 Subject: [PATCH 7/9] pr comments --- akka-javasdk-tests/src/test/resources/application.conf | 1 - akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/akka-javasdk-tests/src/test/resources/application.conf b/akka-javasdk-tests/src/test/resources/application.conf index b026b7ea6..660f145c2 100644 --- a/akka-javasdk-tests/src/test/resources/application.conf +++ b/akka-javasdk-tests/src/test/resources/application.conf @@ -1,4 +1,3 @@ # Using a different port to not conflict with parallel tests akka.javasdk.testkit.http-port = 39391 -akka.javasdk.profiles.active = test akka.javasdk.components.exclude = "akkajavasdk.components.keyvalueentities.user.ProdCounterEntity,akkajavasdk.components.keyvalueentities.user.StageCounterEntity" diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala index e9e6aefa8..27cb6b9f5 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala @@ -24,8 +24,7 @@ private[impl] object Settings { DevModeSettings( serviceName = sdkConfig.getString("dev-mode.service-name"), httpPort = sdkConfig.getInt("dev-mode.http-port"))), - excludedComponents = - Option(sdkConfig.getString("components.exclude")).fold(Seq.empty[String])(_.split(",").toSeq)) + excludedComponents = sdkConfig.getString("components.exclude").split(",").map(_.trim).toSet) } final case class DevModeSettings(serviceName: String, httpPort: Int) @@ -39,4 +38,4 @@ private[impl] final case class Settings( cleanupDeletedEventSourcedEntityAfter: Duration, cleanupDeletedKeyValueEntityAfter: Duration, devModeSettings: Option[DevModeSettings], - excludedComponents: Seq[String]) + excludedComponents: Set[String]) From 3bdeb7dac16f035cd5c7ce8f72a6ffe53fa8dd4f Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Thu, 9 Jan 2025 15:15:40 +0100 Subject: [PATCH 8/9] exclude -> disable --- akka-javasdk-tests/src/test/resources/application.conf | 2 +- akka-javasdk/src/main/resources/reference.conf | 4 ++-- akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala | 4 ++-- akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/akka-javasdk-tests/src/test/resources/application.conf b/akka-javasdk-tests/src/test/resources/application.conf index 660f145c2..6f86c3002 100644 --- a/akka-javasdk-tests/src/test/resources/application.conf +++ b/akka-javasdk-tests/src/test/resources/application.conf @@ -1,3 +1,3 @@ # Using a different port to not conflict with parallel tests akka.javasdk.testkit.http-port = 39391 -akka.javasdk.components.exclude = "akkajavasdk.components.keyvalueentities.user.ProdCounterEntity,akkajavasdk.components.keyvalueentities.user.StageCounterEntity" +akka.javasdk.components.disabled = "akkajavasdk.components.keyvalueentities.user.ProdCounterEntity,akkajavasdk.components.keyvalueentities.user.StageCounterEntity" diff --git a/akka-javasdk/src/main/resources/reference.conf b/akka-javasdk/src/main/resources/reference.conf index cb9efbb13..ab0eb05fc 100644 --- a/akka-javasdk/src/main/resources/reference.conf +++ b/akka-javasdk/src/main/resources/reference.conf @@ -92,6 +92,6 @@ akka.javasdk { collector-endpoint = ${?COLLECTOR_ENDPOINT} } } - # The comma separated list of FQCNs of components to exclude from running - components.exclude = "" + # The comma separated list of FQCNs of components disabled from running + components.disable = "" } diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala index 589dd9951..2cdc45839 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala @@ -357,8 +357,8 @@ private final class Sdk( private def isExcluded(clz: Class[_]): Boolean = { val componentName = clz.getName - if (sdkSettings.excludedComponents.contains(componentName)) { - logger.info("Ignoring component [{}] as it is excluded in the configuration", clz.getName) + if (sdkSettings.disabledComponents.contains(componentName)) { + logger.info("Ignoring component [{}] as it is disabled in the configuration", clz.getName) true } else { false diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala index 27cb6b9f5..dcdf909a9 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/Settings.scala @@ -24,7 +24,7 @@ private[impl] object Settings { DevModeSettings( serviceName = sdkConfig.getString("dev-mode.service-name"), httpPort = sdkConfig.getInt("dev-mode.http-port"))), - excludedComponents = sdkConfig.getString("components.exclude").split(",").map(_.trim).toSet) + disabledComponents = sdkConfig.getString("components.disable").split(",").map(_.trim).toSet) } final case class DevModeSettings(serviceName: String, httpPort: Int) @@ -38,4 +38,4 @@ private[impl] final case class Settings( cleanupDeletedEventSourcedEntityAfter: Duration, cleanupDeletedKeyValueEntityAfter: Duration, devModeSettings: Option[DevModeSettings], - excludedComponents: Set[String]) + disabledComponents: Set[String]) From 5877df8570f7ccfc2c3dcc57a86f9a6ec8399920 Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Thu, 9 Jan 2025 15:39:57 +0100 Subject: [PATCH 9/9] small fixes --- akka-javasdk-tests/src/test/resources/application.conf | 2 +- .../src/main/scala/akka/javasdk/impl/SdkRunner.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/akka-javasdk-tests/src/test/resources/application.conf b/akka-javasdk-tests/src/test/resources/application.conf index 6f86c3002..e9da444b6 100644 --- a/akka-javasdk-tests/src/test/resources/application.conf +++ b/akka-javasdk-tests/src/test/resources/application.conf @@ -1,3 +1,3 @@ # Using a different port to not conflict with parallel tests akka.javasdk.testkit.http-port = 39391 -akka.javasdk.components.disabled = "akkajavasdk.components.keyvalueentities.user.ProdCounterEntity,akkajavasdk.components.keyvalueentities.user.StageCounterEntity" +akka.javasdk.components.disable = "akkajavasdk.components.keyvalueentities.user.ProdCounterEntity,akkajavasdk.components.keyvalueentities.user.StageCounterEntity" diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala index 2cdc45839..65d9fca16 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala @@ -355,7 +355,7 @@ private final class Sdk( } } - private def isExcluded(clz: Class[_]): Boolean = { + private def isDisabled(clz: Class[_]): Boolean = { val componentName = clz.getName if (sdkSettings.disabledComponents.contains(componentName)) { logger.info("Ignoring component [{}] as it is disabled in the configuration", clz.getName) @@ -416,7 +416,7 @@ private final class Sdk( // collect all Endpoints and compose them to build a larger router private val httpEndpointDescriptors = componentClasses .filter(Reflect.isRestEndpoint) - .filterNot(isExcluded) + .filterNot(isDisabled) .map { httpEndpointClass => HttpEndpointDescriptorFactory(httpEndpointClass, httpEndpointFactory(httpEndpointClass)) } @@ -430,7 +430,7 @@ private final class Sdk( componentClasses .filter(hasComponentId) - .filterNot(isExcluded) + .filterNot(isDisabled) .foreach { case clz if classOf[EventSourcedEntity[_, _]].isAssignableFrom(clz) => val componentId = clz.getAnnotation(classOf[ComponentId]).value