Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added hypermedia support in the form of a DiscoveredResource abstraction #76

Merged
merged 1 commit into from Jan 13, 2016

Conversation

odrotbohm
Copy link
Contributor

This commit introduces support to easily discover resources in remote service APIs and periodically validating they're still available.

The core concept is a DiscoveredResource and its primary implementation TraversingDiscoveredResource which consists of a ServiceInstanceProvider and a TraversalDescription. The service instance provider abstracts the location of the service the DiscoveredResource is hosted at. Currently there are two implementations available: a StaticServiceInstanceProvider that — as the name suggests — returns a statically configured instance, as well as the DynamicServiceInstanceProvider which takes a DiscoveryClient and service name as input to dynamically discover the ServiceInstance on request.

The TraversalDefinition is a SAM type that allows to define the path traversals that shall be executed to discover the target resource within the service instance. This allows the following configuration:

DiscoveryClient client = …
ServiceInstanceProvider provider = new DynamicServiceInstanceProvider(client, "stores");
DiscoveredResource resource = new DiscoveredResource(provider, traverson -> traverson.follow("stores", "search", "by-location");

This will cause the following to happen:

  1. In this default state the resource is undiscovered and calls to ….getLink() will return null.
  2. A call to ….verifyOrDiscover() will trigger resource discovery, i.e. lookup the service instance using the discovery client, execute the path traversal as defined and store the final link discovered ("by-location") in the discovered resource.
  3. Calls to ….getLink() will return that link. Calls to ….verifyOrDiscover() will issue a HEAD request to verify the resource is still available and reset the link if not. Subsequent calls to ….verifyOrDiscover() will trigger rediscovery.

That means that we basically shield against either the service instance becoming unavailable or the resource becoming unavailable within the service instance (due to some URI restructuring or the like).

The calls to ….verifyOrDiscover() are currently automated by a DiscoveredResourceRefresherwhich is auto-configured if a DiscoveredResource is declared as Spring bean and its initial and fixed delay can be configured via properties in the cloud.hypermedia namespace.

@odrotbohm odrotbohm force-pushed the hypermedia branch 2 times, most recently from 9e3fa1d to 7a64384 Compare December 23, 2015 16:02
}

@Data
@ConfigurationProperties(prefix = "cloud.hypermedia")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been prefixing with spring.cloud.

@spencergibb
Copy link
Member

From your description:
DiscoveredResource resource = new DiscoveredResource(provider, traverson -> traverson.follow("stores", "search", "by-location");
Should this be:
DiscoveredResource resource = new TraversingDiscoveredResource(provider, traverson -> traverson.follow("stores", "search", "by-location");?

@spencergibb
Copy link
Member

So this would replace this kind of work?

@odrotbohm
Copy link
Contributor Author

Re: naming in the commit message — indeed, I changed a few names and extracted interfaces in the course of writing up the summary and adding test cases.

Re: replacement — right, the client now looks like this (although that particular variant still uses Optional return as return type as it was based on Java 8. The configuration can be found here.

@spencergibb
Copy link
Member

@olivergierke awesome! That configuration link helps me see the power of this change!

@odrotbohm
Copy link
Contributor Author

I've changed the config properties namespace to spring.cloud.hypermedia. Also, I've changed the terms used slightly. The interface is now a RemoteResource, while the implementation using link traversal is the DiscoveredResource. I felt that's more correct as a different implementation could have the path to the resource backed by a static string (not very hypermedia-ish but still reasonable within the abstraction).

@spencergibb
Copy link
Member

@dsyer @marcingrzejszczak or @adriancole any comments?


return link;

} catch (RestClientException o_O) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception variable is awesome :)

@spencergibb
Copy link
Member

@olivergierke the following tests fail

  CloudHypermediaAutoConfigurationIntegrationTests.picksUpHypermediaProperties:49 » NoSuchBeanDefinition
  CloudHypermediaAutoConfigurationIntegrationTests.registersResourceRefresherIfDiscoverredResourceIsDefined:81 » NoSuchBeanDefinition

With the following errors respectively:


org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration$CloudHypermediaProperties] is defined

    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:372)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1066)
    at org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfigurationIntegrationTests.picksUpHypermediaProperties(CloudHypermediaAutoConfigurationIntegrationTests.java:49)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

and


org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.cloud.client.hypermedia.RemoteResourceRefresher] is defined

    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:372)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1066)
    at org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfigurationIntegrationTests.registersResourceRefresherIfDiscoverredResourceIsDefined(CloudHypermediaAutoConfigurationIntegrationTests.java:81)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

This commit introduces support to easily discover resources in remote
service APIs and periodically validating they're still available.

The core concept is a RemoteResource and its primary implementation
DiscoveredResource which consists of a ServiceInstanceProvider and a
TraversalDescription. The service instance provider abstracts the location
of the service the DiscoveredResource is hosted at. Currently there are
two implementations available: a StaticServiceInstanceProvider that — as
the name suggests — returns a statically configured instance, as well as
the DynamicServiceInstanceProvider which takes a DiscoveryClient and service
name as input to dynamically discover the ServiceInstance on request.

The TraversalDefinition is a SAM type that allows to define the path traversals
that shall be executed to discover the target resource within the service
instance. This allows the following configuration:

DiscoveryClient client = …
ServiceInstanceProvider provider = new DynamicServiceInstanceProvider(client, "stores");
RemoteResource resource = new DiscoveredResource(provider, traverson -> traverson.follow("stores", "search", "by-location");

This will cause the following to happen:

1. In this default state the resource is undiscovered and calls to getLink()
   will return null.
2. A call to verifyOrDiscover() will trigger resource discovery, i.e. lookup
   the service instance using the discovery client, execute the path traversal
   as defined and store the final link discovered ("by-location") in the
   discovered resource.
3. Calls to getLink() will return that link. Calls to verifyOrDiscover()
   will issue a HEAD request to verify the resource is still available and
   reset the link if not. Subsequent calls to verifyOrDiscover() will trigger
   rediscovery.

That means that we basically shield against either the service instance becoming
unavailable or the resource becoming unavailable within the service instance
(due to some URI restructuring or the like).

The calls to verifyOrDiscover() are currently automated by a RemoteResourceRefresher
which is auto-configured if a RemoteResource is declared as Spring bean
and its initial and fixed delay can be configured via properties in the
spring.cloud.hypermedia namespace.
@odrotbohm
Copy link
Contributor Author

That should be fixed :).

@spencergibb spencergibb added this to the 1.1.0 milestone Jan 13, 2016
spencergibb added a commit that referenced this pull request Jan 13, 2016
Added hypermedia support in the form of a DiscoveredResource abstraction
@spencergibb spencergibb merged commit 457d237 into spring-cloud:master Jan 13, 2016
@spencergibb
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants