├── .github ├── CompatibilityUtils.java ├── PublishReleaseNotes.java ├── dependabot.yml ├── project.yml ├── render-documentation.sh └── workflows │ ├── build.yml │ ├── post-release.yml │ ├── prepare-release.yml │ ├── release.yml │ ├── review-release.yml │ └── website.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.adoc ├── api ├── pom.xml ├── revapi.json └── src │ ├── main │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ ├── api │ │ ├── LoadBalancer.java │ │ ├── Metadata.java │ │ ├── MetadataKey.java │ │ ├── NoAcceptableServiceInstanceFoundException.java │ │ ├── NoServiceInstanceFoundException.java │ │ ├── NoSuchServiceDefinitionException.java │ │ ├── NoSuchServiceRegistrarException.java │ │ ├── Service.java │ │ ├── ServiceDefinition.java │ │ ├── ServiceDiscovery.java │ │ ├── ServiceInstance.java │ │ ├── ServiceRegistrar.java │ │ ├── StorkServiceRegistry.java │ │ ├── config │ │ │ ├── ConfigWithType.java │ │ │ ├── Constants.java │ │ │ ├── LoadBalancerAttribute.java │ │ │ ├── LoadBalancerAttributes.java │ │ │ ├── LoadBalancerType.java │ │ │ ├── ServiceConfig.java │ │ │ ├── ServiceDiscoveryAttribute.java │ │ │ ├── ServiceDiscoveryAttributes.java │ │ │ ├── ServiceDiscoveryType.java │ │ │ ├── ServiceRegistrarAttribute.java │ │ │ ├── ServiceRegistrarAttributes.java │ │ │ ├── ServiceRegistrarConfig.java │ │ │ └── ServiceRegistrarType.java │ │ └── observability │ │ │ ├── NoopObservationCollector.java │ │ │ ├── ObservationCollector.java │ │ │ ├── StorkEventHandler.java │ │ │ └── StorkObservation.java │ │ └── spi │ │ ├── CallStatisticsCollector.java │ │ ├── ElementWithType.java │ │ ├── LoadBalancerProvider.java │ │ ├── ServiceDiscoveryProvider.java │ │ ├── ServiceRegistrarProvider.java │ │ ├── StorkInfrastructure.java │ │ ├── config │ │ ├── ConfigProvider.java │ │ ├── SimpleRegistrarConfig.java │ │ └── SimpleServiceConfig.java │ │ └── internal │ │ ├── LoadBalancerLoader.java │ │ ├── ServiceDiscoveryLoader.java │ │ └── ServiceRegistrarLoader.java │ └── test │ └── java │ └── io │ └── smallrye │ └── stork │ └── api │ ├── MetadataTest.java │ ├── NoAcceptableServiceInstanceFoundExceptionTest.java │ ├── NoServiceInstanceFoundExceptionTest.java │ └── ServiceInstanceTest.java ├── bom └── pom.xml ├── config-generator ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── smallrye │ └── stork │ └── config │ └── generator │ ├── ClassName.java │ ├── ConfigClassWriter.java │ ├── ConfigurationGenerator.java │ └── DocWriter.java ├── core ├── pom.xml ├── revapi.json └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ ├── Stork.java │ │ │ ├── impl │ │ │ ├── CachingServiceDiscovery.java │ │ │ ├── ConsulMetadataKey.java │ │ │ ├── DefaultServiceInstance.java │ │ │ ├── EurekaMetadataKey.java │ │ │ ├── RoundRobinLoadBalancer.java │ │ │ ├── RoundRobinLoadBalancerProvider.java │ │ │ └── ServiceInstanceWithStatGathering.java │ │ │ ├── integration │ │ │ ├── DefaultStorkInfrastructure.java │ │ │ └── ObservableStorkInfrastructure.java │ │ │ └── utils │ │ │ ├── DurationUtils.java │ │ │ ├── HostAndPort.java │ │ │ ├── InMemoryAddressesBackend.java │ │ │ ├── ServiceInstanceIds.java │ │ │ ├── ServiceInstanceUtils.java │ │ │ ├── StorkAddressUtils.java │ │ │ └── StorkConfigUtils.java │ └── resources │ │ └── META-INF │ │ └── beans.xml │ └── test │ └── java │ └── io │ └── smallrye │ └── stork │ ├── AnchoredServiceDiscoveryProvider.java │ ├── EmptyLoadBalancerProvider.java │ ├── EmptyServiceDiscoveryProvider.java │ ├── FakeObservationCollector.java │ ├── FakeServiceConfig.java │ ├── MockLoadBalancerProvider.java │ ├── MockServiceDiscoveryProvider.java │ ├── MyDataBean.java │ ├── ObservationTest.java │ ├── ProgrammaticApiTest.java │ ├── SingleLoadBalancer.java │ ├── SingleLoadBalancerProvider.java │ ├── StorkTest.java │ ├── StorkWithCDITest.java │ ├── TestEnv.java │ ├── TestMetadataKey.java │ ├── TestServiceRegistrar.java │ ├── TestServiceRegistrarProvider.java │ ├── WeirdTypedLoadBalancerProvider.java │ ├── WeirdTypedServiceDiscoveryProvider.java │ ├── WeldTestBase.java │ ├── impl │ ├── CachingServiceDiscoveryRefreshTest.java │ ├── RoundRobinLoadBalancerProviderCDITest.java │ └── RoundRobinLoadBalancerProviderTest.java │ └── utils │ ├── DurationUtilsTest.java │ ├── InMemoryAddressesBackendTest.java │ ├── StorkAddressUtilsTest.java │ └── StorkConfigUtilsTest.java ├── coverage └── pom.xml ├── dco.txt ├── docs ├── Pipfile ├── Pipfile.lock ├── docs │ ├── concepts.md │ ├── diagrams │ │ ├── includes │ │ │ └── themes │ │ │ │ ├── dark.puml │ │ │ │ ├── general.puml │ │ │ │ └── light.puml │ │ ├── observability_sequence.puml │ │ ├── observation_sequence.puml │ │ ├── sequence.puml │ │ ├── srv_sequence.puml │ │ └── stork.puml │ ├── extra.css │ ├── images │ │ ├── problem-dark.png │ │ ├── problem-light.png │ │ ├── redhat_reversed.svg │ │ ├── solution-dark.png │ │ ├── solution-light.png │ │ └── stork-white.png │ ├── index.md │ ├── load-balancer │ │ ├── custom-load-balancer.md │ │ ├── least-requests.md │ │ ├── least-response-time.md │ │ ├── overview.md │ │ ├── power-of-two-choices.md │ │ ├── random.md │ │ ├── round-robin.md │ │ └── sticky.md │ ├── microprofile-config.md │ ├── observability.md │ ├── programmatic-api.md │ ├── quarkus.md │ ├── service-discovery │ │ ├── composite.md │ │ ├── consul.md │ │ ├── custom-service-discovery.md │ │ ├── dns.md │ │ ├── eureka.md │ │ ├── knative.md │ │ ├── kubernetes.md │ │ ├── overview.md │ │ └── static-list.md │ ├── service-registration │ │ ├── consul.md │ │ ├── custom-service-registration.md │ │ ├── eureka.md │ │ ├── overview.md │ │ └── static-list.md │ └── springboot-config.md ├── mkdocs-customizations │ ├── macros │ │ ├── __pycache__ │ │ │ └── docissimo.cpython-310.pyc │ │ └── docissimo.py │ └── overrides │ │ ├── main.html │ │ └── partials │ │ └── copyright.html ├── mkdocs.yml ├── pom.xml └── snippets │ └── examples │ ├── AcmeDiscoveryApiUsage.java │ ├── AcmeLoadBalancer.java │ ├── AcmeLoadBalancerProvider.java │ ├── AcmeObservationCollector.java │ ├── AcmeSelectorApiUsage.java │ ├── AcmeServiceDiscovery.java │ ├── AcmeServiceDiscoveryProvider.java │ ├── AcmeServiceRegistrar.java │ ├── AcmeServiceRegistrarProvider.java │ ├── CachedAcmeServiceDiscovery.java │ ├── CachedAcmeServiceDiscoveryProvider.java │ ├── CustomExpirationCachedAcmeServiceDiscovery.java │ ├── DefinitionExample.java │ ├── HelloClient.java │ ├── InitializationExample.java │ ├── LookupExample.java │ ├── ObservableInitializationExample.java │ ├── ObservationExample.java │ ├── SpringBootInitializationExample.java │ ├── StorkApiExample.java │ ├── StorkEntryPointExample.java │ ├── StorkServiceExample.java │ ├── StorkServiceLookupExample.java │ └── StorkServiceSelectionExample.java ├── load-balancer ├── least-requests │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── loadbalancer │ │ │ └── requests │ │ │ ├── InflightRequestCollector.java │ │ │ ├── LeastRequestsLoadBalancer.java │ │ │ └── LeastRequestsLoadBalancerProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── loadbalancer │ │ └── leastresponsetime │ │ ├── LeastRequestsLoadBalancerCDITest.java │ │ ├── LeastRequestsLoadBalancerProgrammaticApiTest.java │ │ └── LeastRequestsLoadBalancerTest.java ├── least-response-time │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── loadbalancer │ │ │ └── leastresponsetime │ │ │ ├── LeastResponseTimeLoadBalancer.java │ │ │ ├── LeastResponseTimeLoadBalancerProvider.java │ │ │ └── impl │ │ │ ├── CallStatistics.java │ │ │ └── util │ │ │ └── FastPower.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── loadbalancer │ │ └── leastresponsetime │ │ ├── LeastResponseTimeLoadBalancerCDITest.java │ │ ├── LeastResponseTimeLoadBalancerProgrammaticApiCDITest.java │ │ ├── LeastResponseTimeLoadBalancerProgrammaticApiTest.java │ │ ├── LeastResponseTimeLoadBalancerTest.java │ │ ├── impl │ │ └── TestUtils.java │ │ └── util │ │ └── FastPowerTest.java ├── power-of-two-choices │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── loadbalancer │ │ │ └── poweroftwochoices │ │ │ ├── PowerOfTwoChoicesLoadBalancer.java │ │ │ └── PowerOfTwoChoicesLoadBalancerProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── loadbalancer │ │ └── poweroftwochoices │ │ ├── PowerOfTwoChoicesLoadBalancerCDITest.java │ │ ├── PowerOfTwoChoicesLoadBalancerProgrammaticApiCDITest.java │ │ ├── PowerOfTwoChoicesLoadBalancerProgrammaticApiTest.java │ │ └── PowerOfTwoChoicesLoadBalancerTest.java ├── random │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── loadbalancer │ │ │ └── random │ │ │ ├── RandomLoadBalancer.java │ │ │ └── RandomLoadBalancerProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── loadbalancer │ │ └── leastresponsetime │ │ ├── RandomLoadBalancerCDITest.java │ │ ├── RandomLoadBalancerProgrammaticApiCDITest.java │ │ ├── RandomLoadBalancerProgrammaticApiTest.java │ │ └── RandomLoadBalancerTest.java └── sticky │ ├── pom.xml │ ├── revapi.json │ └── src │ ├── main │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── loadbalancer │ │ └── random │ │ ├── StickyLoadBalancer.java │ │ └── StickyLoadBalancerProvider.java │ └── test │ └── java │ └── io │ └── smallrye │ └── stork │ └── loadbalancer │ └── random │ ├── StickyLoadBalancerCDITest.java │ └── StickyLoadBalancerTest.java ├── microprofile ├── pom.xml ├── revapi.json └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── microprofile │ │ │ └── MicroProfileConfigProvider.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.smallrye.stork.spi.config.ConfigProvider │ └── test │ └── java │ └── io │ └── smallrye │ └── stork │ └── microprofile │ ├── MicroProfileConfigProviderTest.java │ └── TestMicroProfileConfigProvider.java ├── pom.xml ├── service-discovery ├── composite │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── servicediscovery │ │ │ └── composite │ │ │ ├── CompositeServiceDiscovery.java │ │ │ ├── CompositeServiceDiscoveryProvider.java │ │ │ └── util │ │ │ ├── CombiningIterator.java │ │ │ └── CombiningList.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── servicediscovery │ │ └── composite │ │ ├── CompositeServiceDiscoveryCDITest.java │ │ ├── CompositeServiceDiscoveryTest.java │ │ └── util │ │ └── CombiningListTest.java ├── consul │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── servicediscovery │ │ │ └── consul │ │ │ ├── ConsulServiceDiscovery.java │ │ │ └── ConsulServiceDiscoveryProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── servicediscovery │ │ └── consul │ │ ├── ConsulServiceDiscoveryCDITest.java │ │ ├── ConsulServiceDiscoveryProgrammaticApiCDITest.java │ │ ├── ConsulServiceDiscoveryProgrammaticApiTest.java │ │ └── ConsulServiceDiscoveryTest.java ├── dns │ ├── consul │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── servicediscovery │ │ │ └── dns │ │ │ ├── DnsMetadataKey.java │ │ │ ├── DnsRecordType.java │ │ │ ├── DnsServiceDiscovery.java │ │ │ └── DnsServiceDiscoveryProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── servicediscovery │ │ └── dns │ │ ├── DnsServiceDiscoveryCDITest.java │ │ └── DnsServiceDiscoveryTest.java ├── eureka │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── servicediscovery │ │ │ └── eureka │ │ │ ├── ApplicationInstance.java │ │ │ ├── EurekaServiceDiscovery.java │ │ │ └── EurekaServiceDiscoveryProvider.java │ │ └── test │ │ ├── java │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── servicediscovery │ │ │ └── eureka │ │ │ ├── EurekaDiscoveryCDITest.java │ │ │ └── EurekaDiscoveryTest.java │ │ └── resources │ │ └── application.properties ├── knative │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── servicediscovery │ │ │ └── knative │ │ │ ├── KnativeMetadataKey.java │ │ │ ├── KnativeServiceDiscovery.java │ │ │ └── KnativeServiceDiscoveryProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── servicediscovery │ │ └── knative │ │ ├── KnativeServiceDiscoveryCDITest.java │ │ ├── KnativeServiceDiscoveryRealClusterTest.java │ │ └── KnativeServiceDiscoveryTest.java ├── kubernetes │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── servicediscovery │ │ │ └── kubernetes │ │ │ ├── KubernetesMetadataKey.java │ │ │ ├── KubernetesServiceDiscovery.java │ │ │ └── KubernetesServiceDiscoveryProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── servicediscovery │ │ └── kubernetes │ │ ├── KubernetesServiceDiscoveryCDITest.java │ │ ├── KubernetesServiceDiscoveryRealClusterIT.java │ │ └── KubernetesServiceDiscoveryTest.java └── static-list │ ├── pom.xml │ ├── revapi.json │ └── src │ ├── main │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── servicediscovery │ │ └── staticlist │ │ ├── StaticListServiceDiscovery.java │ │ └── StaticListServiceDiscoveryProvider.java │ └── test │ └── java │ └── io │ └── smallrye │ └── stork │ └── servicediscovery │ └── staticlist │ ├── StaticListServiceDiscoveryCDITest.java │ ├── StaticListServiceDiscoveryProgrammaticApiCDITest.java │ ├── StaticListServiceDiscoveryProgrammaticApiTest.java │ └── StaticListServiceDiscoveryTest.java ├── service-registration ├── consul │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── serviceregistration │ │ │ └── consul │ │ │ ├── ConsulServiceRegistrar.java │ │ │ └── ConsulServiceRegistrarProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── serviceregistration │ │ └── consul │ │ ├── ConsulServiceRegistrationCDITest.java │ │ └── ConsulServiceRegistrationTest.java ├── eureka │ ├── pom.xml │ ├── revapi.json │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── serviceregistration │ │ │ └── eureka │ │ │ ├── EurekaServiceRegistrar.java │ │ │ └── EurekaServiceRegistrarProvider.java │ │ └── test │ │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── serviceregistration │ │ └── eureka │ │ ├── EurekaRegistrationCDITest.java │ │ └── EurekaRegistrationTest.java └── static-list │ ├── pom.xml │ ├── revapi.json │ └── src │ ├── main │ └── java │ │ └── io │ │ └── smallrye │ │ └── stork │ │ └── serviceregistration │ │ └── staticlist │ │ ├── StaticListServiceRegistrar.java │ │ └── StaticListServiceRegistrarProvider.java │ └── test │ └── java │ └── io │ └── smallrye │ └── stork │ └── serviceregistration │ └── staticlist │ └── StaticServiceRegistrationTest.java ├── spring-boot ├── pom.xml ├── revapi.json └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── smallrye │ │ │ └── stork │ │ │ └── springboot │ │ │ ├── SpringBootApplicationContextProvider.java │ │ │ └── SpringBootConfigProvider.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.smallrye.stork.spi.config.ConfigProvider │ └── test │ └── java │ └── io │ └── smallrye │ └── stork │ └── microprofile │ └── SpringBootConfigProviderTest.java └── test-utils ├── pom.xml ├── revapi.json └── src └── main ├── java └── io │ └── smallrye │ └── stork │ └── test │ ├── EmptyServicesServiceDiscoveryProvider.java │ ├── StorkTestUtils.java │ ├── TestConfigProvider.java │ ├── TestConfigProviderBean.java │ ├── TestLoadBalancer1.java │ ├── TestLoadBalancer1Provider.java │ ├── TestLoadBalancer2.java │ ├── TestLoadBalancer2Provider.java │ ├── TestServiceDiscovery.java │ ├── TestServiceDiscovery1Provider.java │ ├── TestServiceDiscovery2.java │ ├── TestServiceDiscovery2Provider.java │ └── TestServiceRegistrarProvider.java └── resources └── META-INF └── services └── io.smallrye.stork.spi.config.ConfigProvider /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Main branch 4 | - package-ecosystem: maven 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/project.yml: -------------------------------------------------------------------------------- 1 | name: SmallRye Stork 2 | release: 3 | current-version: 2.7.3 4 | next-version: 2.7.4-SNAPSHOT 5 | -------------------------------------------------------------------------------- /.github/render-documentation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | DIR="$(dirname $(realpath "$0"))" 5 | echo $DIR 6 | 7 | export STORK_VERSION=$(cat "$DIR/project.yml" | yq eval '.release.current-version' -) 8 | cd docs 9 | pipenv run mike deploy --push --update-aliases $STORK_VERSION latest --branch gh-pages 10 | pipenv run mike set-default --push latest -------------------------------------------------------------------------------- /.github/workflows/post-release.yml: -------------------------------------------------------------------------------- 1 | name: Post-Release 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - "!*" 7 | tags: 8 | - '[0-9]+.[0-9]+.[0-9]+*' 9 | jobs: 10 | post-release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | name: Checkout code 15 | with: 16 | fetch-depth: 0 # fetch all commits and branches for mike to work properly 17 | - uses: actions/setup-java@v4 18 | name: Set up Java 21 19 | with: 20 | java-version: 21 21 | distribution: temurin 22 | cache: maven 23 | - name: Build local artifacts 24 | run: mvn -B install -DskipTests 25 | - name: Collect breaking changes 26 | run: | 27 | sudo apt-get update -o Dir::Etc::sourcelist="sources.list" \ 28 | -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0" 29 | sudo apt-get install -y gnupg2 gnupg-agent 30 | curl -s "https://get.sdkman.io" | bash 31 | source ~/.sdkman/bin/sdkman-init.sh && \ 32 | sdk install jbang 33 | mkdir -p target 34 | jbang .github/CompatibilityUtils.java extract 35 | - name: Update release notes 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 38 | run: | 39 | sudo apt-get install -y gnupg2 gnupg-agent 40 | curl -s "https://get.sdkman.io" | bash 41 | source ~/.sdkman/bin/sdkman-init.sh && \ 42 | sdk install jbang 43 | export STORK_VERSION=$(cat ".github/project.yml" | yq eval '.release.current-version' -) 44 | jbang .github/PublishReleaseNotes.java --release-version=$STORK_VERSION --token=$GITHUB_TOKEN -------------------------------------------------------------------------------- /.github/workflows/prepare-release.yml: -------------------------------------------------------------------------------- 1 | name: SmallRye Prepare Release 2 | 3 | on: 4 | pull_request: 5 | types: [ closed ] 6 | paths: 7 | - '.github/project.yml' 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | prepare-release: 15 | name: Prepare Release 16 | if: ${{ github.event.pull_request.merged == true}} 17 | uses: smallrye/.github/.github/workflows/prepare-release.yml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: SmallRye Release 2 | run-name: Perform ${{github.event.inputs.tag || github.ref_name}} Release 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: 'Tag to release' 11 | required: true 12 | 13 | permissions: 14 | attestations: write 15 | id-token: write 16 | # Needed for the publish-* workflows 17 | contents: write 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | perform-release: 25 | name: Perform Release 26 | uses: smallrye/.github/.github/workflows/perform-release.yml@main 27 | secrets: inherit 28 | with: 29 | version: ${{github.event.inputs.tag || github.ref_name}} 30 | -------------------------------------------------------------------------------- /.github/workflows/review-release.yml: -------------------------------------------------------------------------------- 1 | name: SmallRye Review Release 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/project.yml' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | name: pre release 12 | 13 | steps: 14 | - uses: radcortez/project-metadata-action@main 15 | name: retrieve project metadata 16 | id: metadata 17 | with: 18 | github-token: ${{secrets.GITHUB_TOKEN}} 19 | metadata-file-path: '.github/project.yml' 20 | 21 | - name: Validate version 22 | if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT') 23 | run: | 24 | echo '::error::Cannot release a SNAPSHOT version.' 25 | exit 1 26 | 27 | - uses: radcortez/milestone-review-action@main 28 | name: milestone review 29 | with: 30 | github-token: ${{secrets.GITHUB_TOKEN}} 31 | milestone-title: ${{steps.metadata.outputs.current-version}} 32 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Publish the website 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - "!*" 7 | tags: 8 | - '[0-9]+.[0-9]+.[0-9]+*' 9 | jobs: 10 | publish-website: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | name: Checkout code 15 | with: 16 | fetch-depth: 0 # fetch all commits and branches for mike to work properly 17 | - uses: actions/setup-java@v4 18 | name: Set up Java 21 19 | with: 20 | java-version: 21 21 | distribution: temurin 22 | cache: maven 23 | - uses: actions/setup-python@v5 24 | name: Set up Python 25 | with: 26 | python-version: 3.x 27 | - name: Tools setup 28 | run: | 29 | git config --global user.name "SmallRye CI" 30 | git config --global user.email "smallrye@googlegroups.com" 31 | cd docs 32 | sudo snap install yq 33 | pip3 install pipenv 34 | pipenv install 35 | cd .. 36 | - name: Build local artifacts 37 | run: mvn -B install -DskipTests 38 | - name: Render docs and publish 39 | run: .github/render-documentation.sh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .vscode 4 | .project 5 | .settings 6 | .classpath 7 | target 8 | site/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Breaking changes 2 | 3 | ## For the next release 4 | The artifact containing the Least Response Time Load Balancer has been renamed to `stork-load-balancer-least-response-time` -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | 8 | [dev-packages] 9 | 10 | [requires] 11 | python_version = "3.10" 12 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "fedbd2ab7afd84cf16f128af0619749267b62277b4cb6989ef16d4bef6e4eef2" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": {}, 19 | "develop": {} 20 | } 21 | -------------------------------------------------------------------------------- /api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.smallrye.stork 7 | stork-parent 8 | 2.7.4-SNAPSHOT 9 | 10 | 11 | stork-api 12 | 13 | SmallRye Stork : Main API classes 14 | 15 | Main Stork API classes. You are likely to need `smallrye-stork-core` and not this module. 16 | 17 | 18 | false 19 | 20 | 21 | 22 | 23 | io.smallrye.reactive 24 | mutiny 25 | 26 | 27 | 28 | org.junit.jupiter 29 | junit-jupiter 30 | test 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /api/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.api(\\..*)?/" 17 | }, { 18 | "matcher" : "java-package", 19 | "match" : "/io\\.smallrye\\.stork\\.api(\\..*)?/" 20 | } ] 21 | } 22 | } 23 | }, { 24 | "extension" : "revapi.differences", 25 | "id" : "breaking-changes", 26 | "configuration" : { 27 | "criticality" : "highlight", 28 | "minSeverity" : "POTENTIALLY_BREAKING", 29 | "minCriticality" : "documented", 30 | "differences" : [ ] 31 | } 32 | }, { 33 | "extension" : "revapi.reporter.json", 34 | "configuration" : { 35 | "minSeverity" : "POTENTIALLY_BREAKING", 36 | "minCriticality" : "documented", 37 | "output" : "target/compatibility.json", 38 | "indent" : true, 39 | "append" : false, 40 | "keepEmptyFile" : true 41 | } 42 | }, { 43 | "extension" : "revapi.reporter.text", 44 | "configuration" : { 45 | "minSeverity" : "POTENTIALLY_BREAKING", 46 | "minCriticality" : "documented", 47 | "output" : "out" 48 | } 49 | } ] -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/LoadBalancer.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | import java.util.Collection; 4 | 5 | /** 6 | * Works with a single service name. 7 | * Provides a single service instance. 8 | *
9 | * Must be non-blocking 10 | */ 11 | public interface LoadBalancer { 12 | 13 | /** 14 | * Select a single {@link ServiceInstance} from the given list. 15 | * 16 | * @param serviceInstances instances to choose from 17 | * 18 | * @return a ServiceInstance 19 | * 20 | * @throws NoServiceInstanceFoundException if the incoming collection is empty or all the service instances in the 21 | * collection 22 | * are deemed invalid for some reason 23 | */ 24 | ServiceInstance selectServiceInstance(Collection serviceInstances); 25 | 26 | /** 27 | * LoadBalancers often record information of the calls being made with instances they return, be it inflight requests, 28 | * response time, etc. 29 | *
30 | * These calculations often assume that an operation using a previously selected service instance is marked 31 | * as started before the next instance selection. This prevents a situation in which multiple parallel invocations 32 | * of the LoadBalancer return the same service instance (because they have the same input for selection). 33 | *
34 | * If the load balancer is insusceptible of this problem, this method should return false. 35 | * 36 | * @return true if the selecting service instance should be called atomically with marking the operation as started 37 | * 38 | * @see Service#selectInstanceAndRecordStart(boolean) 39 | * @see Service#selectInstanceAndRecordStart(Collection, boolean) 40 | */ 41 | default boolean requiresStrictRecording() { 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/MetadataKey.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | /** 4 | * Interface representing metadata key. 5 | */ 6 | public interface MetadataKey { 7 | /** 8 | * @return the name 9 | */ 10 | String getName(); 11 | } 12 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/NoAcceptableServiceInstanceFoundException.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | /** 4 | * Thrown by {@link LoadBalancer} when all available services are not acceptable for some, arbitrary, reason 5 | */ 6 | public class NoAcceptableServiceInstanceFoundException extends NoServiceInstanceFoundException { 7 | 8 | /** 9 | * Creates a new NoAcceptableServiceInstanceFoundException. 10 | * 11 | * @param message the message 12 | */ 13 | public NoAcceptableServiceInstanceFoundException(String message) { 14 | super(message); 15 | } 16 | 17 | /** 18 | * Creates a new NoAcceptableServiceInstanceFoundException. 19 | * 20 | * @param message the message 21 | * @param cause the cause 22 | */ 23 | public NoAcceptableServiceInstanceFoundException(String message, Throwable cause) { 24 | super(message, cause); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/NoServiceInstanceFoundException.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | /** 4 | * Thrown by a {@link LoadBalancer} when it doesn't have service instances to choose from 5 | * or all available services are not valid to select, e.g. are determined to be faulty 6 | */ 7 | public class NoServiceInstanceFoundException extends RuntimeException { 8 | 9 | /** 10 | * Creates a new NoServiceInstanceFoundException. 11 | * 12 | * @param message the error message 13 | */ 14 | public NoServiceInstanceFoundException(String message) { 15 | super(message); 16 | } 17 | 18 | /** 19 | * Creates a new NoServiceInstanceFoundException. 20 | * 21 | * @param message the error message 22 | * @param cause the cause 23 | */ 24 | public NoServiceInstanceFoundException(String message, Throwable cause) { 25 | super(message, cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/NoSuchServiceDefinitionException.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | /** 4 | * Exception thrown when Stork does not have a {@link Service} associated with a given name. 5 | */ 6 | public class NoSuchServiceDefinitionException extends RuntimeException { 7 | 8 | /** 9 | * Creates a new instance of NoSuchServiceDefinitionException. 10 | * 11 | * @param serviceName the service name 12 | */ 13 | public NoSuchServiceDefinitionException(String serviceName) { 14 | super("No service defined for name " + serviceName); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/NoSuchServiceRegistrarException.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | /** 4 | * Exception thrown when Stork does not have a {@link ServiceRegistrar} associated with a given name. 5 | */ 6 | public class NoSuchServiceRegistrarException extends RuntimeException { 7 | 8 | /** 9 | * Creates a new instance of NoSuchServiceRegistrarException. 10 | * 11 | * @param registrarName the registrar name 12 | */ 13 | public NoSuchServiceRegistrarException(String registrarName) { 14 | super("No service registrar for name " + registrarName); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/ServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | import java.util.List; 4 | 5 | import io.smallrye.mutiny.Uni; 6 | 7 | /** 8 | * Interface to retrieve the list of all available service instances for a given service. 9 | */ 10 | public interface ServiceDiscovery { 11 | /** 12 | * Retrieves the service instances. 13 | *

14 | * This retrieval is an asynchronous action, thus, the method returns a {@link Uni} 15 | * 16 | * @return all `ServiceInstance`'s for the service 17 | */ 18 | Uni> getServiceInstances(); 19 | 20 | /** 21 | * Optional initialization. 22 | * This method will be invoked after all service discoveries and load balancers are registered in Stork 23 | * 24 | * @param stork the stork instance managing the service. 25 | */ 26 | default void initialize(StorkServiceRegistry stork) { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/ServiceRegistrar.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | 5 | public interface ServiceRegistrar & MetadataKey> { 6 | 7 | default Uni registerServiceInstance(String serviceName, String ipAddress, int defaultPort) { 8 | checkAddressNotNull(ipAddress); 9 | return registerServiceInstance(serviceName, Metadata.empty(), ipAddress, defaultPort); 10 | } 11 | 12 | default void checkAddressNotNull(String ipAddress) { 13 | if (ipAddress == null || ipAddress.isEmpty() || ipAddress.isBlank()) { 14 | throw new IllegalArgumentException("Parameter ipAddress should be provided."); 15 | } 16 | } 17 | 18 | Uni registerServiceInstance(String serviceName, Metadata metadata, String ipAddress, 19 | int defaultPort); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/StorkServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | 6 | /** 7 | * The central API of Stork 8 | */ 9 | public interface StorkServiceRegistry { 10 | 11 | /** 12 | * Retrieves the {@link Service} associated with the given name. 13 | * 14 | * @param serviceName the service name, must not be {@code null} 15 | * @return the service 16 | * @throws NoSuchServiceDefinitionException if there is no service associated with the given name. 17 | */ 18 | Service getService(String serviceName); 19 | 20 | /** 21 | * Retrieves the {@link Service} associated with the given name. 22 | * Unlike {@link #getService(String)} this method returns an {@link Optional} and so does not throw a 23 | * {@link NoSuchServiceDefinitionException} if there is no {@link Service} associated with the given name. 24 | * 25 | * @param serviceName the service name, must not be {@code null} 26 | * @return an {@link Optional} containing the {@link Service} if any, empty otherwise. 27 | */ 28 | Optional getServiceOptional(String serviceName); 29 | 30 | /** 31 | * Adds a service to the list of services managed by Stork. 32 | * The service is only added if there is no service with that name already defined. 33 | * Otherwise, the service definition is ignored. 34 | * 35 | * @param name the service name, must not be {@code null} or blank 36 | * @param definition the definition, must not be {@code null} 37 | * @return the Stork instance. 38 | * // TODO Define exception. 39 | */ 40 | StorkServiceRegistry defineIfAbsent(String name, ServiceDefinition definition); 41 | 42 | /** 43 | * @return all the services managed by Stork. The returned map is immutable. 44 | */ 45 | Map getServices(); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ConfigWithType.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Load balancer configuration. Only used internally. The configuration is translated to an object 7 | * that provides parameters through getters 8 | */ 9 | public interface ConfigWithType { 10 | 11 | /** 12 | * Load balancer type, e.g. "round-robin". 13 | * A LoadBalancerProvider for the type has to be available 14 | * 15 | * @return load balancer type 16 | */ 17 | String type(); 18 | 19 | /** 20 | * Load Balancer parameters. 21 | * 22 | * @return map of parameters of the load balancer 23 | */ 24 | Map parameters(); 25 | } 26 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/Constants.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | /** 4 | * Constants for the main API classes 5 | */ 6 | public class Constants { 7 | /** 8 | * Marker value for the default value, used in main API module annotations 9 | */ 10 | public static final String DEFAULT_VALUE = "__$$_DEFAULT_VALUE_$$__"; 11 | } 12 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/LoadBalancerAttribute.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | 5 | import java.lang.annotation.Repeatable; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Defines an attribute for a load balancer. 12 | * 13 | * The name of the attribute corresponds to the value used in the configuration, e.g. for name {@code my-attribute}, 14 | * use the following to set the value: 15 | * {@code stork..load-balancer.my-attribute} 16 | * 17 | * In the configuration class generated for the load balancer, this attribute will be exposed through {@code getMyAttribute()} 18 | */ 19 | @Retention(RetentionPolicy.RUNTIME) 20 | @Target(TYPE) 21 | @Repeatable(LoadBalancerAttributes.class) 22 | public @interface LoadBalancerAttribute { 23 | /** 24 | * Attribute name as used in the configuration. Exposed through a getter with name converted to camelCase with 25 | * characters that cannot be used in a java identifiers filtered out. 26 | * 27 | * @return the name of the configuration property 28 | */ 29 | String name(); 30 | 31 | /** 32 | * Default value for the attribute. If not provided and user didn't set the value - null will be passed 33 | * 34 | * @return the default value 35 | */ 36 | String defaultValue() default Constants.DEFAULT_VALUE; 37 | 38 | /** 39 | * Description of the attribute. Works best in the documentation if it starts with a capital letter and ends with period. 40 | * 41 | * @return the description 42 | */ 43 | String description(); 44 | 45 | /** 46 | * Whether the attribute is mandatory or optional 47 | * 48 | * @return true if the attribute is required, false otherwise 49 | */ 50 | boolean required() default false; 51 | } 52 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/LoadBalancerAttributes.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation used to define arrays of {@link LoadBalancerAttribute}. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | public @interface LoadBalancerAttributes { 14 | /** 15 | * @return the array of {@link LoadBalancerAttribute}, must not contain {@code null}. 16 | */ 17 | LoadBalancerAttribute[] value(); 18 | } 19 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/LoadBalancerType.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * The type of the load balancer. Use this annotation on your {@link io.smallrye.stork.spi.LoadBalancerProvider} 11 | * 12 | * The type is used to determine load balancer for services. 13 | * 14 | * Use {@code stork..load-balancer.type=my-load-balancer} to use a 15 | * {@link io.smallrye.stork.spi.LoadBalancerProvider} annotated with {@code @LoadBalancerType("my-load-balancer")} 16 | */ 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(TYPE) 19 | public @interface LoadBalancerType { 20 | /** 21 | * 22 | * @return the type of the load balance 23 | */ 24 | String value(); 25 | } 26 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ServiceConfig.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | /** 4 | * Service configuration, wraps (optional) LoadBalancer configuration, (required) ServiceDiscovery configuration and (optional) 5 | * ServiceRegistrar configuration 6 | * for a single service 7 | */ 8 | public interface ServiceConfig { 9 | /** 10 | * 11 | * @return (required) name of the service 12 | */ 13 | String serviceName(); 14 | 15 | /** 16 | * LoadBalancer configuration or null if the service is meant only to only be mapped to a list of services 17 | * 18 | * @return (optional) load balancer configuration 19 | */ 20 | ConfigWithType loadBalancer(); 21 | 22 | /** 23 | * ServiceDiscovery configuration for the service 24 | * 25 | * @return (required) service discovery configuration 26 | */ 27 | ConfigWithType serviceDiscovery(); 28 | 29 | /** 30 | * ServiceRegistrar configuration for the service 31 | * 32 | * @return (required) service registrar configuration 33 | */ 34 | ConfigWithType serviceRegistrar(); 35 | 36 | /** 37 | * Whether the communication should use a secure connection (e.g. HTTPS) 38 | * 39 | * @return true if SSL, TLS, etc. should be used for the communication 40 | * @deprecated Use the service discovery 'secure' attribute instead 41 | */ 42 | @Deprecated 43 | default boolean secure() { 44 | return false; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ServiceDiscoveryAttribute.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | 5 | import java.lang.annotation.Repeatable; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Defines an attribute for a service discovery. 12 | * 13 | * The name of the attribute corresponds to the value used in the configuration, e.g. for name {@code my-attribute}, 14 | * use the following to set the value: 15 | * {@code stork..service-discovery.my-attribute} 16 | * 17 | * In the configuration class generated for the service discovery, this attribute will be exposed through 18 | * {@code getMyAttribute()} 19 | */ 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Target(TYPE) 22 | @Repeatable(ServiceDiscoveryAttributes.class) 23 | public @interface ServiceDiscoveryAttribute { 24 | /** 25 | * Attribute name as used in the configuration. Exposed through a getter with name converted to camelCase with 26 | * characters that cannot be used in a java identifiers filtered out. 27 | * 28 | * @return the name of the configuration property 29 | */ 30 | String name(); 31 | 32 | /** 33 | * Default value for the attribute. If not provided and user didn't set the value - null will be passed 34 | * 35 | * @return the default value 36 | */ 37 | String defaultValue() default Constants.DEFAULT_VALUE; 38 | 39 | /** 40 | * Description of the attribute. Works best in the documentation if it starts with a capital letter and ends with period. 41 | * 42 | * @return the description 43 | */ 44 | String description(); 45 | 46 | /** 47 | * Whether the attribute is mandatory or optional 48 | * 49 | * @return true if the attribute is required, false otherwise 50 | */ 51 | boolean required() default false; 52 | } 53 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ServiceDiscoveryAttributes.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation used to define arrays of {@link ServiceDiscoveryAttribute}. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | public @interface ServiceDiscoveryAttributes { 14 | /** 15 | * @return the array of {@link ServiceDiscoveryAttribute}, must not contain {@code null}. 16 | */ 17 | ServiceDiscoveryAttribute[] value(); 18 | } 19 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ServiceDiscoveryType.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * The type of the service discovery. Use this annotation on your {@link io.smallrye.stork.spi.ServiceDiscoveryProvider} 11 | * 12 | * The type is used to determine service discovery for services. 13 | * 14 | * Use {@code stork..service-discovery.type=my-service-discovery} to use a 15 | * {@link io.smallrye.stork.spi.ServiceDiscoveryProvider} annotated with {@code @ServiceDiscoveryType("my-service-discovery")} 16 | */ 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(TYPE) 19 | public @interface ServiceDiscoveryType { 20 | /** 21 | * 22 | * @return the type of the service discovery 23 | */ 24 | String value(); 25 | } 26 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ServiceRegistrarAttribute.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | 5 | import java.lang.annotation.Repeatable; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Defines an attribute for a service registrar. 12 | * 13 | * The name of the attribute corresponds to the value used in the configuration, e.g. for name {@code my-attribute}, 14 | * use the following to set the value: 15 | * {@code stork-registrar..my-attribute} 16 | * 17 | * In the configuration class generated for the service registrar, this attribute will be exposed through 18 | * {@code getMyAttribute()} 19 | */ 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Target(TYPE) 22 | @Repeatable(ServiceRegistrarAttributes.class) 23 | public @interface ServiceRegistrarAttribute { 24 | /** 25 | * Attribute name as used in the configuration. Exposed through a getter with name converted to camelCase with 26 | * characters that cannot be used in a java identifiers filtered out. 27 | * 28 | * @return the name of the configuration property 29 | */ 30 | String name(); 31 | 32 | /** 33 | * Default value for the attribute. If not provided and user didn't set the value - null will be passed 34 | * 35 | * @return the default value 36 | */ 37 | String defaultValue() default Constants.DEFAULT_VALUE; 38 | 39 | /** 40 | * Description of the attribute. Works best in the documentation if it starts with a capital letter and ends with period. 41 | * 42 | * @return the description 43 | */ 44 | String description(); 45 | 46 | /** 47 | * Whether the attribute is mandatory or optional 48 | * 49 | * @return true if the attribute is required, false otherwise 50 | */ 51 | boolean required() default false; 52 | } 53 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ServiceRegistrarAttributes.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation used to define arrays of {@link ServiceRegistrarAttribute}. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | public @interface ServiceRegistrarAttributes { 14 | /** 15 | * @return the array of {@link ServiceRegistrarAttribute}, must not contain {@code null}. 16 | */ 17 | ServiceRegistrarAttribute[] value(); 18 | } 19 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ServiceRegistrarConfig.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | public interface ServiceRegistrarConfig extends ConfigWithType { 4 | String name(); 5 | } 6 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/config/ServiceRegistrarType.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.config; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import io.smallrye.stork.api.MetadataKey; 10 | import io.smallrye.stork.spi.ServiceRegistrarProvider; 11 | 12 | /** 13 | * The type of the service registrar. Use this annotation on your {@link ServiceRegistrarProvider} 14 | *

15 | * The type is used to determine service registrar. 16 | *

17 | * Use {@code stork.my-registration.service-registrar.type=my-service-registrar} to use the 18 | * {@link ServiceRegistrarProvider} annotated with {@code @ServiceRegistrar("my-service-registrar")} 19 | */ 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Target(TYPE) 22 | public @interface ServiceRegistrarType { 23 | /** 24 | * @return the type of the service registrar 25 | */ 26 | String value(); 27 | 28 | /** 29 | * @return metadata key type for the service registrar. Must match the second type argument of the service registrar 30 | * provider 31 | */ 32 | Class metadataKey(); 33 | } 34 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/observability/NoopObservationCollector.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.observability; 2 | 3 | import java.util.List; 4 | 5 | import io.smallrye.stork.api.ServiceInstance; 6 | 7 | public class NoopObservationCollector implements ObservationCollector { 8 | 9 | private static final StorkEventHandler NOOP_HANDLER = ev -> { 10 | // NOOP 11 | }; 12 | 13 | public static final StorkObservation NOOP_STORK_EVENT = new StorkObservation( 14 | null, null, 15 | null, NOOP_HANDLER) { 16 | @Override 17 | public void onServiceDiscoverySuccess(List instances) { 18 | // Noop 19 | } 20 | 21 | @Override 22 | public void onServiceDiscoveryFailure(Throwable throwable) { 23 | // Noop 24 | } 25 | 26 | @Override 27 | public void onServiceSelectionSuccess(long id) { 28 | // Noop 29 | } 30 | 31 | @Override 32 | public void onServiceSelectionFailure(Throwable throwable) { 33 | // Noop 34 | } 35 | }; 36 | 37 | @Override 38 | public StorkObservation create(String serviceName, String serviceDiscoveryType, 39 | String serviceSelectionType) { 40 | return NOOP_STORK_EVENT; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/observability/ObservationCollector.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.observability; 2 | 3 | public interface ObservationCollector { 4 | 5 | StorkObservation create(String serviceName, String serviceDiscoveryType, String serviceSelectionType); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/api/observability/StorkEventHandler.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api.observability; 2 | 3 | public interface StorkEventHandler { 4 | void complete(StorkObservation event); 5 | } 6 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/CallStatisticsCollector.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi; 2 | 3 | /** 4 | * If you use {@code ServiceInstanceWithStatGathering}, use an implementation of this class interface 5 | * for actually collecting information about calls made 6 | */ 7 | public interface CallStatisticsCollector { 8 | 9 | /** 10 | * Records the start of an operation. 11 | * 12 | * @param serviceInstanceId the service instance id 13 | * @param measureTime whether the time must be measured 14 | */ 15 | default void recordStart(long serviceInstanceId, boolean measureTime) { 16 | } 17 | 18 | /** 19 | * Records the response from an operation. 20 | * 21 | * @param serviceInstanceId the service instance id 22 | * @param timeInNanos the duration of the operation in nanoseconds 23 | */ 24 | default void recordReply(long serviceInstanceId, long timeInNanos) { 25 | 26 | } 27 | 28 | /** 29 | * Records the completion of an operation. 30 | * 31 | * @param serviceInstanceId the service instance id 32 | * @param error the error thrown by the operation if the operation failed 33 | */ 34 | default void recordEnd(long serviceInstanceId, Throwable error) { 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/ElementWithType.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi; 2 | 3 | /** 4 | * A class having a {@link #type()} method such a load balancer and service discovery providers. 5 | */ 6 | public interface ElementWithType { 7 | /** 8 | * Gets the type. 9 | * 10 | * @return the type, must not be {@code null}, must not be blank 11 | */ 12 | String type(); 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/LoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi; 2 | 3 | import io.smallrye.stork.api.LoadBalancer; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | 6 | /** 7 | * A load balancer provider allowing to create instances of load balancers. 8 | *

9 | * Implementation should use the {@link io.smallrye.stork.api.config.LoadBalancerAttribute} to define attributes. 10 | * 11 | * @param the configuration type (class generated from the {@link io.smallrye.stork.api.config.LoadBalancerAttribute} 12 | * annotations). 13 | */ 14 | public interface LoadBalancerProvider { 15 | /** 16 | * Creates a load balancer instance 17 | * 18 | * @param config the configuration 19 | * @param serviceDiscovery the service discovery used for that service 20 | * @return the load balancer 21 | */ 22 | LoadBalancer createLoadBalancer(T config, ServiceDiscovery serviceDiscovery); 23 | } 24 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/ServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi; 2 | 3 | import io.smallrye.stork.api.ServiceDiscovery; 4 | import io.smallrye.stork.api.config.ServiceConfig; 5 | 6 | /** 7 | * A service discovery provider allowing to create instances of service discovery. 8 | *

9 | * Implementation should use the {@link io.smallrye.stork.api.config.ServiceDiscoveryAttribute} to define attributes. 10 | * 11 | * @param the configuration type (class generated from the {@link io.smallrye.stork.api.config.ServiceDiscoveryAttribute} 12 | * annotations). 13 | */ 14 | public interface ServiceDiscoveryProvider { 15 | 16 | /** 17 | * Creates a new instance of {@link ServiceDiscovery}. 18 | * 19 | * @param config the configuration, must not be {@code null} 20 | * @param serviceName the service name, must not be {@code null}, or blank. 21 | * @param serviceConfig the service config, must not be {@code null} 22 | * @param storkInfrastructure the stork infrastructure, must not be {@code null} 23 | * @return the service discovery instance 24 | */ 25 | ServiceDiscovery createServiceDiscovery(T config, String serviceName, 26 | ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure); 27 | } 28 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/ServiceRegistrarProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi; 2 | 3 | import io.smallrye.stork.api.MetadataKey; 4 | import io.smallrye.stork.api.ServiceRegistrar; 5 | 6 | public interface ServiceRegistrarProvider & MetadataKey> { 7 | ServiceRegistrar createServiceRegistrar(T config, String serviceRegistrarName, 8 | StorkInfrastructure infrastructure); 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/StorkInfrastructure.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import io.smallrye.stork.api.observability.NoopObservationCollector; 6 | import io.smallrye.stork.api.observability.ObservationCollector; 7 | 8 | /** 9 | * A provider for "utility" objects used by service discovery and load balancer implementations. 10 | * 11 | * The default implementation, {@code DefaultStorkInfrastructure} provides objects created by the passed supplier. 12 | * Vendors can implement their own version of this class to provide custom objects. 13 | * 14 | * E.g. Quarkus uses a single Vert.x instance throughout the project and overrides this to return this Vert.x instance 15 | */ 16 | public interface StorkInfrastructure { 17 | /** 18 | * Get an instance of a "utility" class 19 | * 20 | * @param utilityClass class of the requested object 21 | * @param defaultSupplier should be used by the implementation to create an object if the environment doesn't provide one, 22 | * the result value can be cached. 23 | * @param type of the utility object 24 | * 25 | * @return the utility object 26 | * 27 | * @throws NullPointerException if utilityClass or defaultSupplier are null 28 | */ 29 | T get(Class utilityClass, Supplier defaultSupplier); 30 | 31 | default ObservationCollector getObservationCollector() { 32 | return new NoopObservationCollector(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/config/ConfigProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi.config; 2 | 3 | import java.util.List; 4 | 5 | import io.smallrye.stork.api.config.ServiceConfig; 6 | 7 | /** 8 | * Configuration provider for Service Discovery and Load Balancer 9 | */ 10 | public interface ConfigProvider { 11 | /** 12 | * Get a list of service configurations, each wrapping a configuration for both 13 | * ServiceDiscovery and LoadBalancer 14 | * 15 | * @return a list of configurations 16 | */ 17 | List getConfigs(); 18 | 19 | /** 20 | * Priority of the configuration provider. 21 | * A single ConfigProvider is used in an application, the one with the highest priority 22 | * 23 | * @return the priority 24 | */ 25 | int priority(); 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/config/SimpleRegistrarConfig.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi.config; 2 | 3 | import java.util.Map; 4 | 5 | import io.smallrye.stork.api.config.ServiceRegistrarConfig; 6 | 7 | public class SimpleRegistrarConfig implements ServiceRegistrarConfig { 8 | private final String type; 9 | private final String name; 10 | private final Map parameters; 11 | 12 | public SimpleRegistrarConfig(String type, String name, Map parameters) { 13 | this.type = type; 14 | this.name = name; 15 | this.parameters = parameters; 16 | } 17 | 18 | @Override 19 | public String type() { 20 | return type; 21 | } 22 | 23 | @Override 24 | public Map parameters() { 25 | return parameters; 26 | } 27 | 28 | @Override 29 | public String name() { 30 | return name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/internal/LoadBalancerLoader.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi.internal; 2 | 3 | import io.smallrye.stork.api.LoadBalancer; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | import io.smallrye.stork.api.config.ConfigWithType; 6 | import io.smallrye.stork.spi.ElementWithType; 7 | 8 | /** 9 | * Used by stork internals to generate service loader for LoadBalancerProvider 10 | */ 11 | public interface LoadBalancerLoader extends ElementWithType { 12 | /** 13 | * Creates a load balancer instance. 14 | * 15 | * @param config the configuration, must not be {@code null} 16 | * @param serviceDiscovery the service discovery used for that service 17 | * @return the load balancer 18 | */ 19 | LoadBalancer createLoadBalancer(ConfigWithType config, ServiceDiscovery serviceDiscovery); 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/internal/ServiceDiscoveryLoader.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi.internal; 2 | 3 | import io.smallrye.stork.api.ServiceDiscovery; 4 | import io.smallrye.stork.api.config.ConfigWithType; 5 | import io.smallrye.stork.api.config.ServiceConfig; 6 | import io.smallrye.stork.spi.ElementWithType; 7 | import io.smallrye.stork.spi.StorkInfrastructure; 8 | 9 | /** 10 | * Used by stork internals to generate service loader for ServiceDiscoveryProvider. 11 | */ 12 | public interface ServiceDiscoveryLoader extends ElementWithType { 13 | 14 | /** 15 | * Creates a new {@link ServiceDiscovery} instance. 16 | * 17 | * @param config the service discovery configuration, must not be {@code null} 18 | * @param serviceName the service name, must not be {@code null} or blank 19 | * @param serviceConfig the service configuration, must not be {@code null} 20 | * @param storkInfrastructure the stork infrastructure, must not be {@code null} 21 | * @return the new {@link ServiceDiscovery} 22 | */ 23 | ServiceDiscovery createServiceDiscovery(ConfigWithType config, String serviceName, 24 | ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure); 25 | } 26 | -------------------------------------------------------------------------------- /api/src/main/java/io/smallrye/stork/spi/internal/ServiceRegistrarLoader.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.spi.internal; 2 | 3 | import io.smallrye.stork.api.MetadataKey; 4 | import io.smallrye.stork.api.ServiceRegistrar; 5 | import io.smallrye.stork.api.config.ConfigWithType; 6 | import io.smallrye.stork.spi.ElementWithType; 7 | import io.smallrye.stork.spi.StorkInfrastructure; 8 | 9 | /** 10 | * Used by stork internals to generate service loader for ServiceRegistrarProvider. 11 | */ 12 | public interface ServiceRegistrarLoader & MetadataKey> extends ElementWithType { 13 | 14 | /** 15 | * Creates a new {@link ServiceRegistrar} instance. 16 | * 17 | * @param config the service registrar configuration, must not be {@code null} 18 | * @param serviceName 19 | * @param storkInfrastructure the stork infrastructure, must not be {@code null} 20 | * @return the new {@link ServiceRegistrar} 21 | */ 22 | ServiceRegistrar createServiceRegistrar(ConfigWithType config, 23 | String serviceName, StorkInfrastructure storkInfrastructure); 24 | } 25 | -------------------------------------------------------------------------------- /api/src/test/java/io/smallrye/stork/api/NoAcceptableServiceInstanceFoundExceptionTest.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | class NoAcceptableServiceInstanceFoundExceptionTest { 7 | 8 | @Test 9 | void canCreateNoAcceptableServiceInstanceFoundException() { 10 | NoAcceptableServiceInstanceFoundException exception = new NoAcceptableServiceInstanceFoundException("missing"); 11 | Assertions.assertEquals("missing", exception.getMessage()); 12 | Assertions.assertNull(exception.getCause()); 13 | 14 | Exception cause = new ArithmeticException("boom"); 15 | exception = new NoAcceptableServiceInstanceFoundException("missing", cause); 16 | Assertions.assertEquals("missing", exception.getMessage()); 17 | Assertions.assertEquals(cause, exception.getCause()); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /api/src/test/java/io/smallrye/stork/api/NoServiceInstanceFoundExceptionTest.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | class NoServiceInstanceFoundExceptionTest { 7 | 8 | @Test 9 | void canCreateNoServiceInstanceFoundException() { 10 | NoServiceInstanceFoundException exception = new NoServiceInstanceFoundException("missing"); 11 | Assertions.assertEquals("missing", exception.getMessage()); 12 | Assertions.assertNull(exception.getCause()); 13 | 14 | Exception cause = new ArithmeticException("boom"); 15 | exception = new NoServiceInstanceFoundException("missing", cause); 16 | Assertions.assertEquals("missing", exception.getMessage()); 17 | Assertions.assertEquals(cause, exception.getCause()); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /api/src/test/java/io/smallrye/stork/api/ServiceInstanceTest.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.api; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | 9 | /** 10 | * Verify the default implementation for {@link ServiceInstance}. 11 | */ 12 | class ServiceInstanceTest { 13 | 14 | ServiceInstance instance = new ServiceInstance() { 15 | @Override 16 | public long getId() { 17 | return 0; 18 | } 19 | 20 | @Override 21 | public String getHost() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public int getPort() { 27 | return 0; 28 | } 29 | 30 | @Override 31 | public Optional getPath() { 32 | return Optional.empty(); 33 | } 34 | 35 | @Override 36 | public boolean isSecure() { 37 | return false; 38 | } 39 | }; 40 | 41 | @Test 42 | void defaultMetadataShouldBeEmpty() { 43 | Metadata metadata = instance.getMetadata(); 44 | Assertions.assertNotNull(metadata); 45 | Assertions.assertNull(metadata.getMetadata().get("foo")); 46 | } 47 | 48 | @Test 49 | void defaultLabelsShouldBeEmpty() { 50 | Map labels = instance.getLabels(); 51 | Assertions.assertNotNull(labels); 52 | Assertions.assertTrue(labels.isEmpty()); 53 | } 54 | 55 | @Test 56 | void defaultStatisticsAreDisabled() { 57 | Assertions.assertFalse(instance.gatherStatistics()); 58 | Assertions.assertDoesNotThrow(() -> instance.recordStart(true)); 59 | Assertions.assertDoesNotThrow(() -> instance.recordReply()); 60 | Assertions.assertDoesNotThrow(() -> instance.recordEnd(null)); 61 | Assertions.assertDoesNotThrow(() -> instance.recordStart(false)); 62 | Assertions.assertDoesNotThrow(() -> instance.recordEnd(new Exception("boom"))); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /config-generator/src/main/java/io/smallrye/stork/config/generator/ClassName.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.config.generator; 2 | 3 | public class ClassName { 4 | public final String fullName; 5 | public final String simpleName; 6 | 7 | public ClassName(String fullName, String simpleName) { 8 | this.fullName = fullName; 9 | this.simpleName = simpleName; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /core/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork/" 17 | }, { 18 | "matcher" : "java-package", 19 | "match" : "/io\\.smallrye\\.stork\\.integration(\\..*)?/" 20 | }, { 21 | "matcher" : "java-package", 22 | "match" : "/io\\.smallrye\\.stork\\.utils(\\..*)?/" 23 | } ], 24 | "exclude" : [ "class io\\.smallrye\\.stork\\.impl\\..*" ] 25 | } 26 | } 27 | }, { 28 | "extension" : "revapi.differences", 29 | "id" : "breaking-changes", 30 | "configuration" : { 31 | "criticality" : "highlight", 32 | "minSeverity" : "POTENTIALLY_BREAKING", 33 | "minCriticality" : "documented", 34 | "differences" : [ ] 35 | } 36 | }, { 37 | "extension" : "revapi.reporter.json", 38 | "configuration" : { 39 | "minSeverity" : "POTENTIALLY_BREAKING", 40 | "minCriticality" : "documented", 41 | "output" : "target/compatibility.json", 42 | "indent" : true, 43 | "append" : false, 44 | "keepEmptyFile" : true 45 | } 46 | }, { 47 | "extension" : "revapi.reporter.text", 48 | "configuration" : { 49 | "minSeverity" : "POTENTIALLY_BREAKING", 50 | "minCriticality" : "documented", 51 | "output" : "out" 52 | } 53 | } ] -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/impl/ConsulMetadataKey.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.impl; 2 | 3 | import io.smallrye.stork.api.MetadataKey; 4 | 5 | /** 6 | * The Consul metadata keys. 7 | */ 8 | public enum ConsulMetadataKey implements MetadataKey { 9 | 10 | /** 11 | * The key for the consul service id. 12 | */ 13 | META_CONSUL_SERVICE_ID("consul-service-id"), 14 | /** 15 | * The key for the consul service node. 16 | */ 17 | META_CONSUL_SERVICE_NODE("consul-service-node"), 18 | /** 19 | * The key for the consul service node address. 20 | */ 21 | META_CONSUL_SERVICE_NODE_ADDRESS("consul-service-node-address"); 22 | 23 | private final String name; 24 | 25 | /** 26 | * Creates a new ConsulMetadataKey 27 | * 28 | * @param name the name 29 | */ 30 | ConsulMetadataKey(String name) { 31 | this.name = name; 32 | } 33 | 34 | @Override 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/impl/EurekaMetadataKey.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.impl; 2 | 3 | import io.smallrye.stork.api.MetadataKey; 4 | 5 | /** 6 | * The Eureka metadata keys. 7 | */ 8 | public enum EurekaMetadataKey implements MetadataKey { 9 | 10 | /** 11 | * The key for the eureka service id. 12 | */ 13 | META_EUREKA_SERVICE_ID("eureka-service-id"); 14 | 15 | private final String name; 16 | 17 | /** 18 | * Creates a new ConsulMetadataKey 19 | * 20 | * @param name the name 21 | */ 22 | EurekaMetadataKey(String name) { 23 | this.name = name; 24 | } 25 | 26 | @Override 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/impl/RoundRobinLoadBalancer.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | import io.smallrye.stork.api.LoadBalancer; 10 | import io.smallrye.stork.api.NoServiceInstanceFoundException; 11 | import io.smallrye.stork.api.ServiceInstance; 12 | 13 | public class RoundRobinLoadBalancer implements LoadBalancer { 14 | 15 | private final AtomicInteger index = new AtomicInteger(); 16 | 17 | @Override 18 | public ServiceInstance selectServiceInstance(Collection serviceInstances) { 19 | if (serviceInstances.isEmpty()) { 20 | throw new NoServiceInstanceFoundException("No services found."); 21 | } 22 | // todo do better, cache the list if possible maybe? 23 | List modifiableList = new ArrayList<>(serviceInstances); 24 | modifiableList.sort(Comparator.comparingLong(ServiceInstance::getId)); 25 | return select(modifiableList); 26 | } 27 | 28 | @Override 29 | public boolean requiresStrictRecording() { 30 | return false; 31 | } 32 | 33 | private ServiceInstance select(List instances) { 34 | if (instances.isEmpty()) { 35 | return null; 36 | } 37 | 38 | return instances.get(index.getAndIncrement() % instances.size()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/impl/RoundRobinLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.impl; 2 | 3 | import io.smallrye.stork.Stork; 4 | import io.smallrye.stork.api.LoadBalancer; 5 | import io.smallrye.stork.api.ServiceDiscovery; 6 | import io.smallrye.stork.api.config.LoadBalancerType; 7 | import io.smallrye.stork.spi.LoadBalancerProvider; 8 | 9 | /** 10 | * Round-robin is the only implementation built-in in the Stork API. 11 | * It is used when no load-balancer configuration is given. 12 | * 13 | * Note that it is not registered using the SPI, but directly in {@link Stork#initialize()}. 14 | */ 15 | @LoadBalancerType(RoundRobinLoadBalancerProvider.ROUND_ROBIN_TYPE) 16 | public class RoundRobinLoadBalancerProvider 17 | implements LoadBalancerProvider { 18 | 19 | public static final String ROUND_ROBIN_TYPE = "round-robin"; 20 | 21 | @Override 22 | public LoadBalancer createLoadBalancer(io.smallrye.stork.impl.RoundRobinConfiguration config, 23 | ServiceDiscovery serviceDiscovery) { 24 | return new RoundRobinLoadBalancer(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/impl/ServiceInstanceWithStatGathering.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.impl; 2 | 3 | import java.util.Optional; 4 | 5 | import io.smallrye.stork.api.ServiceInstance; 6 | import io.smallrye.stork.spi.CallStatisticsCollector; 7 | 8 | public class ServiceInstanceWithStatGathering implements ServiceInstance { 9 | private final ServiceInstance delegate; 10 | private final CallStatisticsCollector statistics; 11 | 12 | volatile long start; 13 | 14 | public ServiceInstanceWithStatGathering(ServiceInstance delegate, CallStatisticsCollector statistics) { 15 | this.delegate = delegate; 16 | this.statistics = statistics; 17 | } 18 | 19 | @Override 20 | public boolean gatherStatistics() { 21 | return true; 22 | } 23 | 24 | @Override 25 | public long getId() { 26 | return delegate.getId(); 27 | } 28 | 29 | @Override 30 | public String getHost() { 31 | return delegate.getHost(); 32 | } 33 | 34 | @Override 35 | public int getPort() { 36 | return delegate.getPort(); 37 | } 38 | 39 | @Override 40 | public Optional getPath() { 41 | return delegate.getPath(); 42 | } 43 | 44 | @Override 45 | public boolean isSecure() { 46 | return delegate.isSecure(); 47 | } 48 | 49 | @Override 50 | public void recordStart(boolean measureTime) { 51 | if (measureTime) { 52 | start = System.nanoTime(); 53 | } 54 | statistics.recordStart(getId(), measureTime); 55 | } 56 | 57 | @Override 58 | public void recordReply() { 59 | statistics.recordReply(getId(), System.nanoTime() - start); 60 | } 61 | 62 | @Override 63 | public void recordEnd(Throwable failure) { 64 | statistics.recordEnd(getId(), failure); 65 | } 66 | 67 | /** 68 | * Do not use for production code 69 | * 70 | * mock recording time for tests 71 | * 72 | * @param timeInNs time to record, in nanoseconds 73 | */ 74 | @Deprecated //for tests only 75 | public void mockRecordingTime(long timeInNs) { 76 | statistics.recordReply(getId(), timeInNs); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/integration/DefaultStorkInfrastructure.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.integration; 2 | 3 | import java.util.Map; 4 | import java.util.Objects; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.function.Supplier; 7 | 8 | import io.smallrye.stork.spi.StorkInfrastructure; 9 | 10 | /** 11 | * An implementation of {@link StorkInfrastructure} that creates utility objects from {@code defaultSupplier} 12 | * and caches them 13 | */ 14 | public class DefaultStorkInfrastructure implements StorkInfrastructure { 15 | private final Map, Object> utilities = new ConcurrentHashMap<>(); 16 | 17 | @SuppressWarnings("unchecked") 18 | @Override 19 | public T get(Class utilityClass, Supplier defaultSupplier) { 20 | Objects.requireNonNull(utilityClass, "utilityClass cannot be null"); 21 | Objects.requireNonNull(defaultSupplier, "defaultSupplier cannot be null"); 22 | return (T) utilities.computeIfAbsent(utilityClass, key -> defaultSupplier.get()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/integration/ObservableStorkInfrastructure.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.integration; 2 | 3 | import io.smallrye.stork.api.observability.ObservationCollector; 4 | 5 | public class ObservableStorkInfrastructure extends DefaultStorkInfrastructure { 6 | 7 | private final ObservationCollector observationCollector; 8 | 9 | public ObservableStorkInfrastructure(ObservationCollector observationCollector) { 10 | this.observationCollector = observationCollector; 11 | } 12 | 13 | @Override 14 | public ObservationCollector getObservationCollector() { 15 | return observationCollector; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/utils/DurationUtils.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.utils; 2 | 3 | import java.time.Duration; 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * A set of utility methods around durations. 8 | */ 9 | public class DurationUtils { 10 | 11 | private static final Pattern DIGITS = Pattern.compile("^[-+]?\\d+$"); 12 | 13 | private DurationUtils() { 14 | // Avoid direct instantiation 15 | } 16 | 17 | /** 18 | * Converts a value representing the refresh period which start with a number by implicitly appending `PT` to it. 19 | * If the value consists only of a number, it implicitly treats the value as seconds. 20 | * Otherwise, tries to convert the value assuming that it is in the accepted ISO-8601 duration format. 21 | * 22 | * @param duration duration as String 23 | * @param parameter the parameter for which we parse the value to duration 24 | * @return {@link Duration} 25 | */ 26 | public static Duration parseDuration(String duration, String parameter) { 27 | if (duration.startsWith("-")) { 28 | throw new IllegalArgumentException("Negative " + parameter + " specified for service discovery: " + duration); 29 | } 30 | if (DIGITS.asPredicate().test(duration)) { 31 | return Duration.ofSeconds(Long.parseLong(duration)); 32 | } 33 | return Duration.parse("PT" + duration); 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/utils/HostAndPort.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.utils; 2 | 3 | import java.util.Optional; 4 | 5 | /** 6 | * Structure representing a host:port address. 7 | */ 8 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 9 | public class HostAndPort { 10 | /** 11 | * The host. 12 | */ 13 | public final String host; 14 | /** 15 | * The port. 16 | */ 17 | public final int port; 18 | 19 | /** 20 | * The path, if any. 21 | */ 22 | public final Optional path; 23 | 24 | /** 25 | * Creates a new HostAndPort 26 | * 27 | * @param host the host 28 | * @param port the port 29 | * @param path the path, can be {@code null} 30 | */ 31 | public HostAndPort(String host, int port, String path) { 32 | this.host = host; 33 | this.port = port; 34 | this.path = Optional.ofNullable(path); 35 | } 36 | 37 | /** 38 | * Creates a new HostAndPort 39 | * 40 | * @param host the host 41 | * @param port the port 42 | */ 43 | public HostAndPort(String host, int port) { 44 | this(host, port, null); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/utils/InMemoryAddressesBackend.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class InMemoryAddressesBackend { 9 | 10 | private static Map> backend = new HashMap<>(); 11 | 12 | public static List getAddresses(String serviceName) { 13 | return backend.get(serviceName); 14 | } 15 | 16 | public static void add(String serviceName, String address) { 17 | if (serviceName == null || serviceName.length() == 0) { 18 | throw new IllegalArgumentException("No service name provided for address " + address); 19 | } 20 | if (backend.get(serviceName) != null) { 21 | if (!backend.get(serviceName).contains(address)) { 22 | backend.get(serviceName).add(address); 23 | } 24 | } else { 25 | List addresses = new ArrayList<>(); 26 | addresses.add(address); 27 | backend.put(serviceName, addresses); 28 | } 29 | } 30 | 31 | public static void clear(String serviceName) { 32 | if (backend != null) { 33 | backend.remove(serviceName); 34 | } 35 | } 36 | 37 | public static void clearAll() { 38 | backend.clear(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/utils/ServiceInstanceIds.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.utils; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | /** 6 | * Service instance id generator. 7 | */ 8 | // TODO: is this sufficient? 9 | public class ServiceInstanceIds { 10 | 11 | private static final AtomicLong idSequence = new AtomicLong(); 12 | 13 | /** 14 | * Gets the next, unused instance id. 15 | * 16 | * @return the next instance id 17 | */ 18 | public static Long next() { 19 | return idSequence.getAndIncrement(); 20 | } 21 | 22 | private ServiceInstanceIds() { 23 | // Avoid direct instantiation 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/io/smallrye/stork/utils/ServiceInstanceUtils.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.utils; 2 | 3 | import java.util.Collection; 4 | 5 | import io.smallrye.stork.api.ServiceInstance; 6 | 7 | /** 8 | * A set of utility methods around {@link ServiceInstance}. 9 | */ 10 | public class ServiceInstanceUtils { 11 | 12 | /** 13 | * Finds a matching instance for a given hostname and port 14 | * 15 | * @param serviceInstances the list of instances 16 | * @param hostname the hostname 17 | * @param port the port 18 | * @return the found instance or {@code null} if none matches 19 | */ 20 | public static ServiceInstance findMatching(Collection serviceInstances, String hostname, int port) { 21 | if (hostname == null) { 22 | throw new NullPointerException("Hostname cannot be null"); 23 | } 24 | for (ServiceInstance instance : serviceInstances) { 25 | if (hostname.equals(instance.getHost()) && port == instance.getPort()) { 26 | return instance; 27 | } 28 | } 29 | return null; 30 | } 31 | 32 | private ServiceInstanceUtils() { 33 | // Avoid direct instantiation. 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallrye/smallrye-stork/47751840baa0ae2fadc5e010e186526b86cb2bc6/core/src/main/resources/META-INF/beans.xml -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/EmptyLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import io.smallrye.stork.api.LoadBalancer; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | import io.smallrye.stork.api.config.LoadBalancerType; 6 | import io.smallrye.stork.spi.LoadBalancerProvider; 7 | 8 | @LoadBalancerType("empty-selector") 9 | public class EmptyLoadBalancerProvider implements LoadBalancerProvider { 10 | 11 | @Override 12 | public LoadBalancer createLoadBalancer(EmptySelectorConfiguration config, ServiceDiscovery serviceDiscovery) { 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/EmptyServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import io.smallrye.stork.api.ServiceDiscovery; 4 | import io.smallrye.stork.api.config.ServiceConfig; 5 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 6 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 7 | import io.smallrye.stork.spi.StorkInfrastructure; 8 | 9 | @ServiceDiscoveryType("empty") 10 | public class EmptyServiceDiscoveryProvider 11 | implements ServiceDiscoveryProvider { 12 | 13 | @Override 14 | public ServiceDiscovery createServiceDiscovery(EmptyConfiguration config, String serviceName, 15 | ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/FakeObservationCollector.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import io.smallrye.stork.api.observability.ObservationCollector; 4 | import io.smallrye.stork.api.observability.StorkEventHandler; 5 | import io.smallrye.stork.api.observability.StorkObservation; 6 | 7 | public class FakeObservationCollector implements ObservationCollector { 8 | 9 | private static final StorkEventHandler FAKE_HANDLER = ev -> { 10 | // FAKE 11 | }; 12 | 13 | public static StorkObservation FAKE_STORK_EVENT; 14 | 15 | @Override 16 | public StorkObservation create(String serviceName, String serviceDiscoveryType, 17 | String serviceSelectionType) { 18 | FAKE_STORK_EVENT = new StorkObservation( 19 | serviceName, serviceDiscoveryType, serviceSelectionType, 20 | FAKE_HANDLER); 21 | return FAKE_STORK_EVENT; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/MockLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import static org.mockito.Mockito.mock; 4 | 5 | import jakarta.enterprise.context.ApplicationScoped; 6 | 7 | import io.smallrye.stork.api.LoadBalancer; 8 | import io.smallrye.stork.api.ServiceDiscovery; 9 | import io.smallrye.stork.api.config.LoadBalancerType; 10 | import io.smallrye.stork.spi.LoadBalancerProvider; 11 | 12 | @LoadBalancerType("fake-selector") 13 | @ApplicationScoped 14 | public class MockLoadBalancerProvider implements LoadBalancerProvider { 15 | 16 | @Override 17 | public LoadBalancer createLoadBalancer(FakeSelectorConfiguration config, ServiceDiscovery serviceDiscovery) { 18 | return mock(LoadBalancer.class); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/MockServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import static org.mockito.Mockito.mock; 4 | 5 | import jakarta.enterprise.context.ApplicationScoped; 6 | 7 | import io.smallrye.stork.api.ServiceDiscovery; 8 | import io.smallrye.stork.api.config.ServiceConfig; 9 | import io.smallrye.stork.api.config.ServiceDiscoveryAttribute; 10 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 11 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 12 | import io.smallrye.stork.spi.StorkInfrastructure; 13 | 14 | @ServiceDiscoveryType("mock") 15 | @ServiceDiscoveryAttribute(name = "failure", description = "indicates if service discovery should fail") 16 | @ApplicationScoped 17 | public class MockServiceDiscoveryProvider implements ServiceDiscoveryProvider { 18 | 19 | @Override 20 | public ServiceDiscovery createServiceDiscovery(MockConfiguration config, String serviceName, ServiceConfig serviceConfig, 21 | StorkInfrastructure storkInfrastructure) { 22 | return mock(ServiceDiscovery.class); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/MyDataBean.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | @ApplicationScoped 6 | public class MyDataBean { 7 | 8 | private String value; 9 | 10 | public void set(String value) { 11 | this.value = value; 12 | } 13 | 14 | public String value() { 15 | return this.value; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/SingleLoadBalancer.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import java.util.Collection; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.NoServiceInstanceFoundException; 7 | import io.smallrye.stork.api.ServiceInstance; 8 | 9 | public class SingleLoadBalancer implements LoadBalancer { 10 | 11 | ServiceInstance instance; 12 | 13 | @Override 14 | public ServiceInstance selectServiceInstance(Collection serviceInstances) { 15 | 16 | if (instance == null && !serviceInstances.isEmpty()) { 17 | instance = serviceInstances.iterator().next(); 18 | return instance; 19 | } 20 | 21 | if (instance != null) { 22 | return instance; 23 | } 24 | 25 | throw new NoServiceInstanceFoundException("nope"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/SingleLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import io.smallrye.stork.api.LoadBalancer; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | import io.smallrye.stork.api.config.LoadBalancerType; 6 | import io.smallrye.stork.spi.LoadBalancerProvider; 7 | 8 | @LoadBalancerType("single") 9 | 10 | public class SingleLoadBalancerProvider implements LoadBalancerProvider { 11 | @Override 12 | public LoadBalancer createLoadBalancer(SingleConfiguration config, ServiceDiscovery serviceDiscovery) { 13 | return new SingleLoadBalancer(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/TestEnv.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.UncheckedIOException; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.HashSet; 11 | import java.util.List; 12 | import java.util.Set; 13 | import java.util.stream.Collectors; 14 | 15 | import io.smallrye.stork.api.config.ServiceConfig; 16 | import io.smallrye.stork.spi.config.ConfigProvider; 17 | 18 | public class TestEnv { 19 | public static final File SPI_ROOT = new File("target/test-classes/META-INF/services"); 20 | public static final List configurations = new ArrayList<>(); 21 | public static Set createdSpis = new HashSet<>(); 22 | 23 | @SuppressWarnings("unchecked") 24 | public static void install(Class itf, Class... impls) { 25 | File out = new File(SPI_ROOT, itf.getName()); 26 | if (out.isFile()) { 27 | throw new IllegalArgumentException(out.getAbsolutePath() + " does already exist"); 28 | } 29 | if (impls == null || impls.length == 0) { 30 | throw new IllegalArgumentException("The list of providers must not be `null` or empty"); 31 | } 32 | 33 | List list = Arrays.stream(impls).map(Class::getName).collect(Collectors.toList()); 34 | try { 35 | Files.write(out.toPath(), list); 36 | } catch (IOException e) { 37 | throw new UncheckedIOException(e); 38 | } 39 | createdSpis.add(out.toPath()); 40 | } 41 | 42 | public static void clearSPIs() throws IOException { 43 | for (Path createdSpi : createdSpis) { 44 | Files.delete(createdSpi); 45 | } 46 | createdSpis.clear(); 47 | } 48 | 49 | public static class AnchoredConfigProvider implements ConfigProvider { 50 | 51 | @Override 52 | public List getConfigs() { 53 | return new ArrayList<>(configurations); 54 | } 55 | 56 | @Override 57 | public int priority() { 58 | return 5; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/TestMetadataKey.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import io.smallrye.stork.api.MetadataKey; 4 | 5 | public enum TestMetadataKey implements MetadataKey { 6 | 7 | /** 8 | * The key for the consul service id. 9 | */ 10 | META_CONSUL_SERVICE_ID("consul-service-id"), 11 | /** 12 | * The key for the consul service node. 13 | */ 14 | META_CONSUL_SERVICE_NODE("consul-service-node"), 15 | /** 16 | * The key for the consul service node address. 17 | */ 18 | META_CONSUL_SERVICE_NODE_ADDRESS("consul-service-node-address"); 19 | 20 | private final String name; 21 | 22 | /** 23 | * Creates a new ConsulMetadataKey 24 | * 25 | * @param name the name 26 | */ 27 | TestMetadataKey(String name) { 28 | this.name = name; 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/TestServiceRegistrar.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.smallrye.mutiny.Uni; 7 | import io.smallrye.stork.api.Metadata; 8 | import io.smallrye.stork.api.ServiceRegistrar; 9 | import io.smallrye.stork.spi.StorkInfrastructure; 10 | 11 | public class TestServiceRegistrar implements ServiceRegistrar { 12 | private static final Logger log = LoggerFactory.getLogger(TestServiceRegistrar.class); 13 | private final TestRegistrarConfiguration config; 14 | 15 | public TestServiceRegistrar(TestRegistrarConfiguration config, String serviceName, 16 | StorkInfrastructure infrastructure) { 17 | this.config = config; 18 | 19 | } 20 | 21 | @Override 22 | public Uni registerServiceInstance(String serviceName, Metadata metadata, String ipAddress, 23 | int defaultPort) { 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/TestServiceRegistrarProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import io.smallrye.stork.api.ServiceRegistrar; 4 | import io.smallrye.stork.api.config.ServiceRegistrarType; 5 | import io.smallrye.stork.spi.ServiceRegistrarProvider; 6 | import io.smallrye.stork.spi.StorkInfrastructure; 7 | 8 | @ServiceRegistrarType(value = "test", metadataKey = TestMetadataKey.class) 9 | public class TestServiceRegistrarProvider implements ServiceRegistrarProvider { 10 | 11 | @Override 12 | public ServiceRegistrar createServiceRegistrar(TestRegistrarConfiguration config, String serviceRegistrarName, 13 | StorkInfrastructure infrastructure) { 14 | return new TestServiceRegistrar(config, serviceRegistrarName, infrastructure); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/WeirdTypedLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import static org.mockito.Mockito.mock; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.config.LoadBalancerType; 8 | import io.smallrye.stork.spi.LoadBalancerProvider; 9 | 10 | @LoadBalancerType("these-arent-the-droids-youre-looking-for-selector") 11 | public class WeirdTypedLoadBalancerProvider 12 | implements LoadBalancerProvider { 13 | @Override 14 | public LoadBalancer createLoadBalancer(TheseArentTheDroidsYoureLookingForSelectorConfiguration config, 15 | ServiceDiscovery serviceDiscovery) { 16 | return mock(LoadBalancer.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/WeirdTypedServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | import io.smallrye.stork.api.config.ServiceConfig; 6 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 7 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 8 | import io.smallrye.stork.spi.StorkInfrastructure; 9 | 10 | @ServiceDiscoveryType("these-arent-the-droids-you-are-looking-for") 11 | public class WeirdTypedServiceDiscoveryProvider 12 | implements ServiceDiscoveryProvider { 13 | @Override 14 | public ServiceDiscovery createServiceDiscovery(TheseArentTheDroidsYouAreLookingForConfiguration config, String serviceName, 15 | ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { 16 | return () -> Uni.createFrom().item(() -> AnchoredServiceDiscoveryProvider.services); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/WeldTestBase.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork; 2 | 3 | import org.jboss.weld.environment.se.Weld; 4 | import org.jboss.weld.environment.se.WeldContainer; 5 | 6 | public class WeldTestBase { 7 | 8 | protected Weld weld; 9 | protected WeldContainer container; 10 | 11 | public WeldTestBase() { 12 | weld = new Weld(); 13 | weld.addBeanClass(MyDataBean.class); 14 | TestEnv.configurations.clear(); 15 | } 16 | 17 | public void run() { 18 | container = weld.initialize(); 19 | } 20 | 21 | public void close() { 22 | if (container != null) { 23 | container.close(); 24 | } else { 25 | weld.shutdown(); 26 | } 27 | TestEnv.configurations.clear(); 28 | } 29 | 30 | public T get(Class clazz) { 31 | return container.select(clazz).get(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /core/src/test/java/io/smallrye/stork/utils/DurationUtilsTest.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.utils; 2 | 3 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import java.time.Duration; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | public class DurationUtilsTest { 11 | 12 | @Test 13 | public void testOnlyNumberValueProvided() { 14 | Duration expectedDuration = Duration.ofSeconds(3); 15 | Duration actualDuration = DurationUtils.parseDuration("3", "refresh-period"); 16 | assertEquals(expectedDuration, actualDuration); 17 | } 18 | 19 | @Test 20 | public void testNumberWithUnitValueProvided() { 21 | Duration expectedDuration = Duration.ofMinutes(3); 22 | Duration actualDuration = DurationUtils.parseDuration("3M", "refresh-period"); 23 | assertEquals(expectedDuration, actualDuration); 24 | } 25 | 26 | @Test 27 | public void testValueStartingWithNumberAndInCorrectFormatProvided() { 28 | assertThatExceptionOfType(IllegalArgumentException.class) 29 | .isThrownBy(() -> { 30 | DurationUtils.parseDuration("-5", "refresh-period"); 31 | }).withMessage("Negative refresh-period specified for service discovery: -5"); 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /dco.txt: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /docs/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | mkdocs = "*" 8 | mkdocs-material = "*" 9 | mkdocs-macros-plugin = "*" 10 | mkdocs-build-plantuml-plugin = "*" 11 | mike = "*" 12 | 13 | [dev-packages] 14 | 15 | [requires] 16 | python_version = "3.12" 17 | -------------------------------------------------------------------------------- /docs/docs/diagrams/includes/themes/dark.puml: -------------------------------------------------------------------------------- 1 | !define BG_MAIN_COLOR #1A618C 2 | !define BG_SECONDARY_COLOR #616768 3 | !define BG_PACKAGE_COLOR #7a209f 4 | !define BORDER_COLOR #EEEEEE 5 | !define FONT_COLOR #EEEEEE 6 | !define ARROW_COLOR #EEEEEE 7 | 8 | ' include the general skinparams for both, light and dark 9 | ' to overwrite with the given settings 10 | !include general.puml -------------------------------------------------------------------------------- /docs/docs/diagrams/includes/themes/light.puml: -------------------------------------------------------------------------------- 1 | !define BG_MAIN_COLOR AliceBlue 2 | !define BG_SECONDARY_COLOR #B4BCBE 3 | !define BG_PACKAGE_COLOR #eeb291 4 | !define BORDER_COLOR #444444 5 | !define FONT_COLOR #444444 6 | !define ARROW_COLOR DarkGrey 7 | 8 | 9 | ' include the general skinparams for both, light and dark 10 | ' to overwrite with the given settings 11 | !include general.puml -------------------------------------------------------------------------------- /docs/docs/diagrams/observability_sequence.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | !include diagrams/includes/themes/light.puml 4 | 5 | skinparam sequenceMessageAlign center 6 | autonumber "(0)" 7 | 8 | 9 | participant Application 10 | participant ObservableStorkInfrastructure 11 | participant ObservationCollector 12 | participant Stork 13 | participant Service 14 | 15 | Application -> ObservableStorkInfrastructure : instantiates 16 | ObservableStorkInfrastructure -> ObservationCollector : instantiates 17 | ObservationCollector -> ObservableStorkInfrastructure: ObservationCollector 18 | ObservableStorkInfrastructure -> Application: ObservableStorkInfrastructure 19 | 20 | ... ... 21 | 22 | Application -> Stork : initialize(observableInfrastructure) 23 | Stork -> Service : instantiates (..., ObservationCollector, ...) 24 | @enduml -------------------------------------------------------------------------------- /docs/docs/diagrams/observation_sequence.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | !include diagrams/includes/themes/light.puml 4 | 5 | skinparam sequenceMessageAlign center 6 | autonumber "(0)" 7 | 8 | 9 | participant Service 10 | participant ObservationCollector 11 | participant StorkObservation 12 | participant StorkEventHandler 13 | 14 | Service -> ObservationCollector : create("serviceName", sd type, ss type) 15 | ObservationCollector -> StorkObservation : instantiates 16 | StorkObservation -> StorkObservation: Registers start time 17 | ObservationCollector -> Service : StorkObservation 18 | 19 | ... ... 20 | 21 | Service -> StorkObservation : onServiceDiscoverySuccess(List) 22 | StorkObservation -> StorkObservation : Registers end service\ndiscovery time.\nRegisters instances count\n 23 | ... ... 24 | 25 | Service -> StorkObservation : onServiceDiscoveryFailure(Throwable) 26 | StorkObservation -> StorkObservation : Registers end service\ndiscovery time.\nRegisters failure cause\n 27 | ... ... 28 | 29 | Service -> StorkObservation : onServiceSelectionSuccess(instanceId) 30 | StorkObservation -> StorkObservation : Registers end service\nselection time.\nRegisters instance id\nRegisters overall duration\n 31 | StorkObservation -> StorkEventHandler: complete(this) 32 | ... ... 33 | 34 | Service -> StorkObservation : onServiceSelectionFailure(Throwable)) 35 | StorkObservation -> StorkObservation : Registers overall duration.\nRegisters failure cause\n 36 | StorkObservation -> StorkEventHandler: complete(this) 37 | @enduml -------------------------------------------------------------------------------- /docs/docs/diagrams/sequence.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | !include diagrams/includes/themes/light.puml 4 | 5 | skinparam sequenceMessageAlign center 6 | autonumber "(0)" 7 | 8 | 9 | participant Application 10 | participant Stork 11 | participant ConfigProvider 12 | participant Service 13 | participant "Service Discovery" 14 | participant "Load Balancer" 15 | 16 | Application -> Stork : initialize() 17 | Stork -> ConfigProvider : getConfig() 18 | ConfigProvider -> Stork : : List 19 | 20 | ... ... 21 | 22 | Application -> Stork : getService("name") 23 | Stork -> Application: Service 24 | 25 | ... ... 26 | 27 | 28 | Application -> Service : selectServiceInstance() 29 | Service -> "Service Discovery" : getServiceInstances() 30 | "Service Discovery" ---> Service : Uni> 31 | Service -> "Load Balancer" : selectServiceInstance() 32 | "Load Balancer" ---> Service : Uni 33 | Service ---> Application : Uni 34 | 35 | @enduml -------------------------------------------------------------------------------- /docs/docs/diagrams/srv_sequence.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | !include diagrams/includes/themes/light.puml 3 | 4 | skinparam sequenceMessageAlign center 5 | 6 | 7 | participant Application 8 | participant Stork 9 | participant "DNS Server" 10 | 11 | Application -> Stork : get service instances 12 | Stork -> "DNS Server" : get SRV records for hostname 13 | "DNS Server" -> Stork : list of SRV records 14 | Stork -> Application : list of SRV record targets if no resolution desired 15 | Stork -> "DNS Server" : get A/AAAA records for each SRV record target 16 | "DNS Server" -> Stork : list of A/AAAA 17 | Stork -> Application : list of ServiceInstances 18 | 19 | @enduml 20 | -------------------------------------------------------------------------------- /docs/docs/diagrams/stork.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | !include diagrams/includes/themes/light.puml 3 | hide members 4 | class Stork <> 5 | interface ServiceDiscovery 6 | interface ServiceInstance 7 | interface LoadBalancer 8 | class Service <> 9 | 10 | Stork -- "*" Service 11 | Service -- ServiceDiscovery 12 | Service -- LoadBalancer 13 | 14 | Service --[dotted] ServiceInstance: selectServiceInstance 15 | 16 | class Kubernetes implements ServiceDiscovery 17 | class Consul implements ServiceDiscovery 18 | class "Custom Service Discovery" implements ServiceDiscovery 19 | 20 | class RoundRobin<> implements LoadBalancer 21 | class LeastResponseTime implements LoadBalancer 22 | class "Custom Load Balancer" implements LoadBalancer 23 | 24 | @enduml -------------------------------------------------------------------------------- /docs/docs/extra.css: -------------------------------------------------------------------------------- 1 | .md-header__title { 2 | margin-left: 0 !important; 3 | } 4 | 5 | img { 6 | margin-left: auto; 7 | margin-right: auto; 8 | max-width: 80% !important; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /docs/docs/images/problem-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallrye/smallrye-stork/47751840baa0ae2fadc5e010e186526b86cb2bc6/docs/docs/images/problem-dark.png -------------------------------------------------------------------------------- /docs/docs/images/problem-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallrye/smallrye-stork/47751840baa0ae2fadc5e010e186526b86cb2bc6/docs/docs/images/problem-light.png -------------------------------------------------------------------------------- /docs/docs/images/redhat_reversed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/docs/images/solution-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallrye/smallrye-stork/47751840baa0ae2fadc5e010e186526b86cb2bc6/docs/docs/images/solution-dark.png -------------------------------------------------------------------------------- /docs/docs/images/solution-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallrye/smallrye-stork/47751840baa0ae2fadc5e010e186526b86cb2bc6/docs/docs/images/solution-light.png -------------------------------------------------------------------------------- /docs/docs/images/stork-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallrye/smallrye-stork/47751840baa0ae2fadc5e010e186526b86cb2bc6/docs/docs/images/stork-white.png -------------------------------------------------------------------------------- /docs/docs/load-balancer/least-requests.md: -------------------------------------------------------------------------------- 1 | # Least Requests Load Balancing 2 | 3 | The `least-requests` load balancing strategy monitors the number of inflight calls and selects the less-used instance. 4 | 5 | This strategy keeps track of the inflight calls made by the application and picks the service instance with the smallest number of inflight requests: 6 | 7 | 1. when the selection happens, the service instance with the smallest number of inflight requests is selected, and this number is incremented 8 | 2. when the operation completes, successfully or not, the number of inflight requests is decremented 9 | 10 | ## Dependency 11 | 12 | First, you need to add the `least-requests` load-balancer to your project: 13 | 14 | ```xml 15 | 16 | io.smallrye.stork 17 | stork-load-balancer-least-requests 18 | {{version.current}} 19 | 20 | ``` 21 | 22 | ## Configuration 23 | 24 | For each service expected to use a least-response-time selection, configure the `load-balancer` to be `least-requests`: 25 | 26 | === "stork standalone" 27 | ```properties 28 | stork.my-service.load-balancer.type=least-requests 29 | ``` 30 | 31 | === "stork in quarkus" 32 | ```properties 33 | quarkus.stork.my-service.load-balancer.type=least-requests 34 | ``` 35 | 36 | Supported configuration properties are the following: 37 | 38 | --8<-- "target/attributes/META-INF/stork-docs/least-requests-lb-attributes.txt" -------------------------------------------------------------------------------- /docs/docs/load-balancer/overview.md: -------------------------------------------------------------------------------- 1 | ### Load Balancer / Service Selection in SmallRye Stork 2 | 3 | Once services are registered and discovered, the next critical step is selecting which service instance will handle a given request. 4 | SmallRye Stork provides flexible load balancing strategies to efficiently distribute requests across multiple instances of a service. 5 | This ensures optimal resource usage, improved performance, and high availability. 6 | 7 | #### Key Features: 8 | - **Multiple Load Balancing Strategies**: SmallRye Stork supports several built-in strategies for selecting service instances. 9 | Check them out in the following dedicated sections. 10 | - **Customizable Strategies**: You can define custom service selection strategies based on your unique use case or performance requirements, ensuring that the load balancer can adapt to specific needs. 11 | 12 | #### How it Works: 13 | Once a service has been registered and discovered, the load balancer comes into play when a client makes a request to that service. 14 | Stork applies the configured load balancing strategy to select an instance from the available pool of discovered services. 15 | 16 | This feature ensures that your services remain responsive, scalable, and resilient, providing a smooth experience for both users and developers. 17 | -------------------------------------------------------------------------------- /docs/docs/load-balancer/power-of-two-choices.md: -------------------------------------------------------------------------------- 1 | # Power Of Two Choices Load Balancing 2 | 3 | The `power-of-two-choices` load balancing selects two random service instances and then chooses the one with the least inflight requests. 4 | It avoids the overhead of `least-requests` and the worst case for `random` where it selects a busy destination. 5 | 6 | This strategy acts as follows: 7 | 8 | 1. when the selection happens, it picks two random instances from the list, 9 | 2. it returns the least loaded instance (based on the number of inflight requests), 10 | 3. when the operation completes, successfully or not, the number of inflight requests for the instance is decremented. 11 | 12 | Check [The Power of Two Random Choices](http://www.eecs.harvard.edu/~michaelm/NEWWORK/postscripts/twosurvey.pdf) paper to learn more about this pattern and the benefits. 13 | 14 | ## Dependency 15 | 16 | First, you need to add the random load-balancer to your project: 17 | 18 | ```xml 19 | 20 | io.smallrye.stork 21 | stork-load-balancer-power-of-two-choices 22 | {{version.current}} 23 | 24 | ``` 25 | 26 | ## Configuration 27 | 28 | For each service expected to use a random service selection, configure the `load-balancer` to be `power-of-two-choices`: 29 | 30 | 31 | === "stork standalone" 32 | ```properties 33 | stork.my-service.load-balancer.type=power-of-two-choices 34 | ``` 35 | 36 | === "stork in quarkus" 37 | ```properties 38 | quarkus.stork.my-service.load-balancer.type=power-of-two-choices 39 | ``` 40 | 41 | Supported configuration properties are the following: 42 | 43 | --8<-- "target/attributes/META-INF/stork-docs/power-of-two-choices-lb-attributes.txt" -------------------------------------------------------------------------------- /docs/docs/load-balancer/random.md: -------------------------------------------------------------------------------- 1 | # Random Load Balancing 2 | 3 | The `random` load balancing is a straightforward service instance selection solution that picks a random instance every time. 4 | 5 | ## Dependency 6 | 7 | First, you need to add the random load-balancer to your project: 8 | 9 | ```xml 10 | 11 | io.smallrye.stork 12 | stork-load-balancer-random 13 | {{version.current}} 14 | 15 | ``` 16 | 17 | ## Configuration 18 | 19 | For each service expected to use a random service selection, configure the `load-balancer` to be `random`: 20 | 21 | === "stork standalone" 22 | ```properties 23 | stork.my-service.load-balancer.type=random 24 | ``` 25 | 26 | === "stork in quarkus" 27 | ```properties 28 | quarkus.stork.my-service.load-balancer.type=random 29 | ``` 30 | 31 | Supported attributes are the following: 32 | 33 | --8<-- "target/attributes/META-INF/stork-docs/random-lb-attributes.txt" 34 | -------------------------------------------------------------------------------- /docs/docs/load-balancer/round-robin.md: -------------------------------------------------------------------------------- 1 | # Round-Robin Load Balancing 2 | 3 | The round-robin is a straightforward load-balancing solution that just iterates over the set of service instances. 4 | While being simple, this solution shares the load among the instances and may be sufficient in many cases. 5 | 6 | The round-robin strategy is the default load-balancing strategy. 7 | It is provided by Stork itself, and so does not require an additional dependency. 8 | 9 | ## Configuration 10 | 11 | There is no need to configure the load-balancing strategy to be `round-robin`. 12 | Stork automatically uses this strategy when none are configured. 13 | 14 | However, you can also configure it explicitly as follows: 15 | 16 | === "stork standalone" 17 | ```properties 18 | stork.my-service.load-balancer.type=round-robin 19 | ``` 20 | 21 | === "stork in quarkus" 22 | ```properties 23 | quarkus.stork.my-service.load-balancer.type=round-robin 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/docs/load-balancer/sticky.md: -------------------------------------------------------------------------------- 1 | # Sticky Load Balancing 2 | 3 | The sticky load balancer selects a single service instance and keeps using it until it fails. 4 | Then, it selects another one. 5 | 6 | It is possible to configure a backoff time to specify for how long a failing service instance should not be retried. 7 | 8 | Precisely, the load balancer works as follows: 9 | 10 | * if no service instance has been selected so far, select the first instance from the collection; 11 | * else if the previously selected service instance has not failed, and is still available, return it; 12 | * else return the first available service instance that has no recorded failure, if one exists; 13 | * else, find the available instance for which the time since the last failure is the longest, and 14 | * if the backoff time since the failure passed, return it; 15 | * or, throw an `NoAcceptableServiceInstanceFoundException` as no acceptable instances are available. 16 | 17 | ## Configuration 18 | 19 | To use the `sticky` load service selection strategy, set the load balancer type to `sticky`: 20 | 21 | === "stork standalone" 22 | ```properties 23 | stork.my-service.load-balancer.type=sticky 24 | ``` 25 | 26 | === "stork in quarkus" 27 | ```properties 28 | quarkus.stork.my-service.load-balancer.type=sticky 29 | ``` 30 | 31 | 32 | The following attributes are supported: 33 | 34 | --8<-- "target/attributes/META-INF/stork-docs/sticky-lb-attributes.txt" -------------------------------------------------------------------------------- /docs/docs/microprofile-config.md: -------------------------------------------------------------------------------- 1 | # Stork MicroProfile Config 2 | 3 | Stork integrates with MicroProfile Configuration out of the box, enabling seamless access to configuration properties. 4 | This documentation explains how Stork can retrieve configuration details from the MicroProfile Config file present in the classpath. 5 | Quarkus uses this approach for reading configuration details from the MicroProfile Config file located within the classpath. 6 | 7 | 8 | ## Dependency setup 9 | 10 | To enable MicroProfile Config integration in Stork, you need to include the following dependency: 11 | 12 | ```xml 13 | 14 | io.smallrye.stork 15 | smallrye-stork-microprofile 16 | {{version.current}} 17 | 18 | ``` 19 | 20 | ## Initializing Stork 21 | 22 | If your framework lacks a pre-configured Stork instance, you'll need to perform initialization: 23 | 24 | ```java linenums="1" 25 | {{ insert('examples/InitializationExample.java') }} 26 | ``` 27 | Upon initialization, Stork scans for the `io.smallrye.stork.config.MicroProfileConfigProvider` SPI provider and CDI beans (from version 2.x onwards). It then builds a comprehensive list of managed services by parsing the properties configuration files. 28 | 29 | -------------------------------------------------------------------------------- /docs/docs/programmatic-api.md: -------------------------------------------------------------------------------- 1 | # Stork Programmatic API 2 | 3 | Stork proposes a programmatic API that lets you register new service Definitions and do manual lookup and selection. 4 | When using the programmatic API of Stork, you can: 5 | Retrieve the singleton Stork instance. This instance is configured with the set of Services it manages. 6 | Register new service definition. 7 | Retrieve the Service you want to use. Each Service is associated with a name. 8 | Retrieve the ServiceInstance, which will provide the metadata to access the actual instance. 9 | 10 | 11 | ## Initializing Stork 12 | 13 | If your framework does not already provide a configured `Stork` instance, you need to do: 14 | 15 | ```java linenums="1" 16 | {{ insert('examples/InitializationExample.java') }} 17 | ``` 18 | 19 | ## Adding service dynamically 20 | 21 | To register a new `ServiceDefinition`, use the `defineIfAbsent` method: 22 | 23 | ```java linenums="1" 24 | {{ insert('examples/DefinitionExample.java') }} 25 | ``` 26 | 27 | The `ServiceDefinition` instances can be created from: 28 | 29 | - A service discovery configuration - these classes are provided by the service discovery implementations, 30 | - An optional load balancer configuration - these classes are provided by the load balancer implementations 31 | 32 | Attributes from the service discovery and load balancer can be configured from the `Configuration` classes. 33 | 34 | ## Looking for service instances 35 | 36 | To list the service instances for a given service, or to select an instance according to the load balancer strategy, use the following code: 37 | 38 | ```java linenums="1" 39 | {{ insert('examples/LookupExample.java') }} 40 | ``` 41 | 42 | The lookup and selection methods are returning Uni as these processes are asynchronous. 43 | 44 | ## All in one example 45 | 46 | The following snippet provides an _all in one_ example of the Stork programmatic API: 47 | 48 | ```java linenums="1" 49 | {{ insert('examples/StorkApiExample.java') }} 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/docs/service-discovery/consul.md: -------------------------------------------------------------------------------- 1 | # Consul Service Discovery 2 | 3 | [Consul](https://www.consul.io/) is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure. 4 | It's often used as service discovery backend to register and locate the services composing your system. 5 | Consul makes it simple for services to register themselves and to discover other services via a DNS or HTTP interface. 6 | External services can be registered as well. 7 | 8 | As [specified](https://developer.hashicorp.com/consul/api-docs/agent/service#address) in the Consul documentation, if the host address is not provided, Stork will automatically use the Consul node address for the instance. 9 | 10 | This page explains how Stork can use Consul to handle the service discovery and service registration. 11 | 12 | ## Dependency 13 | 14 | First, you need to add the Stork Consul Service Discovery provider: 15 | 16 | ```xml 17 | 18 | io.smallrye.stork 19 | stork-service-discovery-consul 20 | {{version.current}} 21 | 22 | ``` 23 | 24 | ### Service discovery configuration 25 | 26 | For each service that should get the service instances from Consul, configure the service discovery `type`: 27 | 28 | === "stork standalone" 29 | ```properties 30 | stork.my-service.service-discovery.type=consul 31 | ``` 32 | 33 | === "stork in quarkus" 34 | ```properties 35 | quarkus.stork.my-service.service-discovery.type=consul 36 | ``` 37 | 38 | Consul service discovery is configured with the following parameters: 39 | 40 | --8<-- "target/attributes/META-INF/stork-docs/consul-sd-attributes.txt" 41 | 42 | -------------------------------------------------------------------------------- /docs/docs/service-discovery/eureka.md: -------------------------------------------------------------------------------- 1 | # Eureka Service Discovery 2 | 3 | [Eureka](https://github.com/Netflix/eureka) is a RESTful service that is primarily used in the AWS cloud for the purpose of discovery, load balancing, and failover of middle-tier servers. 4 | 5 | This page explains how Stork can use Eureka to handle the service discovery and service registration. 6 | 7 | ## Dependency 8 | 9 | First, you need to add the Stork Eureka Service Discovery provider: 10 | 11 | ```xml 12 | 13 | io.smallrye.stork 14 | stork-service-discovery-eureka 15 | {{version.current}} 16 | 17 | ``` 18 | 19 | ### Service discovery configuration 20 | 21 | For each application instance expected to be registered in Eureka, configure the lookup: 22 | 23 | === "stork standalone" 24 | ```properties 25 | stork.my-service.service-discovery.type=eureka 26 | stork.my-service.service-discovery.eureka-host=localhost 27 | stork.my-service.service-discovery.eureka-port=8761 28 | ``` 29 | 30 | === "stork in quarkus" 31 | ```properties 32 | quarkus.stork.my-service.service-discovery.type=eureka 33 | quarkus.stork.my-service.service-discovery.eureka-host=localhost 34 | quarkus.stork.my-service.service-discovery.eureka-port=8761 35 | ``` 36 | 37 | 38 | Stork looks for the service with the given name (`my-service` in the previous example). 39 | 40 | Supported attributes are the following: 41 | 42 | --8<-- "target/attributes/META-INF/stork-docs/eureka-sd-attributes.txt" 43 | 44 | The `application` attribute is optional. 45 | It uses the Stork service name (`my-service` in the previous configuration) if not set. 46 | 47 | The `instance` attribute allows selecting a specific instance. 48 | Using this attribute prevents load-balancing as you will always select a single instance. 49 | 50 | The `secure` attribute indicates if you want the _secure virtual address_ of the application instance. 51 | If set to `true`, unsecured instances are filtered out from the available instances. 52 | 53 | -------------------------------------------------------------------------------- /docs/docs/service-discovery/overview.md: -------------------------------------------------------------------------------- 1 | ### Service Discovery in SmallRye Stork 2 | 3 | As already introduced, service discovery is a crucial part of modern microservices architectures. 4 | It allows services to dynamically discover the location of other services at runtime, which is particularly useful in distributed systems where services may scale up or down, 5 | or change their network addresses. 6 | 7 | SmallRye Stork provides a flexible and extensible mechanism for service discovery. 8 | It supports out of the box some service discovery such as Kubernetes or Consul but the main strength of it is customization so you can easily create your own implementation related on your business for example. 9 | Stork allows services to communicate with each other without requiring hardcoded addresses, making it an ideal solution for microservices deployments. 10 | SmallRye Stork brings this capability to clients for Quarkus applications but it's vendor agnostic so you easily use it with other solutions and even in standalone mode. 11 | 12 | You can explore the different implementations and learn how to create your own in the following sections. -------------------------------------------------------------------------------- /docs/docs/service-discovery/static-list.md: -------------------------------------------------------------------------------- 1 | # Static List Service Discovery 2 | 3 | In some situations, such as demos, development, or testing, you may want to mock the service discovery by providing a predefined list of service instances. 4 | For this purpose, Stork comes with a `static` service discovery type. 5 | 6 | ## Dependency 7 | 8 | To use the `static` service discovery, first add the appropriate Service Discovery provider dependency to your project: 9 | 10 | ```xml 11 | 12 | io.smallrye.stork 13 | stork-service-discovery-static-list 14 | {{version.current}} 15 | 16 | ``` 17 | 18 | ## Configuration 19 | 20 | For each service that should use the static list of service instances configure the service registrar `type`: 21 | 22 | === "stork standalone" 23 | ```properties 24 | stork.my-service.service-discovery.type=static 25 | stork.my-service.service-discovery.address-list=localhost:8080,localhost:8081 26 | ``` 27 | 28 | === "stork in quarkus" 29 | ```properties 30 | quarkus.stork.my-service.service-discovery.type=static 31 | quarkus.stork.my-service.service-discovery.address-list=localhost:8080,localhost:8081 32 | ``` 33 | 34 | These are all the static service discovery parameters: 35 | 36 | --8<-- "target/attributes/META-INF/stork-docs/static-sd-attributes.txt" 37 | -------------------------------------------------------------------------------- /docs/docs/service-registration/consul.md: -------------------------------------------------------------------------------- 1 | # Consul Service Registration 2 | 3 | [Consul](https://www.consul.io/) is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure. 4 | It's often used as service discovery backend to register and locate the services composing your system. 5 | Consul makes it simple for services to register themselves and to discover other services via a DNS or HTTP interface. 6 | External services can be registered as well. 7 | 8 | This page explains how Stork can use Consul to handle the service registration. 9 | 10 | ## Dependency 11 | 12 | First, you need to add the Stork Consul Service Registration provider: 13 | 14 | ```xml 15 | 16 | io.smallrye.stork 17 | stork-service-registration-consul 18 | {{version.current}} 19 | 20 | ``` 21 | 22 | ## Service registration configuration 23 | 24 | For each service that should register the service instances in Consul, configure the service registrar `type`: 25 | 26 | === "stork standalone" 27 | ```properties 28 | stork.my-service.service-registrar.type=consul 29 | ``` 30 | 31 | === "stork in quarkus" 32 | ```properties 33 | quarkus.stork.my-service.service-registrar.type=consul 34 | ``` 35 | 36 | Consul service registrar is configured with the following parameters: 37 | 38 | --8<-- "target/attributes/META-INF/stork-docs/consul-sr-attributes.txt" -------------------------------------------------------------------------------- /docs/docs/service-registration/eureka.md: -------------------------------------------------------------------------------- 1 | # Eureka Service Discovery 2 | 3 | [Eureka](https://github.com/Netflix/eureka) is a RESTful service that is primarily used in the AWS cloud for the purpose of discovery, load balancing, and failover of middle-tier servers. 4 | 5 | This page explains how Stork can use Eureka to handle the service registration. 6 | 7 | ## Dependency 8 | 9 | First, you need to add the Stork Eureka Service Registration provider: 10 | 11 | ```xml 12 | 13 | io.smallrye.stork 14 | stork-service-registration-eureka 15 | {{version.current}} 16 | 17 | ``` 18 | 19 | ## Service registration configuration 20 | 21 | For each service that should register the service instances in Eureka, configure the service registrar `type`: 22 | 23 | === "stork standalone" 24 | ```properties 25 | stork.my-service.service-registrar.type=eureka 26 | stork.my-service.service-registrar.eureka-host=localhost 27 | stork.my-service.service-registrar.eureka-port=8761 28 | ``` 29 | 30 | === "stork in quarkus" 31 | ```properties 32 | quarkus.stork.my-service.service-registrar.type=eureka 33 | quarkus.stork.my-service.service-registrar.eureka-host=localhost 34 | quarkus.stork.my-service.service-registrar.eureka-port=8761 35 | ``` 36 | 37 | Eureka service registrar is configured with the following parameters: 38 | 39 | --8<-- "target/attributes/META-INF/stork-docs/eureka-sr-attributes.txt" 40 | -------------------------------------------------------------------------------- /docs/docs/service-registration/overview.md: -------------------------------------------------------------------------------- 1 | ### Service Registration in SmallRye Stork 2 | 3 | Service registration is the process by which services announce their availability to a central registry, allowing other services to discover and communicate with them. 4 | In SmallRye Stork, service registration is automated and integrated with supported registries like **Consul**. 5 | This ensures that services can dynamically join and leave the network. 6 | 7 | #### Key Features: 8 | - **Automatic Registration**: For Quarkus applications, SmallRye Stork automatically registers it with the configured service registry (e.g., Consul). 9 | 10 | **IMPORTANT** Public IP address needs to be provided. Smallrye Stork will fail if the service IP address is not provided during registration. 11 | 12 | #### Supported Registries: 13 | Currently, Smallrye Stork provides seamless integration with **Consul**, Eureka and a Static registry. 14 | This integration simplifies the management of dynamic environments where services are frequently added or removed. 15 | 16 | #### Custom Registration: 17 | In addition to the default mechanisms, SmallRye Stork allows you to implement custom service registration strategies, providing flexibility for different infrastructures or custom service discovery needs. 18 | 19 | In the following sections you can have more details about each specific implementation. -------------------------------------------------------------------------------- /docs/docs/service-registration/static-list.md: -------------------------------------------------------------------------------- 1 | # Static List Service Registration 2 | 3 | Stork provides the ability to register services using Static list as backend. 4 | 5 | 6 | ## Dependency 7 | 8 | To use the `static` service registrar, first add the appropriate Service Registration provider dependency to your project: 9 | 10 | ```xml 11 | 12 | io.smallrye.stork 13 | stork-service-registration-static-list 14 | {{version.current}} 15 | 16 | ``` 17 | 18 | ## Service registration configuration 19 | 20 | For each service that should register the service instances in a static list, configure the service registrar `type`: 21 | 22 | === "stork standalone" 23 | ```properties 24 | stork.my-service.service-registrar.type=static 25 | ``` 26 | 27 | === "stork in quarkus" 28 | ```properties 29 | quarkus.stork.my-service.service-registrar.type=static 30 | ``` 31 | 32 | Static service registrar is configured with the following parameters: 33 | 34 | --8<-- "target/attributes/META-INF/stork-docs/static-sr-attributes.txt" -------------------------------------------------------------------------------- /docs/docs/springboot-config.md: -------------------------------------------------------------------------------- 1 | # Stork Spring Boot Config 2 | 3 | Stork seamlessly supports Spring Boot configuration, facilitating access to configuration properties. 4 | This documentation elaborates on how Spring Boot developers can use Stork in their Spring Boot applications and configure it using the application.properties file. 5 | 6 | 7 | ## Dependency setup 8 | 9 | To enable Spring Boot configuration integration in Stork, you need to include the following dependency: 10 | 11 | ```xml 12 | 13 | io.smallrye.stork 14 | stork-spring-boot-config 15 | {{version.current}} 16 | 17 | ``` 18 | 19 | ## Initializing Stork 20 | 21 | Since Spring Boot lacks a pre-configured Stork instance, you'll need create one. It can be done by providing a Spring bean performing Stork initialization: 22 | 23 | ```java linenums="1" 24 | {{ insert('examples/SpringBootInitializationExample.java') }} 25 | ``` 26 | Upon initialization, Stork scans for the `io.smallrye.stork.springboot.SpringBootConfigProvider` SPI provider and CDI beans (from version 2.x onwards). 27 | It then builds a comprehensive list of managed services by parsing the properties configuration files. 28 | 29 | 30 | Please note the importance of the `io.smallrye.stork.springboot.SpringBootApplicationContextProvider` bean in our setup. 31 | This bean has a critical role by granting Stork access to the current `org.springframework.context.ApplicationContext`. 32 | It enables it to retrieve configuration details effectively. Consequently, it's imperative that this bean is instantiated prior to initiating the Stork initialization process. 33 | In this case, we utilize the `@DependsOn` annotation for that. 34 | It allows us controlling the bean creation order. 35 | 36 | ## Comprehensive Example 37 | 38 | You can check our [Guitar Hero Application](https://github.com/aureamunoz/spring-stork-guitar-hero/) showcasing the seamless integration of Stork with Spring Boot Configuration. 39 | -------------------------------------------------------------------------------- /docs/mkdocs-customizations/macros/__pycache__/docissimo.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallrye/smallrye-stork/47751840baa0ae2fadc5e010e186526b86cb2bc6/docs/mkdocs-customizations/macros/__pycache__/docissimo.cpython-310.pyc -------------------------------------------------------------------------------- /docs/mkdocs-customizations/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block outdated %} 4 | You are not viewing the latest version. 5 | 6 | Click here to go to latest. 7 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /docs/mkdocs-customizations/overrides/partials/copyright.html: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 |

-------------------------------------------------------------------------------- /docs/snippets/examples/AcmeDiscoveryApiUsage.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | import io.smallrye.stork.api.ServiceDefinition; 5 | import io.smallrye.stork.api.ServiceInstance; 6 | import io.smallrye.stork.api.StorkServiceRegistry; 7 | 8 | public class AcmeDiscoveryApiUsage { 9 | 10 | public void example(StorkServiceRegistry stork) { 11 | stork.defineIfAbsent("my-service", ServiceDefinition.of( 12 | new AcmeConfiguration().withHost("my-host")) 13 | ); 14 | 15 | Uni uni = stork.getService("my-service").selectInstance(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /docs/snippets/examples/AcmeLoadBalancer.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Random; 6 | 7 | import io.smallrye.stork.api.LoadBalancer; 8 | import io.smallrye.stork.api.NoServiceInstanceFoundException; 9 | import io.smallrye.stork.api.ServiceInstance; 10 | 11 | public class AcmeLoadBalancer implements LoadBalancer { 12 | 13 | private final Random random; 14 | 15 | public AcmeLoadBalancer(AcmeLoadBalancerConfiguration config) { 16 | random = new Random(); 17 | } 18 | 19 | @Override 20 | public ServiceInstance selectServiceInstance(Collection serviceInstances) { 21 | if (serviceInstances.isEmpty()) { 22 | throw new NoServiceInstanceFoundException("No services found."); 23 | } 24 | int index = random.nextInt(serviceInstances.size()); 25 | return new ArrayList<>(serviceInstances).get(index); 26 | } 27 | 28 | @Override 29 | public boolean requiresStrictRecording() { 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/snippets/examples/AcmeLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.api.LoadBalancer; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | import io.smallrye.stork.api.config.LoadBalancerAttribute; 6 | import io.smallrye.stork.api.config.LoadBalancerType; 7 | import io.smallrye.stork.spi.LoadBalancerProvider; 8 | import jakarta.enterprise.context.ApplicationScoped; 9 | 10 | @LoadBalancerType("acme-load-balancer") 11 | @LoadBalancerAttribute(name = "my-attribute", 12 | description = "Attribute that alters the behavior of the LoadBalancer") 13 | @ApplicationScoped 14 | public class AcmeLoadBalancerProvider implements 15 | LoadBalancerProvider { 16 | 17 | @Override 18 | public LoadBalancer createLoadBalancer(AcmeLoadBalancerConfiguration config, 19 | ServiceDiscovery serviceDiscovery) { 20 | return new AcmeLoadBalancer(config); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/snippets/examples/AcmeObservationCollector.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.Stork; 4 | import io.smallrye.stork.api.observability.ObservationCollector; 5 | import io.smallrye.stork.api.observability.StorkEventHandler; 6 | import io.smallrye.stork.api.observability.StorkObservation; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | public class AcmeObservationCollector implements ObservationCollector { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(AcmeObservationCollector.class); 13 | 14 | private static final StorkEventHandler ACME_HANDLER = event -> { 15 | //This is the terminal event. Put here your custom logic to extend the metrics collection. 16 | 17 | //E.g. Expose metrics to Micrometer, additional logs.... 18 | LOGGER.info( "Service discovery took " + event.getServiceDiscoveryDuration() + "."); 19 | LOGGER.info( event.getDiscoveredInstancesCount() + " have been discovered for " + event.getServiceName() + "."); 20 | LOGGER.info( "Service selection took " + event.getServiceSelectionDuration() + "."); 21 | 22 | // ... 23 | 24 | }; 25 | 26 | public static StorkObservation ACME_STORK_EVENT; 27 | 28 | @Override 29 | public StorkObservation create(String serviceName, String serviceDiscoveryType, 30 | String serviceSelectionType) { 31 | ACME_STORK_EVENT = new StorkObservation( 32 | serviceName, serviceDiscoveryType, serviceSelectionType, 33 | ACME_HANDLER); 34 | return ACME_STORK_EVENT; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/snippets/examples/AcmeSelectorApiUsage.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | import io.smallrye.stork.api.ServiceDefinition; 5 | import io.smallrye.stork.api.ServiceInstance; 6 | import io.smallrye.stork.api.StorkServiceRegistry; 7 | import io.smallrye.stork.servicediscovery.staticlist.StaticConfiguration; 8 | import io.smallrye.stork.serviceregistration.staticlist.StaticRegistrarConfiguration; 9 | 10 | public class AcmeSelectorApiUsage { 11 | 12 | public void example(StorkServiceRegistry stork) { 13 | String list = "localhost:8080, localhost:8081"; 14 | stork.defineIfAbsent("my-service", ServiceDefinition.of( 15 | new StaticConfiguration().withAddressList(list), 16 | new AcmeLoadBalancerConfiguration().withMyAttribute("my-value"),new StaticRegistrarConfiguration()) 17 | ); 18 | 19 | Uni uni = stork.getService("my-service").selectInstance(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /docs/snippets/examples/AcmeServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import io.smallrye.mutiny.Uni; 7 | import io.smallrye.stork.api.ServiceDiscovery; 8 | import io.smallrye.stork.api.ServiceInstance; 9 | import io.smallrye.stork.impl.DefaultServiceInstance; 10 | import io.smallrye.stork.utils.ServiceInstanceIds; 11 | 12 | public class AcmeServiceDiscovery implements ServiceDiscovery { 13 | 14 | private final String host; 15 | private final int port; 16 | 17 | public AcmeServiceDiscovery(AcmeConfiguration configuration) { 18 | this.host = configuration.getHost(); 19 | this.port = Integer.parseInt(configuration.getPort()); 20 | } 21 | 22 | @Override 23 | public Uni> getServiceInstances() { 24 | // Proceed to the lookup... 25 | // Here, we just return a DefaultServiceInstance with the configured host and port 26 | // The last parameter specifies whether the communication with the instance should 27 | // happen over a secure connection 28 | DefaultServiceInstance instance = 29 | new DefaultServiceInstance(ServiceInstanceIds.next(), host, port, false); 30 | return Uni.createFrom().item(() -> Collections.singletonList(instance)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/snippets/examples/AcmeServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.api.ServiceDiscovery; 4 | import io.smallrye.stork.api.config.ServiceConfig; 5 | import io.smallrye.stork.api.config.ServiceDiscoveryAttribute; 6 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 7 | import io.smallrye.stork.spi.StorkInfrastructure; 8 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 9 | import jakarta.enterprise.context.ApplicationScoped; 10 | 11 | @ServiceDiscoveryType("acme") 12 | @ServiceDiscoveryAttribute(name = "host", 13 | description = "Host name of the service discovery server.", required = true) 14 | @ServiceDiscoveryAttribute(name = "port", 15 | description = "Hort of the service discovery server.", required = false) 16 | @ApplicationScoped 17 | public class AcmeServiceDiscoveryProvider 18 | implements ServiceDiscoveryProvider { 19 | 20 | @Override 21 | public ServiceDiscovery createServiceDiscovery( 22 | AcmeConfiguration config, 23 | String serviceName, 24 | ServiceConfig serviceConfig, 25 | StorkInfrastructure storkInfrastructure) { 26 | return new AcmeServiceDiscovery(config); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/snippets/examples/AcmeServiceRegistrar.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | import io.smallrye.stork.api.Metadata; 5 | import io.smallrye.stork.api.ServiceRegistrar; 6 | 7 | public class AcmeServiceRegistrar implements ServiceRegistrar { 8 | 9 | private final String backendHost; 10 | private final int backendPort; 11 | 12 | public AcmeServiceRegistrar(AcmeRegistrarConfiguration configuration) { 13 | this.backendHost = configuration.getHost(); 14 | this.backendPort = Integer.parseInt(configuration.getPort()); 15 | } 16 | 17 | 18 | @Override 19 | public Uni registerServiceInstance(String serviceName, Metadata metadata, String ipAddress, int defaultPort) { 20 | //do whatever is needed for registering service instance 21 | return Uni.createFrom().voidItem(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/snippets/examples/AcmeServiceRegistrarProvider.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.api.Metadata; 4 | import io.smallrye.stork.api.ServiceRegistrar; 5 | import io.smallrye.stork.api.config.ServiceRegistrarAttribute; 6 | import io.smallrye.stork.api.config.ServiceRegistrarType; 7 | import io.smallrye.stork.spi.ServiceRegistrarProvider; 8 | import io.smallrye.stork.spi.StorkInfrastructure; 9 | import jakarta.enterprise.context.ApplicationScoped; 10 | 11 | @ServiceRegistrarType(value = "acme", metadataKey = Metadata.DefaultMetadataKey.class) 12 | @ServiceRegistrarAttribute(name = "host", 13 | description = "Host name of the service discovery server.", required = true) 14 | @ServiceRegistrarAttribute(name = "port", 15 | description = "Hort of the service discovery server.", required = false) 16 | @ApplicationScoped 17 | public class AcmeServiceRegistrarProvider 18 | implements ServiceRegistrarProvider { 19 | 20 | @Override 21 | public ServiceRegistrar createServiceRegistrar( 22 | AcmeRegistrarConfiguration config, 23 | String serviceName, 24 | StorkInfrastructure storkInfrastructure) { 25 | return new AcmeServiceRegistrar(config); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /docs/snippets/examples/CachedAcmeServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | import io.smallrye.stork.api.ServiceInstance; 6 | import io.smallrye.stork.impl.CachingServiceDiscovery; 7 | import io.smallrye.stork.impl.DefaultServiceInstance; 8 | import io.smallrye.stork.utils.ServiceInstanceIds; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class CachedAcmeServiceDiscovery extends CachingServiceDiscovery { 14 | 15 | private final String host; 16 | private final int port; 17 | 18 | public CachedAcmeServiceDiscovery(CachedAcmeConfiguration configuration) { 19 | super(configuration.getRefreshPeriod()); // (1) 20 | this.host = configuration.getHost(); 21 | this.port = Integer.parseInt(configuration.getPort()); 22 | } 23 | 24 | @Override // (2) 25 | public Uni> fetchNewServiceInstances(List previousInstances) { 26 | // Retrieve services... 27 | DefaultServiceInstance instance = 28 | new DefaultServiceInstance(ServiceInstanceIds.next(), host, port, false); 29 | return Uni.createFrom().item(() -> Collections.singletonList(instance)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/snippets/examples/CachedAcmeServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.api.ServiceDiscovery; 4 | import io.smallrye.stork.api.config.ServiceConfig; 5 | import io.smallrye.stork.api.config.ServiceDiscoveryAttribute; 6 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 7 | import io.smallrye.stork.impl.CachingServiceDiscovery; 8 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 9 | import io.smallrye.stork.spi.StorkInfrastructure; 10 | import jakarta.enterprise.context.ApplicationScoped; 11 | 12 | @ServiceDiscoveryType("cached-acme") 13 | @ServiceDiscoveryAttribute(name = "host", 14 | description = "Host name of the service discovery server.", required = true) 15 | @ServiceDiscoveryAttribute(name = "port", 16 | description = "Hort of the service discovery server.", required = false) 17 | @ServiceDiscoveryAttribute(name = "refresh-period", 18 | description = "Service discovery cache refresh period.", 19 | defaultValue = CachingServiceDiscovery.DEFAULT_REFRESH_INTERVAL) 20 | @ApplicationScoped 21 | public class CachedAcmeServiceDiscoveryProvider 22 | implements ServiceDiscoveryProvider { 23 | 24 | @Override 25 | public ServiceDiscovery createServiceDiscovery( 26 | CachedAcmeConfiguration config, 27 | String serviceName, 28 | ServiceConfig serviceConfig, 29 | StorkInfrastructure storkInfrastructure) { 30 | return new CachedAcmeServiceDiscovery(config); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/snippets/examples/CustomExpirationCachedAcmeServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | import io.smallrye.stork.api.ServiceInstance; 5 | import io.smallrye.stork.impl.CachingServiceDiscovery; 6 | import io.smallrye.stork.impl.DefaultServiceInstance; 7 | import io.smallrye.stork.utils.ServiceInstanceIds; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | 13 | public class CustomExpirationCachedAcmeServiceDiscovery extends CachingServiceDiscovery { 14 | 15 | private final String host; 16 | private final int port; 17 | 18 | private AtomicBoolean invalidated = new AtomicBoolean(); 19 | 20 | public CustomExpirationCachedAcmeServiceDiscovery(CachedAcmeConfiguration configuration) { 21 | super(configuration.getRefreshPeriod()); 22 | this.host = configuration.getHost(); 23 | this.port = Integer.parseInt(configuration.getPort()); 24 | } 25 | 26 | @Override 27 | public Uni> fetchNewServiceInstances(List previousInstances) { 28 | // Retrieve services... 29 | DefaultServiceInstance instance = 30 | new DefaultServiceInstance(ServiceInstanceIds.next(), host, port, false); 31 | return Uni.createFrom().item(() -> Collections.singletonList(instance)); 32 | } 33 | 34 | @Override 35 | public Uni> cache(Uni> uni) { 36 | return uni.memoize().until(() -> invalidated.get()); 37 | } 38 | 39 | //command-based cache invalidation: user triggers the action to invalidate the cache. 40 | public void invalidate() { 41 | invalidated.set(true); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /docs/snippets/examples/DefinitionExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.Stork; 4 | import io.smallrye.stork.api.ServiceDefinition; 5 | import io.smallrye.stork.loadbalancer.random.RandomConfiguration; 6 | import io.smallrye.stork.servicediscovery.staticlist.StaticConfiguration; 7 | import io.smallrye.stork.serviceregistration.staticlist.StaticRegistrarConfiguration; 8 | 9 | public class DefinitionExample { 10 | 11 | public static void example(Stork stork) { 12 | String example = "localhost:8080, localhost:8081"; 13 | 14 | // A service using a static list of locations as discovery 15 | // As not set, it defaults to round-robin to select the instance. 16 | stork.defineIfAbsent("my-service", 17 | ServiceDefinition.of(new StaticConfiguration().withAddressList(example))); 18 | 19 | // Another service using the random selection strategy, instead of round-robin 20 | stork.defineIfAbsent("my-second-service", 21 | ServiceDefinition.of(new StaticConfiguration().withAddressList(example), 22 | new RandomConfiguration())); 23 | 24 | // Another service using the random selection strategy, instead of round-robin 25 | // and a static service registrar 26 | stork.defineIfAbsent("my-second-service", 27 | ServiceDefinition.of(new StaticConfiguration().withAddressList(example), 28 | new RandomConfiguration(), new StaticRegistrarConfiguration())); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/snippets/examples/HelloClient.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import jakarta.ws.rs.Consumes; 4 | import jakarta.ws.rs.POST; 5 | import jakarta.ws.rs.Path; 6 | import jakarta.ws.rs.Produces; 7 | import jakarta.ws.rs.core.MediaType; 8 | import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; 9 | 10 | 11 | 12 | @Path("/") 13 | @RegisterRestClient(baseUri = "stork://hello-service/hello") 14 | public interface HelloClient { 15 | @POST 16 | @Produces(MediaType.TEXT_PLAIN) 17 | @Consumes(MediaType.TEXT_PLAIN) 18 | String echo(String name); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /docs/snippets/examples/InitializationExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.Stork; 4 | import io.smallrye.stork.api.ServiceDefinition; 5 | import io.smallrye.stork.api.ServiceInstance; 6 | import io.smallrye.stork.loadbalancer.random.RandomConfiguration; 7 | import io.smallrye.stork.servicediscovery.staticlist.StaticConfiguration; 8 | 9 | import java.time.Duration; 10 | 11 | public class InitializationExample { 12 | 13 | public static void main(String[] args) { 14 | Stork.initialize(); 15 | Stork stork = Stork.getInstance(); 16 | // ... 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/snippets/examples/LookupExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | import io.smallrye.stork.Stork; 5 | import io.smallrye.stork.api.Service; 6 | import io.smallrye.stork.api.ServiceDefinition; 7 | import io.smallrye.stork.api.ServiceInstance; 8 | import io.smallrye.stork.loadbalancer.random.RandomConfiguration; 9 | import io.smallrye.stork.servicediscovery.staticlist.StaticConfiguration; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class LookupExample { 15 | 16 | public static void example(Stork stork) { 17 | Service service = stork.getService("my-service"); 18 | 19 | // Gets all the available instances: 20 | Uni> instances = service.getInstances(); 21 | // Select one instance using the load balancing strategy 22 | Uni instance = service.selectInstance(); 23 | 24 | // Gets all the managed services: 25 | Map services = stork.getServices(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/snippets/examples/ObservableInitializationExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.Stork; 4 | import io.smallrye.stork.integration.ObservableStorkInfrastructure; 5 | 6 | public class ObservableInitializationExample { 7 | 8 | public static void main(String[] args) { 9 | Stork.initialize(new ObservableStorkInfrastructure(new AcmeObservationCollector())); 10 | Stork stork = Stork.getInstance(); 11 | // ... 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/snippets/examples/ObservationExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | import io.smallrye.stork.Stork; 5 | import io.smallrye.stork.api.Service; 6 | import io.smallrye.stork.api.ServiceInstance; 7 | import io.smallrye.stork.api.observability.ObservationCollector; 8 | import io.smallrye.stork.api.observability.StorkObservation; 9 | 10 | import java.time.Duration; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | import static examples.AcmeObservationCollector.*; 15 | 16 | public class ObservationExample { 17 | 18 | public static void example(Stork stork) { 19 | Service service = stork.getService("my-service"); 20 | 21 | ObservationCollector observations = service.getObservations(); 22 | 23 | // Gets the time spent in service discovery and service selection even if any error happens 24 | Duration overallDuration = ACME_STORK_EVENT.getOverallDuration(); 25 | 26 | // Gets the total number of instances discovered 27 | int discoveredInstancesCount = ACME_STORK_EVENT.getDiscoveredInstancesCount(); 28 | 29 | // Gets the error raised during the process 30 | Throwable failure = ACME_STORK_EVENT.failure(); 31 | 32 | // ... 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/snippets/examples/SpringBootInitializationExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.Stork; 4 | import io.smallrye.stork.springboot.SpringBootApplicationContextProvider; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.DependsOn; 9 | 10 | @SpringBootApplication 11 | public class SpringBootInitializationExample { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SpringBootInitializationExample.class, args); 15 | } 16 | 17 | @Bean 18 | @DependsOn("springBootApplicationContextProvider") 19 | public Stork stork() { 20 | Stork.initialize(); 21 | Stork stork = Stork.getInstance(); 22 | return stork; 23 | } 24 | 25 | @Bean 26 | public SpringBootApplicationContextProvider springBootApplicationContextProvider() { 27 | return new SpringBootApplicationContextProvider(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/snippets/examples/StorkApiExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.Stork; 4 | import io.smallrye.stork.api.ServiceDefinition; 5 | import io.smallrye.stork.api.ServiceInstance; 6 | import io.smallrye.stork.loadbalancer.random.RandomConfiguration; 7 | import io.smallrye.stork.servicediscovery.staticlist.StaticConfiguration; 8 | import io.smallrye.stork.serviceregistration.staticlist.StaticRegistrarConfiguration; 9 | 10 | import java.time.Duration; 11 | 12 | public class StorkApiExample { 13 | 14 | public static void main(String[] args) { 15 | Stork.initialize(); 16 | Stork stork = Stork.getInstance(); 17 | 18 | String example = "localhost:8080, localhost:8082"; 19 | 20 | // A service using a static list of locations as discovery 21 | // As not set, it defaults to round-robin to select the instance. 22 | stork.defineIfAbsent("my-service", 23 | ServiceDefinition.of(new StaticConfiguration().withAddressList(example))); 24 | 25 | // Another service using the random selection strategy, instead of round-robin 26 | stork.defineIfAbsent("my-second-service", 27 | ServiceDefinition.of(new StaticConfiguration().withAddressList(example), 28 | new RandomConfiguration())); 29 | 30 | ServiceInstance instance = stork.getService("my-second-service").selectInstance() 31 | .await().atMost(Duration.ofSeconds(1)); 32 | System.out.println(instance.getHost() + ":" + instance.getPort()); 33 | 34 | // Another service using the random selection strategy, instead of round-robin 35 | // and a static service registrar 36 | stork.defineIfAbsent("my-third-service", 37 | ServiceDefinition.of(new StaticConfiguration().withAddressList(example), 38 | new RandomConfiguration(), new StaticRegistrarConfiguration())); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/snippets/examples/StorkEntryPointExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.Stork; 4 | 5 | public class StorkEntryPointExample { 6 | 7 | public static void main(String[] args) { 8 | Stork.initialize(); 9 | Stork stork = Stork.getInstance(); 10 | // ... 11 | Stork.shutdown(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /docs/snippets/examples/StorkServiceExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.smallrye.stork.api.Service; 4 | import io.smallrye.stork.Stork; 5 | 6 | public class StorkServiceExample { 7 | 8 | public static void main(String[] args) { 9 | Stork.initialize(); 10 | Stork stork = Stork.getInstance(); 11 | 12 | Service service = stork.getService("my-service"); 13 | 14 | // ... 15 | Stork.shutdown(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /docs/snippets/examples/StorkServiceLookupExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.time.Duration; 4 | import java.util.List; 5 | 6 | import io.smallrye.stork.Stork; 7 | import io.smallrye.stork.api.Service; 8 | import io.smallrye.stork.api.ServiceInstance; 9 | 10 | public class StorkServiceLookupExample { 11 | 12 | public static void main(String[] args) { 13 | Stork.initialize(); 14 | Stork stork = Stork.getInstance(); 15 | 16 | Service service = stork.getService("my-service"); 17 | List instances = service.getInstances() 18 | .await().atMost(Duration.ofSeconds(5)); 19 | 20 | // ... 21 | Stork.shutdown(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /docs/snippets/examples/StorkServiceSelectionExample.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.time.Duration; 4 | 5 | import io.smallrye.stork.Stork; 6 | import io.smallrye.stork.api.Service; 7 | import io.smallrye.stork.api.ServiceInstance; 8 | 9 | public class StorkServiceSelectionExample { 10 | 11 | public static void main(String[] args) { 12 | Stork.initialize(); 13 | Stork stork = Stork.getInstance(); 14 | 15 | Service service = stork.getService("my-service"); 16 | ServiceInstance instance = service.selectInstance() 17 | .await().atMost(Duration.ofSeconds(5)); 18 | 19 | System.out.println(instance.getHost() + ":" + instance.getPort()); 20 | 21 | // ... 22 | Stork.shutdown(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /load-balancer/least-requests/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.loadbalancer\\.requests(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [ 28 | 29 | ] 30 | } 31 | }, { 32 | "extension" : "revapi.reporter.json", 33 | "configuration" : { 34 | "minSeverity" : "POTENTIALLY_BREAKING", 35 | "minCriticality" : "documented", 36 | "output" : "target/compatibility.json", 37 | "indent" : true, 38 | "append" : false, 39 | "keepEmptyFile" : true 40 | } 41 | }, { 42 | "extension" : "revapi.reporter.text", 43 | "configuration" : { 44 | "minSeverity" : "POTENTIALLY_BREAKING", 45 | "minCriticality" : "documented", 46 | "output" : "out" 47 | } 48 | } ] -------------------------------------------------------------------------------- /load-balancer/least-requests/src/main/java/io/smallrye/stork/loadbalancer/requests/InflightRequestCollector.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.requests; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | import io.smallrye.stork.spi.CallStatisticsCollector; 7 | 8 | /** 9 | * {@link CallStatisticsCollector} that keep tracks of the number of inflight requests. 10 | */ 11 | public class InflightRequestCollector implements CallStatisticsCollector { 12 | 13 | private final ConcurrentHashMap storage = new ConcurrentHashMap<>(); 14 | 15 | /** 16 | * Gets the number of inflight requests for the service instance with the given {@code id}. 17 | * 18 | * @param id the service instance id 19 | * @return the number of inflight request, {@code 0} if none. 20 | */ 21 | public int get(long id) { 22 | return storage.computeIfAbsent(id, x -> new AtomicInteger(0)).get(); 23 | } 24 | 25 | @Override 26 | public void recordStart(long serviceInstanceId, boolean measureTime) { 27 | storage.get(serviceInstanceId).incrementAndGet(); 28 | } 29 | 30 | @Override 31 | public void recordEnd(long serviceInstanceId, Throwable throwable) { 32 | storage.get(serviceInstanceId).decrementAndGet(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /load-balancer/least-requests/src/main/java/io/smallrye/stork/loadbalancer/requests/LeastRequestsLoadBalancer.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.requests; 2 | 3 | import java.util.Collection; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.NoAcceptableServiceInstanceFoundException; 7 | import io.smallrye.stork.api.NoServiceInstanceFoundException; 8 | import io.smallrye.stork.api.ServiceInstance; 9 | import io.smallrye.stork.impl.ServiceInstanceWithStatGathering; 10 | 11 | /** 12 | * An implementation of load-balancer that keep tracks of inflight request, and picks the less "used" instance. 13 | */ 14 | public class LeastRequestsLoadBalancer implements LoadBalancer { 15 | 16 | private final InflightRequestCollector collector = new InflightRequestCollector(); 17 | 18 | @Override 19 | public ServiceInstance selectServiceInstance(Collection serviceInstances) { 20 | if (serviceInstances.isEmpty()) { 21 | throw new NoServiceInstanceFoundException("No service instance found"); 22 | } 23 | 24 | ServiceInstance selected = null; 25 | int min = Integer.MAX_VALUE; 26 | for (ServiceInstance instance : serviceInstances) { 27 | int concurrency = collector.get(instance.getId()); 28 | if (concurrency < min) { 29 | selected = instance; 30 | min = concurrency; 31 | } 32 | } 33 | 34 | if (selected == null) { 35 | throw new NoAcceptableServiceInstanceFoundException("No service instance found"); 36 | } 37 | 38 | return new ServiceInstanceWithStatGathering(selected, collector); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /load-balancer/least-requests/src/main/java/io/smallrye/stork/loadbalancer/requests/LeastRequestsLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.requests; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.config.LoadBalancerType; 8 | import io.smallrye.stork.spi.LoadBalancerProvider; 9 | 10 | /** 11 | * A load balancer provider that picks the instance with the less inflight requests. 12 | */ 13 | @LoadBalancerType("least-requests") 14 | @ApplicationScoped 15 | public class LeastRequestsLoadBalancerProvider 16 | implements LoadBalancerProvider { 17 | 18 | @Override 19 | public LoadBalancer createLoadBalancer(LeastRequestsConfiguration config, 20 | ServiceDiscovery serviceDiscovery) { 21 | return new LeastRequestsLoadBalancer(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /load-balancer/least-response-time/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.loadbalancer\\.leastresponsetime(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [ 28 | ] 29 | } 30 | }, { 31 | "extension" : "revapi.reporter.json", 32 | "configuration" : { 33 | "minSeverity" : "POTENTIALLY_BREAKING", 34 | "minCriticality" : "documented", 35 | "output" : "target/compatibility.json", 36 | "indent" : true, 37 | "append" : false, 38 | "keepEmptyFile" : true 39 | } 40 | }, { 41 | "extension" : "revapi.reporter.text", 42 | "configuration" : { 43 | "minSeverity" : "POTENTIALLY_BREAKING", 44 | "minCriticality" : "documented", 45 | "output" : "out" 46 | } 47 | } ] -------------------------------------------------------------------------------- /load-balancer/least-response-time/src/main/java/io/smallrye/stork/loadbalancer/leastresponsetime/LeastResponseTimeLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.leastresponsetime; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.config.LoadBalancerAttribute; 8 | import io.smallrye.stork.api.config.LoadBalancerType; 9 | import io.smallrye.stork.spi.LoadBalancerProvider; 10 | 11 | /** 12 | * A load balancer provider following response times and failures. 13 | */ 14 | @LoadBalancerType("least-response-time") 15 | @LoadBalancerAttribute(name = "declining-factor", defaultValue = "0.9", description = "How much *score* should decline in time, see Score calculation in the docs for details.") 16 | @LoadBalancerAttribute(name = "error-penalty", defaultValue = "60s", description = "This load balancer treats an erroneous response as a response after this time.") 17 | @LoadBalancerAttribute(name = "use-secure-random", defaultValue = "false", description = "Whether the load balancer should use a SecureRandom instead of a Random (default). Check [this page](https://stackoverflow.com/questions/11051205/difference-between-java-util-random-and-java-security-securerandom) to understand the difference") 18 | @ApplicationScoped 19 | public class LeastResponseTimeLoadBalancerProvider 20 | implements LoadBalancerProvider { 21 | 22 | @Override 23 | public LoadBalancer createLoadBalancer(LeastResponseTimeConfiguration config, 24 | ServiceDiscovery serviceDiscovery) { 25 | return new LeastResponseTimeLoadBalancer(config); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /load-balancer/least-response-time/src/main/java/io/smallrye/stork/loadbalancer/leastresponsetime/impl/util/FastPower.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.leastresponsetime.impl.util; 2 | 3 | public class FastPower { 4 | final double base; 5 | final double[] baseToPowersOfTwo; 6 | 7 | public FastPower(double base) { 8 | this.base = base; 9 | 10 | baseToPowersOfTwo = new double[17]; 11 | // e^2^0, e^2^1, e^2^2, e^2^3, e^2^4, 12 | baseToPowersOfTwo[0] = base; 13 | // this gives us the ability to quickly calculate powers up to 2^17 - 1 14 | for (int i = 1; i <= 16; i++) { 15 | baseToPowersOfTwo[i] = baseToPowersOfTwo[i - 1] * baseToPowersOfTwo[i - 1]; 16 | } 17 | } 18 | 19 | public double toPower(long exponent) { 20 | double result = 1; 21 | for (int i = 0; i <= 16; i++) { 22 | int powerOf2 = 1 << i; 23 | if ((exponent & powerOf2) != 0) { 24 | result *= baseToPowersOfTwo[i]; 25 | } 26 | } 27 | return result; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /load-balancer/least-response-time/src/test/java/io/smallrye/stork/loadbalancer/leastresponsetime/impl/TestUtils.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.leastresponsetime.impl; 2 | 3 | public class TestUtils { 4 | @SuppressWarnings("deprecation") 5 | public static void clear(CallStatistics statistics) { 6 | statistics.clear(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /load-balancer/least-response-time/src/test/java/io/smallrye/stork/loadbalancer/leastresponsetime/util/FastPowerTest.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.leastresponsetime.util; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.assertj.core.data.Percentage; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import io.smallrye.stork.loadbalancer.leastresponsetime.impl.util.FastPower; 9 | 10 | public class FastPowerTest { 11 | 12 | @Test 13 | void shouldWorkOnPowersOfTwo() { 14 | FastPower power = new FastPower(2); 15 | assertThat(power.toPower(0L)).isCloseTo(1., Percentage.withPercentage(0.1)); 16 | assertThat(power.toPower(1L)).isCloseTo(2., Percentage.withPercentage(0.1)); 17 | assertThat(power.toPower(10L)).isCloseTo(1024., Percentage.withPercentage(0.1)); 18 | assertThat(power.toPower(30L)).isCloseTo(1073741824, Percentage.withPercentage(0.1)); 19 | } 20 | 21 | @Test 22 | void shouldWorkOnPowersOfOneTenth() { 23 | FastPower power = new FastPower(0.1); 24 | assertThat(power.toPower(0L)).isCloseTo(1., Percentage.withPercentage(0.1)); 25 | assertThat(power.toPower(1L)).isCloseTo(0.1, Percentage.withPercentage(0.1)); 26 | assertThat(power.toPower(2L)).isCloseTo(0.01, Percentage.withPercentage(0.1)); 27 | assertThat(power.toPower(10L)).isCloseTo(0.0000000001, Percentage.withPercentage(0.1)); 28 | assertThat(power.toPower(30L)).isCloseTo(0.000000000000000000000000000001, Percentage.withPercentage(0.1)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /load-balancer/power-of-two-choices/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.loadbalancer\\.poweroftwochoices(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [ 28 | 29 | ] 30 | } 31 | }, { 32 | "extension" : "revapi.reporter.json", 33 | "configuration" : { 34 | "minSeverity" : "POTENTIALLY_BREAKING", 35 | "minCriticality" : "documented", 36 | "output" : "target/compatibility.json", 37 | "indent" : true, 38 | "append" : false, 39 | "keepEmptyFile" : true 40 | } 41 | }, { 42 | "extension" : "revapi.reporter.text", 43 | "configuration" : { 44 | "minSeverity" : "POTENTIALLY_BREAKING", 45 | "minCriticality" : "documented", 46 | "output" : "out" 47 | } 48 | } ] -------------------------------------------------------------------------------- /load-balancer/power-of-two-choices/src/main/java/io/smallrye/stork/loadbalancer/poweroftwochoices/PowerOfTwoChoicesLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.poweroftwochoices; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.config.LoadBalancerAttribute; 8 | import io.smallrye.stork.api.config.LoadBalancerType; 9 | import io.smallrye.stork.spi.LoadBalancerProvider; 10 | 11 | /** 12 | * A load balancer provider following the Power of two random choices strategy. 13 | */ 14 | @LoadBalancerType("power-of-two-choices") 15 | @LoadBalancerAttribute(name = "use-secure-random", defaultValue = "false", description = "Whether the load balancer should use a SecureRandom instead of a Random (default). Check [this page](https://stackoverflow.com/questions/11051205/difference-between-java-util-random-and-java-security-securerandom) to understand the difference") 16 | @ApplicationScoped 17 | public class PowerOfTwoChoicesLoadBalancerProvider 18 | implements LoadBalancerProvider { 19 | 20 | public LoadBalancer createLoadBalancer(PowerOfTwoChoicesConfiguration config, 21 | ServiceDiscovery serviceDiscovery) { 22 | return new PowerOfTwoChoicesLoadBalancer(Boolean.parseBoolean(config.getUseSecureRandom())); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /load-balancer/random/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.loadbalancer\\.random(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [ 28 | 29 | ] 30 | } 31 | }, { 32 | "extension" : "revapi.reporter.json", 33 | "configuration" : { 34 | "minSeverity" : "POTENTIALLY_BREAKING", 35 | "minCriticality" : "documented", 36 | "output" : "target/compatibility.json", 37 | "indent" : true, 38 | "append" : false, 39 | "keepEmptyFile" : true 40 | } 41 | }, { 42 | "extension" : "revapi.reporter.text", 43 | "configuration" : { 44 | "minSeverity" : "POTENTIALLY_BREAKING", 45 | "minCriticality" : "documented", 46 | "output" : "out" 47 | } 48 | } ] -------------------------------------------------------------------------------- /load-balancer/random/src/main/java/io/smallrye/stork/loadbalancer/random/RandomLoadBalancer.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.random; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.Random; 8 | 9 | import io.smallrye.stork.api.LoadBalancer; 10 | import io.smallrye.stork.api.NoServiceInstanceFoundException; 11 | import io.smallrye.stork.api.ServiceInstance; 12 | 13 | /** 14 | * A load balancer implementation randomly choosing an instance. 15 | */ 16 | public class RandomLoadBalancer implements LoadBalancer { 17 | 18 | private final Random random; 19 | 20 | /** 21 | * Creates a new random load balancer. 22 | * 23 | * @param useSecureRandom {@code true} if the load balancer should use a {@link SecureRandom} instance instead of 24 | * a {@link Random} 25 | */ 26 | protected RandomLoadBalancer(boolean useSecureRandom) { 27 | random = useSecureRandom ? new SecureRandom() : new Random(); 28 | } 29 | 30 | @Override 31 | public ServiceInstance selectServiceInstance(Collection serviceInstances) { 32 | if (serviceInstances.isEmpty()) { 33 | throw new NoServiceInstanceFoundException("No service instance found"); 34 | } 35 | 36 | // Fast track - single service instance 37 | int size = serviceInstances.size(); 38 | if (size == 1) { 39 | return serviceInstances.iterator().next(); 40 | } 41 | 42 | List list = new ArrayList<>(serviceInstances); 43 | return list.get(random.nextInt(size)); 44 | } 45 | 46 | @Override 47 | public boolean requiresStrictRecording() { 48 | return false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /load-balancer/random/src/main/java/io/smallrye/stork/loadbalancer/random/RandomLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.random; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.config.LoadBalancerAttribute; 8 | import io.smallrye.stork.api.config.LoadBalancerType; 9 | import io.smallrye.stork.spi.LoadBalancerProvider; 10 | 11 | /** 12 | * A load balancer provider that choose a random service instance from the discovered list of service instances. 13 | */ 14 | @LoadBalancerType("random") 15 | @LoadBalancerAttribute(name = "use-secure-random", defaultValue = "false", description = "Whether the load balancer should use a SecureRandom instead of a Random (default). Check [this page](https://stackoverflow.com/questions/11051205/difference-between-java-util-random-and-java-security-securerandom) to understand the difference") 16 | @ApplicationScoped 17 | public class RandomLoadBalancerProvider 18 | implements LoadBalancerProvider { 19 | 20 | @Override 21 | public LoadBalancer createLoadBalancer(RandomConfiguration config, 22 | ServiceDiscovery serviceDiscovery) { 23 | return new RandomLoadBalancer(Boolean.parseBoolean(config.getUseSecureRandom())); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /load-balancer/sticky/revapi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "extension": "revapi.java", 4 | "id": "java", 5 | "configuration": { 6 | "missing-classes": { 7 | "behavior": "report", 8 | "ignoreMissingAnnotations": false 9 | } 10 | } 11 | }, 12 | { 13 | "extension": "revapi.filter", 14 | "configuration": { 15 | "elements": { 16 | "include": [ 17 | { 18 | "matcher": "java-package", 19 | "match": "/io\\.smallrye\\.stork\\.loadbalancer\\.sticky(\\..*)?/" 20 | } 21 | ] 22 | } 23 | } 24 | }, 25 | { 26 | "extension": "revapi.differences", 27 | "id": "breaking-changes", 28 | "configuration": { 29 | "criticality": "highlight", 30 | "minSeverity": "POTENTIALLY_BREAKING", 31 | "minCriticality": "documented", 32 | "differences": [ 33 | ] 34 | } 35 | }, 36 | { 37 | "extension": "revapi.reporter.json", 38 | "configuration": { 39 | "minSeverity": "POTENTIALLY_BREAKING", 40 | "minCriticality": "documented", 41 | "output": "target/compatibility.json", 42 | "indent": true, 43 | "append": false, 44 | "keepEmptyFile": true 45 | } 46 | }, 47 | { 48 | "extension": "revapi.reporter.text", 49 | "configuration": { 50 | "minSeverity": "POTENTIALLY_BREAKING", 51 | "minCriticality": "documented", 52 | "output": "out" 53 | } 54 | } 55 | ] -------------------------------------------------------------------------------- /load-balancer/sticky/src/main/java/io/smallrye/stork/loadbalancer/random/StickyLoadBalancerProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.loadbalancer.random; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.config.LoadBalancerAttribute; 8 | import io.smallrye.stork.api.config.LoadBalancerType; 9 | import io.smallrye.stork.spi.LoadBalancerProvider; 10 | import io.smallrye.stork.utils.DurationUtils; 11 | 12 | /** 13 | * A load balancer provider that keep returning the same instance until that instance becomes unavailable or fails. 14 | */ 15 | @LoadBalancerType(StickyLoadBalancerProvider.TYPE) 16 | @LoadBalancerAttribute(name = StickyLoadBalancerProvider.FAILURE_BACKOFF_TIME, defaultValue = "0", description = "After how much time, " 17 | + "a service instance that has failed can be reused.") 18 | @ApplicationScoped 19 | public class StickyLoadBalancerProvider 20 | implements LoadBalancerProvider { 21 | 22 | static final String TYPE = "sticky"; 23 | static final String FAILURE_BACKOFF_TIME = "failure-backoff-time"; 24 | 25 | @Override 26 | public LoadBalancer createLoadBalancer(StickyConfiguration config, 27 | ServiceDiscovery serviceDiscovery) { 28 | return new StickyLoadBalancer(DurationUtils.parseDuration(config.getFailureBackoffTime(), FAILURE_BACKOFF_TIME)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /microprofile/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.smallrye.stork 7 | stork-parent 8 | 2.7.4-SNAPSHOT 9 | 10 | 11 | stork-microprofile-config 12 | 13 | SmallRye Stork : MicroProfile Config integration 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | io.smallrye.stork 22 | stork-core 23 | 24 | 25 | org.eclipse.microprofile.config 26 | microprofile-config-api 27 | 28 | 29 | org.jboss.logging 30 | jboss-logging 31 | 32 | 33 | io.smallrye.config 34 | smallrye-config 35 | test 36 | 37 | 38 | io.smallrye.stork 39 | stork-test-utils 40 | test 41 | 42 | 43 | org.junit.jupiter 44 | junit-jupiter 45 | test 46 | 47 | 48 | org.assertj 49 | assertj-core 50 | test 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /microprofile/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.microprofile(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [ ] 28 | } 29 | }, { 30 | "extension" : "revapi.reporter.json", 31 | "configuration" : { 32 | "minSeverity" : "POTENTIALLY_BREAKING", 33 | "minCriticality" : "documented", 34 | "output" : "target/compatibility.json", 35 | "indent" : true, 36 | "append" : false, 37 | "keepEmptyFile" : true 38 | } 39 | }, { 40 | "extension" : "revapi.reporter.text", 41 | "configuration" : { 42 | "minSeverity" : "POTENTIALLY_BREAKING", 43 | "minCriticality" : "documented", 44 | "output" : "out" 45 | } 46 | } ] -------------------------------------------------------------------------------- /microprofile/src/main/java/io/smallrye/stork/microprofile/MicroProfileConfigProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.microprofile; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.eclipse.microprofile.config.Config; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import io.smallrye.stork.api.config.ServiceConfig; 13 | import io.smallrye.stork.spi.config.ConfigProvider; 14 | import io.smallrye.stork.spi.config.SimpleServiceConfig; 15 | import io.smallrye.stork.utils.StorkConfigUtils; 16 | 17 | /** 18 | * Implementation of {@link ConfigProvider} using MicroProfile Config. 19 | */ 20 | public class MicroProfileConfigProvider implements ConfigProvider { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(MicroProfileConfigProvider.class); 23 | 24 | private final List serviceConfigs = new ArrayList<>(); 25 | 26 | /** 27 | * Creates a new instance of MicroProfileConfigProvider. 28 | */ 29 | public MicroProfileConfigProvider() { 30 | Config config = org.eclipse.microprofile.config.ConfigProvider.getConfig(); 31 | 32 | Map> propertiesByServiceName = new HashMap<>(); 33 | 34 | for (String propertyName : config.getPropertyNames()) { 35 | StorkConfigUtils.computeServiceProperty(propertiesByServiceName, propertyName, 36 | config.getValue(propertyName, String.class)); 37 | } 38 | 39 | for (Map.Entry> serviceEntry : propertiesByServiceName.entrySet()) { 40 | SimpleServiceConfig serviceConfig = StorkConfigUtils.buildServiceConfig(serviceEntry); 41 | serviceConfigs.add(serviceConfig); 42 | } 43 | } 44 | 45 | @Override 46 | public List getConfigs() { 47 | return serviceConfigs; 48 | } 49 | 50 | @Override 51 | public int priority() { 52 | return 100; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /microprofile/src/main/resources/META-INF/services/io.smallrye.stork.spi.config.ConfigProvider: -------------------------------------------------------------------------------- 1 | io.smallrye.stork.microprofile.MicroProfileConfigProvider -------------------------------------------------------------------------------- /microprofile/src/test/java/io/smallrye/stork/microprofile/TestMicroProfileConfigProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.microprofile; 2 | 3 | import org.eclipse.microprofile.config.Config; 4 | import org.eclipse.microprofile.config.spi.ConfigBuilder; 5 | import org.eclipse.microprofile.config.spi.ConfigProviderResolver; 6 | 7 | public class TestMicroProfileConfigProvider extends ConfigProviderResolver { 8 | private final Config config; 9 | 10 | public TestMicroProfileConfigProvider(Config config) { 11 | this.config = config; 12 | } 13 | 14 | @Override 15 | public Config getConfig() { 16 | return config; 17 | } 18 | 19 | @Override 20 | public Config getConfig(ClassLoader loader) { 21 | return null; 22 | } 23 | 24 | @Override 25 | public ConfigBuilder getBuilder() { 26 | throw new IllegalStateException("method not supported"); 27 | } 28 | 29 | @Override 30 | public void registerConfig(Config config, ClassLoader classLoader) { 31 | throw new IllegalStateException("method not supported"); 32 | } 33 | 34 | @Override 35 | public void releaseConfig(Config config) { 36 | throw new IllegalStateException("method not supported"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /service-discovery/composite/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.servicediscovery\\.composite(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [ 28 | 29 | ] 30 | } 31 | }, { 32 | "extension" : "revapi.reporter.json", 33 | "configuration" : { 34 | "minSeverity" : "POTENTIALLY_BREAKING", 35 | "minCriticality" : "documented", 36 | "output" : "target/compatibility.json", 37 | "indent" : true, 38 | "append" : false, 39 | "keepEmptyFile" : true 40 | } 41 | }, { 42 | "extension" : "revapi.reporter.text", 43 | "configuration" : { 44 | "minSeverity" : "POTENTIALLY_BREAKING", 45 | "minCriticality" : "documented", 46 | "output" : "out" 47 | } 48 | } ] -------------------------------------------------------------------------------- /service-discovery/composite/src/main/java/io/smallrye/stork/servicediscovery/composite/util/CombiningIterator.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.servicediscovery.composite.util; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | 6 | class CombiningIterator implements Iterator { 7 | 8 | // iterator of a list that contains the next element, or null: 9 | private Iterator nextNonEmptyIterator; 10 | 11 | private final Iterator> listOfListsIterator; 12 | 13 | CombiningIterator(List> contents) { 14 | listOfListsIterator = contents.iterator(); 15 | nextNonEmptyIterator = nextNonEmptyIterator(listOfListsIterator); 16 | } 17 | 18 | private Iterator nextNonEmptyIterator(Iterator> listIterator) { 19 | while (listIterator.hasNext()) { 20 | List currentList = listIterator.next(); 21 | if (!currentList.isEmpty()) { 22 | return currentList.iterator(); 23 | } 24 | } 25 | return null; 26 | } 27 | 28 | @Override 29 | public boolean hasNext() { 30 | return nextNonEmptyIterator != null && nextNonEmptyIterator.hasNext(); 31 | } 32 | 33 | @Override 34 | public T next() { 35 | T result = nextNonEmptyIterator.next(); 36 | if (!nextNonEmptyIterator.hasNext()) { // the end of the current list 37 | nextNonEmptyIterator = nextNonEmptyIterator(listOfListsIterator); 38 | } 39 | return result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /service-discovery/consul/src/main/java/io/smallrye/stork/servicediscovery/consul/ConsulServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.servicediscovery.consul; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | import io.smallrye.stork.api.ServiceDiscovery; 6 | import io.smallrye.stork.api.config.ServiceConfig; 7 | import io.smallrye.stork.api.config.ServiceDiscoveryAttribute; 8 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 9 | import io.smallrye.stork.impl.CachingServiceDiscovery; 10 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 11 | import io.smallrye.stork.spi.StorkInfrastructure; 12 | import io.vertx.core.Vertx; 13 | 14 | /** 15 | * The consul service discovery provider implementation. 16 | */ 17 | @ServiceDiscoveryAttribute(name = "consul-host", description = "The Consul host.", defaultValue = "localhost") 18 | @ServiceDiscoveryAttribute(name = "consul-port", description = "The Consul port.", defaultValue = "8500") 19 | @ServiceDiscoveryAttribute(name = "use-health-checks", description = "Whether to use health check.", defaultValue = "true") 20 | @ServiceDiscoveryAttribute(name = "application", description = "The application name; if not defined Stork service name will be used.") 21 | @ServiceDiscoveryAttribute(name = "refresh-period", description = "Service discovery cache refresh period.", defaultValue = CachingServiceDiscovery.DEFAULT_REFRESH_INTERVAL) 22 | @ServiceDiscoveryAttribute(name = "secure", description = "whether the connection with the service should be encrypted with TLS.") 23 | @ServiceDiscoveryType("consul") 24 | @ApplicationScoped 25 | public class ConsulServiceDiscoveryProvider implements ServiceDiscoveryProvider { 26 | 27 | @Override 28 | public ServiceDiscovery createServiceDiscovery(ConsulConfiguration config, String serviceName, 29 | ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { 30 | return new ConsulServiceDiscovery(serviceName, config, storkInfrastructure.get(Vertx.class, Vertx::vertx)); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /service-discovery/dns/consul: -------------------------------------------------------------------------------- 1 | docker run --rm --name consul -p 8500:8500 -p 8501:8501 consul:1.7 agent -dev -ui -client=0.0.0.0 -bind=0.0.0.0 --https-port=8501 2 | docker run --rm --name consul -p 8500:8500 -p 8600:8600/udp consul:1.7 agent -dev -ui -client=0.0.0.0 -bind=0.0.0.0 --https-port=8500 -------------------------------------------------------------------------------- /service-discovery/dns/src/main/java/io/smallrye/stork/servicediscovery/dns/DnsMetadataKey.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.servicediscovery.dns; 2 | 3 | import io.smallrye.stork.api.MetadataKey; 4 | 5 | /** 6 | * The DNS metadata keys. 7 | */ 8 | public enum DnsMetadataKey implements MetadataKey { 9 | 10 | /** 11 | * DNS hostname. 12 | */ 13 | DNS_NAME("dns-name"), 14 | 15 | /** 16 | * In case of SRV queries, weight for the target. Otherwise, 1. 17 | */ 18 | DNS_WEIGHT("dns-weight"); 19 | 20 | private final String name; 21 | 22 | /** 23 | * Creates a new DnsMetadataKey 24 | * 25 | * @param name the name 26 | */ 27 | DnsMetadataKey(String name) { 28 | this.name = name; 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /service-discovery/dns/src/main/java/io/smallrye/stork/servicediscovery/dns/DnsRecordType.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.servicediscovery.dns; 2 | 3 | public enum DnsRecordType { 4 | SRV, 5 | A, 6 | AAAA 7 | } 8 | -------------------------------------------------------------------------------- /service-discovery/eureka/src/test/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallrye/smallrye-stork/47751840baa0ae2fadc5e010e186526b86cb2bc6/service-discovery/eureka/src/test/resources/application.properties -------------------------------------------------------------------------------- /service-discovery/knative/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.servicediscovery\\.knative(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [ 28 | 29 | ] 30 | } 31 | }, { 32 | "extension" : "revapi.reporter.json", 33 | "configuration" : { 34 | "minSeverity" : "POTENTIALLY_BREAKING", 35 | "minCriticality" : "documented", 36 | "output" : "target/compatibility.json", 37 | "indent" : true, 38 | "append" : false, 39 | "keepEmptyFile" : true 40 | } 41 | }, { 42 | "extension" : "revapi.reporter.text", 43 | "configuration" : { 44 | "minSeverity" : "POTENTIALLY_BREAKING", 45 | "minCriticality" : "documented", 46 | "output" : "out" 47 | } 48 | } ] -------------------------------------------------------------------------------- /service-discovery/knative/src/main/java/io/smallrye/stork/servicediscovery/knative/KnativeMetadataKey.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.servicediscovery.knative; 2 | 3 | import io.smallrye.stork.api.MetadataKey; 4 | 5 | /** 6 | * Knative metadata keys. 7 | */ 8 | public enum KnativeMetadataKey implements MetadataKey { 9 | 10 | /** 11 | * The key to access the knative service id 12 | */ 13 | META_KNATIVE_SERVICE_ID("knative-service-id"), 14 | /** 15 | * The key to access the knative namespace 16 | */ 17 | META_KNATIVE_NAMESPACE("knative-namespace"), 18 | 19 | /** 20 | * The key to access the knative revision name 21 | */ 22 | META_KNATIVE_LATEST_REVISION("knative-latest-revision"); 23 | 24 | private final String name; 25 | 26 | KnativeMetadataKey(String name) { 27 | this.name = name; 28 | } 29 | 30 | @Override 31 | public String getName() { 32 | return name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /service-discovery/knative/src/main/java/io/smallrye/stork/servicediscovery/knative/KnativeServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.servicediscovery.knative; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | import io.smallrye.stork.api.ServiceDiscovery; 6 | import io.smallrye.stork.api.config.ServiceConfig; 7 | import io.smallrye.stork.api.config.ServiceDiscoveryAttribute; 8 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 9 | import io.smallrye.stork.impl.CachingServiceDiscovery; 10 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 11 | import io.smallrye.stork.spi.StorkInfrastructure; 12 | import io.vertx.core.Vertx; 13 | 14 | /** 15 | * Service discovery provider for Knative. 16 | */ 17 | @ServiceDiscoveryType("knative") 18 | @ServiceDiscoveryAttribute(name = "knative-host", description = "The Knative API host.") 19 | @ServiceDiscoveryAttribute(name = "knative-namespace", description = "The namespace of the service. Use all to discover all namespaces.") 20 | @ServiceDiscoveryAttribute(name = "application", description = "The Knative application Id; if not defined Stork service name will be used.") 21 | @ServiceDiscoveryAttribute(name = "refresh-period", description = "Service discovery cache refresh period.", defaultValue = CachingServiceDiscovery.DEFAULT_REFRESH_INTERVAL) 22 | @ServiceDiscoveryAttribute(name = "secure", description = "Whether the connection with the service should be encrypted with TLS.") 23 | @ApplicationScoped 24 | public class KnativeServiceDiscoveryProvider 25 | implements ServiceDiscoveryProvider { 26 | 27 | @Override 28 | public ServiceDiscovery createServiceDiscovery(KnativeConfiguration config, String serviceName, 29 | ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { 30 | return new KnativeServiceDiscovery(serviceName, config, storkInfrastructure.get(Vertx.class, Vertx::vertx)); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /service-discovery/kubernetes/src/main/java/io/smallrye/stork/servicediscovery/kubernetes/KubernetesMetadataKey.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.servicediscovery.kubernetes; 2 | 3 | import io.smallrye.stork.api.MetadataKey; 4 | 5 | /** 6 | * Kubernetes metadata keys. 7 | */ 8 | public enum KubernetesMetadataKey implements MetadataKey { 9 | 10 | /** 11 | * The key to access the kubernetes service id 12 | */ 13 | META_K8S_SERVICE_ID("k8s-service-id"), 14 | /** 15 | * The key to access the kubernates namespace 16 | */ 17 | META_K8S_NAMESPACE("k8s-namespace"), 18 | 19 | /** 20 | * The key to access the port protocol 21 | */ 22 | META_K8S_PORT_PROTOCOL("k8s-port-protocol"); 23 | 24 | private final String name; 25 | 26 | KubernetesMetadataKey(String name) { 27 | this.name = name; 28 | } 29 | 30 | @Override 31 | public String getName() { 32 | return name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /service-registration/consul/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.serviceregistration\\.consul(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [] 28 | } 29 | }, { 30 | "extension" : "revapi.reporter.json", 31 | "configuration" : { 32 | "minSeverity" : "POTENTIALLY_BREAKING", 33 | "minCriticality" : "documented", 34 | "output" : "target/compatibility.json", 35 | "indent" : true, 36 | "append" : false, 37 | "keepEmptyFile" : true 38 | } 39 | }, { 40 | "extension" : "revapi.reporter.text", 41 | "configuration" : { 42 | "minSeverity" : "POTENTIALLY_BREAKING", 43 | "minCriticality" : "documented", 44 | "output" : "out" 45 | } 46 | } ] -------------------------------------------------------------------------------- /service-registration/consul/src/main/java/io/smallrye/stork/serviceregistration/consul/ConsulServiceRegistrarProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.serviceregistration.consul; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.smallrye.stork.api.ServiceRegistrar; 7 | import io.smallrye.stork.api.config.ServiceRegistrarAttribute; 8 | import io.smallrye.stork.api.config.ServiceRegistrarType; 9 | import io.smallrye.stork.impl.ConsulMetadataKey; 10 | import io.smallrye.stork.spi.ServiceRegistrarProvider; 11 | import io.smallrye.stork.spi.StorkInfrastructure; 12 | 13 | @ServiceRegistrarType(value = "consul", metadataKey = ConsulMetadataKey.class) 14 | @ServiceRegistrarAttribute(name = "consul-host", description = "The Consul host.", defaultValue = "localhost") 15 | @ServiceRegistrarAttribute(name = "consul-port", description = "The Consul port.", defaultValue = "8500") 16 | public class ConsulServiceRegistrarProvider 17 | implements ServiceRegistrarProvider { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(ConsulServiceRegistrarProvider.class); 20 | 21 | @Override 22 | public ServiceRegistrar createServiceRegistrar(ConsulRegistrarConfiguration config, 23 | String serviceRegistrarName, StorkInfrastructure infrastructure) { 24 | return new ConsulServiceRegistrar(config, serviceRegistrarName, infrastructure); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /service-registration/eureka/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.serviceregistration\\.eureka(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [] 28 | } 29 | }, { 30 | "extension" : "revapi.reporter.json", 31 | "configuration" : { 32 | "minSeverity" : "POTENTIALLY_BREAKING", 33 | "minCriticality" : "documented", 34 | "output" : "target/compatibility.json", 35 | "indent" : true, 36 | "append" : false, 37 | "keepEmptyFile" : true 38 | } 39 | }, { 40 | "extension" : "revapi.reporter.text", 41 | "configuration" : { 42 | "minSeverity" : "POTENTIALLY_BREAKING", 43 | "minCriticality" : "documented", 44 | "output" : "out" 45 | } 46 | } ] -------------------------------------------------------------------------------- /service-registration/eureka/src/main/java/io/smallrye/stork/serviceregistration/eureka/EurekaServiceRegistrarProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.serviceregistration.eureka; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import io.smallrye.stork.api.ServiceRegistrar; 9 | import io.smallrye.stork.api.config.ServiceRegistrarAttribute; 10 | import io.smallrye.stork.api.config.ServiceRegistrarType; 11 | import io.smallrye.stork.impl.EurekaMetadataKey; 12 | import io.smallrye.stork.spi.ServiceRegistrarProvider; 13 | import io.smallrye.stork.spi.StorkInfrastructure; 14 | 15 | @ServiceRegistrarType(value = "eureka", metadataKey = EurekaMetadataKey.class) 16 | @ServiceRegistrarAttribute(name = "eureka-host", description = "The Eureka host.", defaultValue = "localhost") 17 | @ServiceRegistrarAttribute(name = "eureka-port", description = "The Eureka port.", defaultValue = "8761") 18 | @ServiceRegistrarAttribute(name = "eureka-context-path", description = "The Eureka server root context path.", defaultValue = "/") 19 | @ServiceRegistrarAttribute(name = "eureka-trust-all", description = "Enable/Disable the TLS certificate verification", defaultValue = "false") 20 | @ServiceRegistrarAttribute(name = "eureka-tls", description = "Use TLS to connect to the Eureka server", defaultValue = "false") 21 | @ApplicationScoped 22 | public class EurekaServiceRegistrarProvider 23 | implements ServiceRegistrarProvider { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(EurekaServiceRegistrarProvider.class); 26 | 27 | @Override 28 | public ServiceRegistrar createServiceRegistrar(EurekaRegistrarConfiguration config, 29 | String serviceRegistrarName, StorkInfrastructure infrastructure) { 30 | return new EurekaServiceRegistrar(config, serviceRegistrarName, infrastructure); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /service-registration/static-list/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.serviceregistration\\.staticlist(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [] 28 | } 29 | }, { 30 | "extension" : "revapi.reporter.json", 31 | "configuration" : { 32 | "minSeverity" : "POTENTIALLY_BREAKING", 33 | "minCriticality" : "documented", 34 | "output" : "target/compatibility.json", 35 | "indent" : true, 36 | "append" : false, 37 | "keepEmptyFile" : true 38 | } 39 | }, { 40 | "extension" : "revapi.reporter.text", 41 | "configuration" : { 42 | "minSeverity" : "POTENTIALLY_BREAKING", 43 | "minCriticality" : "documented", 44 | "output" : "out" 45 | } 46 | } ] -------------------------------------------------------------------------------- /service-registration/static-list/src/main/java/io/smallrye/stork/serviceregistration/staticlist/StaticListServiceRegistrar.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.serviceregistration.staticlist; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.smallrye.mutiny.Uni; 7 | import io.smallrye.stork.api.Metadata; 8 | import io.smallrye.stork.api.ServiceRegistrar; 9 | import io.smallrye.stork.spi.StorkInfrastructure; 10 | import io.smallrye.stork.utils.HostAndPort; 11 | import io.smallrye.stork.utils.InMemoryAddressesBackend; 12 | import io.smallrye.stork.utils.StorkAddressUtils; 13 | 14 | public class StaticListServiceRegistrar implements ServiceRegistrar { 15 | private static final Logger log = LoggerFactory.getLogger(StaticListServiceRegistrar.class); 16 | private final StaticRegistrarConfiguration config; 17 | 18 | public StaticListServiceRegistrar(StaticRegistrarConfiguration config, String serviceName, 19 | StorkInfrastructure infrastructure) { 20 | this.config = config; 21 | } 22 | 23 | @Override 24 | public Uni registerServiceInstance(String serviceName, Metadata metadata, 25 | String ipAddress, 26 | int defaultPort) { 27 | checkAddressNotNull(ipAddress); 28 | HostAndPort hostAndPortToAdd = StorkAddressUtils.parseToHostAndPort(ipAddress, defaultPort, 29 | "service '" + serviceName + "'"); 30 | String hostAndPortToAddString = StorkAddressUtils.parseToString(hostAndPortToAdd); 31 | InMemoryAddressesBackend.add(serviceName, hostAndPortToAddString); 32 | log.info("Address {} has been registered for service {}", hostAndPortToAddString, serviceName); 33 | return Uni.createFrom().voidItem(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /spring-boot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.smallrye.stork 7 | stork-parent 8 | 2.7.4-SNAPSHOT 9 | 10 | 11 | stork-spring-boot-config 12 | 13 | SmallRye Stork : Spring Boot Config integration 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | io.smallrye.stork 22 | stork-core 23 | 24 | 25 | org.springframework.boot 26 | spring-boot 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-autoconfigure 31 | 32 | 33 | org.jboss.logging 34 | jboss-logging 35 | 36 | 37 | io.smallrye.config 38 | smallrye-config 39 | test 40 | 41 | 42 | io.smallrye.stork 43 | stork-test-utils 44 | test 45 | 46 | 47 | org.junit.jupiter 48 | junit-jupiter 49 | test 50 | 51 | 52 | org.assertj 53 | assertj-core 54 | test 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /spring-boot/revapi.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "extension" : "revapi.java", 3 | "id" : "java", 4 | "configuration" : { 5 | "missing-classes" : { 6 | "behavior" : "report", 7 | "ignoreMissingAnnotations" : false 8 | } 9 | } 10 | }, { 11 | "extension" : "revapi.filter", 12 | "configuration" : { 13 | "elements" : { 14 | "include" : [ { 15 | "matcher" : "java-package", 16 | "match" : "/io\\.smallrye\\.stork\\.springboot(\\..*)?/" 17 | } ] 18 | } 19 | } 20 | }, { 21 | "extension" : "revapi.differences", 22 | "id" : "breaking-changes", 23 | "configuration" : { 24 | "criticality" : "highlight", 25 | "minSeverity" : "POTENTIALLY_BREAKING", 26 | "minCriticality" : "documented", 27 | "differences" : [ ] 28 | } 29 | }, { 30 | "extension" : "revapi.reporter.json", 31 | "configuration" : { 32 | "minSeverity" : "POTENTIALLY_BREAKING", 33 | "minCriticality" : "documented", 34 | "output" : "target/compatibility.json", 35 | "indent" : true, 36 | "append" : false, 37 | "keepEmptyFile" : true 38 | } 39 | }, { 40 | "extension" : "revapi.reporter.text", 41 | "configuration" : { 42 | "minSeverity" : "POTENTIALLY_BREAKING", 43 | "minCriticality" : "documented", 44 | "output" : "out" 45 | } 46 | } ] -------------------------------------------------------------------------------- /spring-boot/src/main/java/io/smallrye/stork/springboot/SpringBootApplicationContextProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.springboot; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | 7 | public class SpringBootApplicationContextProvider implements ApplicationContextAware { 8 | private static ApplicationContext applicationContext; 9 | 10 | @Override 11 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 12 | SpringBootApplicationContextProvider.applicationContext = applicationContext; 13 | } 14 | 15 | public static ApplicationContext getApplicationContext() { 16 | return applicationContext; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot/src/main/resources/META-INF/services/io.smallrye.stork.spi.config.ConfigProvider: -------------------------------------------------------------------------------- 1 | io.smallrye.stork.springboot.SpringBootConfigProvider -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/EmptyServicesServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import io.smallrye.mutiny.Uni; 7 | import io.smallrye.stork.api.ServiceDiscovery; 8 | import io.smallrye.stork.api.ServiceInstance; 9 | import io.smallrye.stork.api.config.ServiceConfig; 10 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 11 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 12 | import io.smallrye.stork.spi.StorkInfrastructure; 13 | 14 | /** 15 | * A service discovery implementation returning an empty list of service instances. 16 | * The list is unmodifiable. 17 | */ 18 | @ServiceDiscoveryType("empty-services") 19 | public class EmptyServicesServiceDiscoveryProvider 20 | implements ServiceDiscoveryProvider { 21 | 22 | private static final List EMPTY = Collections.emptyList(); 23 | 24 | @Override 25 | public ServiceDiscovery createServiceDiscovery(EmptyServicesConfiguration config, 26 | String serviceName, ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { 27 | return () -> Uni.createFrom().item(EMPTY); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/StorkTestUtils.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import io.smallrye.stork.Stork; 4 | import io.smallrye.stork.integration.DefaultStorkInfrastructure; 5 | 6 | public class StorkTestUtils { 7 | @SuppressWarnings("deprecation") 8 | public static Stork getNewStorkInstance() { 9 | return new Stork(new DefaultStorkInfrastructure()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/TestLoadBalancer1.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import java.util.Collection; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.ServiceInstance; 8 | 9 | public class TestLoadBalancer1 implements LoadBalancer { 10 | 11 | private final TestLb1Configuration config; 12 | private final ServiceDiscovery serviceDiscovery; 13 | private final String type; 14 | 15 | public TestLoadBalancer1(TestLb1Configuration config, ServiceDiscovery serviceDiscovery, String type) { 16 | this.config = config; 17 | this.serviceDiscovery = serviceDiscovery; 18 | this.type = type; 19 | } 20 | 21 | @Override 22 | public ServiceInstance selectServiceInstance(Collection serviceInstances) { 23 | return null; 24 | } 25 | 26 | public TestLb1Configuration getConfig() { 27 | return config; 28 | } 29 | 30 | public ServiceDiscovery getServiceDiscovery() { 31 | return serviceDiscovery; 32 | } 33 | 34 | public String getType() { 35 | return type; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/TestLoadBalancer1Provider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import io.smallrye.stork.api.LoadBalancer; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | import io.smallrye.stork.api.config.LoadBalancerType; 6 | import io.smallrye.stork.spi.LoadBalancerProvider; 7 | 8 | @LoadBalancerType(TestLoadBalancer1Provider.TYPE) 9 | public class TestLoadBalancer1Provider implements LoadBalancerProvider { 10 | 11 | public static final String TYPE = "test-lb-1"; 12 | 13 | @Override 14 | public LoadBalancer createLoadBalancer(TestLb1Configuration config, ServiceDiscovery serviceDiscovery) { 15 | return new TestLoadBalancer1(config, serviceDiscovery, TYPE); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/TestLoadBalancer2.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import java.util.Collection; 4 | 5 | import io.smallrye.stork.api.LoadBalancer; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.ServiceInstance; 8 | 9 | public class TestLoadBalancer2 implements LoadBalancer { 10 | 11 | private final TestLb2Configuration config; 12 | private final ServiceDiscovery serviceDiscovery; 13 | private final String type; 14 | 15 | public TestLoadBalancer2(TestLb2Configuration config, ServiceDiscovery serviceDiscovery, String type) { 16 | this.config = config; 17 | this.serviceDiscovery = serviceDiscovery; 18 | this.type = type; 19 | } 20 | 21 | @Override 22 | public ServiceInstance selectServiceInstance(Collection serviceInstances) { 23 | return null; 24 | } 25 | 26 | public TestLb2Configuration getConfig() { 27 | return config; 28 | } 29 | 30 | public ServiceDiscovery getServiceDiscovery() { 31 | return serviceDiscovery; 32 | } 33 | 34 | public String getType() { 35 | return type; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/TestLoadBalancer2Provider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import io.smallrye.stork.api.LoadBalancer; 4 | import io.smallrye.stork.api.ServiceDiscovery; 5 | import io.smallrye.stork.api.config.LoadBalancerAttribute; 6 | import io.smallrye.stork.api.config.LoadBalancerType; 7 | import io.smallrye.stork.spi.LoadBalancerProvider; 8 | 9 | @LoadBalancerType(TestLoadBalancer2Provider.TYPE) 10 | @LoadBalancerAttribute(name = "some-prop", description = "no description") 11 | public class TestLoadBalancer2Provider implements LoadBalancerProvider { 12 | 13 | public static final String TYPE = "test-lb-2"; 14 | 15 | @Override 16 | public LoadBalancer createLoadBalancer(TestLb2Configuration config, ServiceDiscovery serviceDiscovery) { 17 | return new TestLoadBalancer2(config, serviceDiscovery, TYPE); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/TestServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import java.util.List; 4 | 5 | import io.smallrye.mutiny.Uni; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.ServiceInstance; 8 | 9 | public class TestServiceDiscovery implements ServiceDiscovery { 10 | 11 | private final TestSd1Configuration config; 12 | private final String type; 13 | 14 | public TestServiceDiscovery(TestSd1Configuration config, String type) { 15 | this.config = config; 16 | this.type = type; 17 | } 18 | 19 | @Override 20 | public Uni> getServiceInstances() { 21 | return null; 22 | } 23 | 24 | public TestSd1Configuration getConfig() { 25 | return config; 26 | } 27 | 28 | public String getType() { 29 | return type; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/TestServiceDiscovery1Provider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import io.smallrye.stork.api.ServiceDiscovery; 4 | import io.smallrye.stork.api.config.ServiceConfig; 5 | import io.smallrye.stork.api.config.ServiceDiscoveryAttribute; 6 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 7 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 8 | import io.smallrye.stork.spi.StorkInfrastructure; 9 | 10 | @ServiceDiscoveryAttribute(name = "one", description = "no description") 11 | @ServiceDiscoveryAttribute(name = "two", description = "no description") 12 | @ServiceDiscoveryType(TestServiceDiscovery1Provider.TYPE) 13 | public class TestServiceDiscovery1Provider implements ServiceDiscoveryProvider { 14 | 15 | public static final String TYPE = "test-sd-1"; 16 | 17 | @Override 18 | public ServiceDiscovery createServiceDiscovery(TestSd1Configuration config, String serviceName, 19 | ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { 20 | return new TestServiceDiscovery(config, TYPE); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/TestServiceDiscovery2.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import java.util.List; 4 | 5 | import io.smallrye.mutiny.Uni; 6 | import io.smallrye.stork.api.ServiceDiscovery; 7 | import io.smallrye.stork.api.ServiceInstance; 8 | 9 | public class TestServiceDiscovery2 implements ServiceDiscovery { 10 | 11 | private final TestSd2Configuration config; 12 | private final String type; 13 | 14 | public TestServiceDiscovery2(TestSd2Configuration config, String type, boolean secure) { 15 | this.config = config; 16 | this.type = type; 17 | } 18 | 19 | @Override 20 | public Uni> getServiceInstances() { 21 | return null; 22 | } 23 | 24 | public TestSd2Configuration getConfig() { 25 | return config; 26 | } 27 | 28 | public String getType() { 29 | return type; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test-utils/src/main/java/io/smallrye/stork/test/TestServiceDiscovery2Provider.java: -------------------------------------------------------------------------------- 1 | package io.smallrye.stork.test; 2 | 3 | import io.smallrye.stork.api.ServiceDiscovery; 4 | import io.smallrye.stork.api.config.ServiceConfig; 5 | import io.smallrye.stork.api.config.ServiceDiscoveryAttribute; 6 | import io.smallrye.stork.api.config.ServiceDiscoveryType; 7 | import io.smallrye.stork.spi.ServiceDiscoveryProvider; 8 | import io.smallrye.stork.spi.StorkInfrastructure; 9 | 10 | @ServiceDiscoveryAttribute(name = "three", description = "none") 11 | @ServiceDiscoveryType(TestServiceDiscovery2Provider.TYPE) 12 | public class TestServiceDiscovery2Provider implements ServiceDiscoveryProvider { 13 | 14 | public static final String TYPE = "test-sd-2"; 15 | 16 | @Override 17 | public ServiceDiscovery createServiceDiscovery(TestSd2Configuration config, String serviceName, 18 | ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { 19 | return new TestServiceDiscovery2(config, TYPE, false); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/META-INF/services/io.smallrye.stork.spi.config.ConfigProvider: -------------------------------------------------------------------------------- 1 | io.smallrye.stork.test.TestConfigProvider --------------------------------------------------------------------------------