diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 21b2dd9..9691a60 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,4 +19,8 @@ updates: directory: /handle-occurrence-service open-pull-requests-limit: 50 schedule: - interval: monthly \ No newline at end of file + interval: monthly + - package-ecosystem: docker + directory: /handle-scoreboard-service + schedule: + interval: monthly diff --git a/handle-scoreboard-service/Dockerfile b/handle-scoreboard-service/Dockerfile new file mode 100644 index 0000000..3c5fb34 --- /dev/null +++ b/handle-scoreboard-service/Dockerfile @@ -0,0 +1,21 @@ +FROM amazoncorretto:19@sha256:a197d796640268bd6fcb74507f92efe69274b2c13de63ee36a07c25058d4bd3f +ARG APP_NAME +ARG APP_VERSION +ENV VERSION=$APP_VERSION + +LABEL org.opencontainers.image.source=https://github.com/creek-service/connected-services-demo/tree/main/handle-scoreboard-service + +RUN yum update -y +RUN yum install -y tar lsof + +RUN mkdir -p /opt/creek + +COPY bin /bin +COPY log4j /log + +COPY ${APP_NAME}-${APP_VERSION}.tar /opt/creek +WORKDIR /opt/creek +RUN tar xf ${APP_NAME}-${APP_VERSION}.tar +RUN ln -s ${APP_NAME}-${APP_VERSION} service + +ENTRYPOINT ["/bin/run.sh"] \ No newline at end of file diff --git a/handle-scoreboard-service/build.gradle.kts b/handle-scoreboard-service/build.gradle.kts new file mode 100644 index 0000000..ef26e34 --- /dev/null +++ b/handle-scoreboard-service/build.gradle.kts @@ -0,0 +1,72 @@ +/* + * Copyright 2023 Creek Contributors (https://github.com/creek-service) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless 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. + */ + +import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage +import com.bmuschko.gradle.docker.tasks.image.DockerPushImage + +plugins { + application + id("com.bmuschko.docker-remote-api") +} + +val creekVersion : String by extra +val kafkaVersion : String by extra +val log4jVersion : String by extra + +dependencies { + implementation(project(":services")) + implementation("org.creekservice:creek-service-context:$creekVersion") + implementation("org.creekservice:creek-kafka-streams-extension:$creekVersion") + implementation("org.apache.logging.log4j:log4j-core:$log4jVersion") + runtimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl:$log4jVersion") + + testImplementation("org.creekservice:creek-kafka-streams-test:$creekVersion") +} + +// Patch Kafka Streams test jar into main Kafka Streams module to avoid split packages: +modularity.patchModule("kafka.streams", "kafka-streams-test-utils-$kafkaVersion.jar") + +application { + mainModule.set("connected.services.demo.handle.scoreboard.service") + mainClass.set("io.github.creek.service.connected.services.demo.handle.scoreboard.service.ServiceMain") +} + +val buildAppImage = tasks.register("buildAppImage") { + dependsOn("prepareDocker") + buildArgs.put("APP_NAME", project.name) + buildArgs.put("APP_VERSION", "${project.version}") + images.add("ghcr.io/creek-service/${rootProject.name}-${project.name}:latest") + images.add("ghcr.io/creek-service/${rootProject.name}-${project.name}:${project.version}") +} + +tasks.register("prepareDocker") { + dependsOn("distTar") + + from( + layout.projectDirectory.file("Dockerfile"), + layout.buildDirectory.file("distributions/${project.name}-${project.version}.tar"), + layout.projectDirectory.dir("include"), + ) + + into(buildAppImage.get().inputDir) +} + +tasks.register("pushAppImage") { + dependsOn("buildAppImage") + images.add("ghcr.io/creek-service/${rootProject.name}-${project.name}:latest") + images.add("ghcr.io/creek-service/${rootProject.name}-${project.name}:${project.version}") +} + diff --git a/handle-scoreboard-service/include/bin/run.sh b/handle-scoreboard-service/include/bin/run.sh new file mode 100755 index 0000000..c7cb9bd --- /dev/null +++ b/handle-scoreboard-service/include/bin/run.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# +# Copyright 2022-2023 Creek Contributors (https://github.com/creek-service) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless 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. +# + +exec java \ + -Xms64m -Xmx256m \ + -Dlog4j.configurationFile=/log/log4j2.xml \ + --module-path "/opt/creek/service/lib" \ + --module connected.services.demo.handle.scoreboard.service/io.github.creek.service.connected.services.demo.handle.scoreboard.service.ServiceMain diff --git a/handle-scoreboard-service/include/log4j/log4j2.xml b/handle-scoreboard-service/include/log4j/log4j2.xml new file mode 100644 index 0000000..87d9472 --- /dev/null +++ b/handle-scoreboard-service/include/log4j/log4j2.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/handle-scoreboard-service/src/main/java/io/github/creek/service/connected/services/demo/handle/scoreboard/service/ServiceMain.java b/handle-scoreboard-service/src/main/java/io/github/creek/service/connected/services/demo/handle/scoreboard/service/ServiceMain.java new file mode 100644 index 0000000..ce5f181 --- /dev/null +++ b/handle-scoreboard-service/src/main/java/io/github/creek/service/connected/services/demo/handle/scoreboard/service/ServiceMain.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2023 Creek Contributors (https://github.com/creek-service) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless 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. + */ + +package io.github.creek.service.connected.services.demo.handle.scoreboard.service; + +import io.github.creek.service.connected.services.demo.handle.scoreboard.service.kafka.streams.TopologyBuilder; +import io.github.creek.service.connected.services.demo.services.HandleScoreboardServiceDescriptor; +import org.apache.kafka.streams.Topology; +import org.creekservice.api.kafka.streams.extension.KafkaStreamsExtension; +import org.creekservice.api.kafka.streams.extension.KafkaStreamsExtensionOptions; +import org.creekservice.api.service.context.CreekContext; +import org.creekservice.api.service.context.CreekServices; + +/** Entry point of the service */ +public final class ServiceMain { + + private ServiceMain() {} + + public static void main(final String... args) { + final CreekContext ctx = + CreekServices.builder(new HandleScoreboardServiceDescriptor()) + .with( + KafkaStreamsExtensionOptions.builder() + // Customize the Kafka streams extension here... + .build()) + .build(); + + final KafkaStreamsExtension ext = ctx.extension(KafkaStreamsExtension.class); + final Topology topology = new TopologyBuilder(ext).build(); + ext.execute(topology); + } +} diff --git a/handle-scoreboard-service/src/main/java/io/github/creek/service/connected/services/demo/handle/scoreboard/service/kafka/streams/TopologyBuilder.java b/handle-scoreboard-service/src/main/java/io/github/creek/service/connected/services/demo/handle/scoreboard/service/kafka/streams/TopologyBuilder.java new file mode 100644 index 0000000..2c874b3 --- /dev/null +++ b/handle-scoreboard-service/src/main/java/io/github/creek/service/connected/services/demo/handle/scoreboard/service/kafka/streams/TopologyBuilder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022-2023 Creek Contributors (https://github.com/creek-service) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless 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. + */ + +package io.github.creek.service.connected.services.demo.handle.scoreboard.service.kafka.streams; + +import static java.util.Objects.requireNonNull; +import static org.creekservice.api.kafka.metadata.KafkaTopicDescriptor.DEFAULT_CLUSTER_NAME; + +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.Topology; +import org.creekservice.api.kafka.streams.extension.KafkaStreamsExtension; +import org.creekservice.api.kafka.streams.extension.util.Name; + +public final class TopologyBuilder { + + private final KafkaStreamsExtension ext; + private final Name name = Name.root(); + + public TopologyBuilder(final KafkaStreamsExtension ext) { + this.ext = requireNonNull(ext, "ext"); + } + + public Topology build() { + final StreamsBuilder builder = new StreamsBuilder(); + + return builder.build(ext.properties(DEFAULT_CLUSTER_NAME)); + } +} diff --git a/handle-scoreboard-service/src/main/java/module-info.java b/handle-scoreboard-service/src/main/java/module-info.java new file mode 100644 index 0000000..024f901 --- /dev/null +++ b/handle-scoreboard-service/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module connected.services.demo.handle.scoreboard.service { + requires connected.services.demo.services; + requires creek.service.context; + requires creek.kafka.streams.extension; + requires org.apache.logging.log4j; +} diff --git a/handle-scoreboard-service/src/test/java/io/github/creek/service/connected/services/demo/example/streams/TopologyBuilderTest.java b/handle-scoreboard-service/src/test/java/io/github/creek/service/connected/services/demo/example/streams/TopologyBuilderTest.java new file mode 100644 index 0000000..f5710d6 --- /dev/null +++ b/handle-scoreboard-service/src/test/java/io/github/creek/service/connected/services/demo/example/streams/TopologyBuilderTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2022-2023 Creek Contributors (https://github.com/creek-service) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless 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. + */ + +package io.github.creek.service.connected.services.demo.example.streams; + +import static org.creekservice.api.kafka.metadata.KafkaTopicDescriptor.DEFAULT_CLUSTER_NAME; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import io.github.creek.service.connected.services.demo.handle.scoreboard.service.kafka.streams.TopologyBuilder; +import io.github.creek.service.connected.services.demo.services.HandleScoreboardServiceDescriptor; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.TopologyTestDriver; +import org.creekservice.api.kafka.streams.extension.KafkaStreamsExtension; +import org.creekservice.api.kafka.streams.test.TestKafkaStreamsExtensionOptions; +import org.creekservice.api.service.context.CreekContext; +import org.creekservice.api.service.context.CreekServices; +import org.creekservice.api.test.util.TestPaths; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TopologyBuilderTest { + + private static CreekContext ctx; + + private TopologyTestDriver testDriver; + private Topology topology; + + @BeforeAll + public static void classSetup() { + ctx = + CreekServices.builder(new HandleScoreboardServiceDescriptor()) + .with(TestKafkaStreamsExtensionOptions.defaults()) + .build(); + } + + @BeforeEach + public void setUp() { + final KafkaStreamsExtension ext = ctx.extension(KafkaStreamsExtension.class); + + topology = new TopologyBuilder(ext).build(); + testDriver = new TopologyTestDriver(topology, ext.properties(DEFAULT_CLUSTER_NAME)); + } + + @AfterEach + public void tearDown() { + testDriver.close(); + } + + /** + * A test that intentionally fails when ever the topology changes. + * + *

This is to make it less likely that unintentional changes to the topology are committed + * and that thought is given to any intentional changes to ensure they won't break any deployed + * instances. + * + *

Care must be taken when changing a deployed topology to ensure either: + * + *

    + *
  1. Changes are backwards compatible and won't leave data stranded in unused topics, or + *
  2. The existing topology is drained before the new topology is deployed + *
+ * + *

Option #1 allows for the simplest deployment, but is not always possible or desirable. + */ + @Test + void shouldNotChangeTheTopologyUnintentionally() { + // Given: + final String expectedTopology = + TestPaths.readString( + TestPaths.moduleRoot("handle-scoreboard-service") + .resolve("src/test/resources/kafka/streams/expected_topology.txt")); + + // When: + final String currentTopology = topology.describe().toString(); + + // Then: + assertThat(currentTopology.trim(), is(expectedTopology.trim())); + } +} diff --git a/handle-scoreboard-service/src/test/java/module-info.test b/handle-scoreboard-service/src/test/java/module-info.test new file mode 100644 index 0000000..eeebf56 --- /dev/null +++ b/handle-scoreboard-service/src/test/java/module-info.test @@ -0,0 +1,8 @@ +--add-modules + org.junitpioneer,org.hamcrest,guava.testlib,creek.test.util,creek.test.hamcrest,creek.kafka.streams.test + +--add-reads + connected.services.demo.handle.scoreboard.service=org.junitpioneer,org.hamcrest,guava.testlib,creek.test.util,creek.test.hamcrest,creek.kafka.streams.test + +--add-opens + org.junitpioneer/org.junitpioneer.jupiter=org.junit.platform.commons \ No newline at end of file diff --git a/handle-scoreboard-service/src/test/resources/kafka/streams/expected_topology.txt b/handle-scoreboard-service/src/test/resources/kafka/streams/expected_topology.txt new file mode 100644 index 0000000..165a4ba --- /dev/null +++ b/handle-scoreboard-service/src/test/resources/kafka/streams/expected_topology.txt @@ -0,0 +1 @@ +Topologies: diff --git a/services/src/main/java/io/github/creek/service/connected/services/demo/services/HandleScoreboardServiceDescriptor.java b/services/src/main/java/io/github/creek/service/connected/services/demo/services/HandleScoreboardServiceDescriptor.java new file mode 100644 index 0000000..adbf761 --- /dev/null +++ b/services/src/main/java/io/github/creek/service/connected/services/demo/services/HandleScoreboardServiceDescriptor.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Creek Contributors (https://github.com/creek-service) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless 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. + */ + +package io.github.creek.service.connected.services.demo.services; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.creekservice.api.platform.metadata.ComponentInput; +import org.creekservice.api.platform.metadata.ComponentInternal; +import org.creekservice.api.platform.metadata.ComponentOutput; +import org.creekservice.api.platform.metadata.ServiceDescriptor; + +public final class HandleScoreboardServiceDescriptor implements ServiceDescriptor { + + private static final List INPUTS = new ArrayList<>(); + private static final List INTERNALS = new ArrayList<>(); + private static final List OUTPUTS = new ArrayList<>(); + + public HandleScoreboardServiceDescriptor() {} + + @Override + public String dockerImage() { + return "ghcr.io/creek-service/connected-services-demo-handle-scoreboard-service"; + } + + @Override + public Collection inputs() { + return List.copyOf(INPUTS); + } + + @Override + public Collection internals() { + return List.copyOf(INTERNALS); + } + + @Override + public Collection outputs() { + return List.copyOf(OUTPUTS); + } + + private static T register(final T input) { + INPUTS.add(input); + return input; + } + + // Uncomment if needed: + // private static T register(final T internal) { + // INTERNALS.add(internal); + // return internal; + // } + + private static T register(final T output) { + OUTPUTS.add(output); + return output; + } +} diff --git a/services/src/main/java/module-info.java b/services/src/main/java/module-info.java index d334cf8..5c3ee73 100644 --- a/services/src/main/java/module-info.java +++ b/services/src/main/java/module-info.java @@ -7,5 +7,7 @@ exports io.github.creek.service.connected.services.demo.services; provides ComponentDescriptor with + io.github.creek.service.connected.services.demo.services + .HandleScoreboardServiceDescriptor, HandleOccurrenceServiceDescriptor; } diff --git a/services/src/main/resources/META-INF/services/org.creekservice.api.platform.metadata.ComponentDescriptor b/services/src/main/resources/META-INF/services/org.creekservice.api.platform.metadata.ComponentDescriptor index 6d1e138..0ba9312 100644 --- a/services/src/main/resources/META-INF/services/org.creekservice.api.platform.metadata.ComponentDescriptor +++ b/services/src/main/resources/META-INF/services/org.creekservice.api.platform.metadata.ComponentDescriptor @@ -14,4 +14,5 @@ # limitations under the License. # -io.github.creek.service.connected.services.demo.services.HandleOccurrenceServiceDescriptor \ No newline at end of file +io.github.creek.service.connected.services.demo.services.HandleOccurrenceServiceDescriptor +io.github.creek.service.connected.services.demo.services.HandleScoreboardServiceDescriptor diff --git a/settings.gradle.kts b/settings.gradle.kts index 21c5a2c..973bf7a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,7 @@ rootProject.name = "connected-services-demo" include( + "handle-scoreboard-service", "api", "handle-occurrence-service", "services",