-
Notifications
You must be signed in to change notification settings - Fork 693
Conversation
A Spring abstraction to simplify Google Cloud Pub/Sub topic and subscription programmatic management. Also adds helpers to instantiate PublisherFactory and SubscriberFactory. Fixes #26
There were questions on our side about whether this should be two separate classes: |
spring-cloud-gcp-pubsub/pom.xml
Outdated
<!-- Perl5Util, required by UrlValidator --> | ||
<groupId>oro</groupId> | ||
<artifactId>oro</artifactId> | ||
<version>2.0.8</version> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to avoid extra dependencies as much as possible. Aren't there really a tool in Google or Spring which may do the same for us?
Thanks
* @param pushEndpoint URL of the service receiving the push messages. If not provided, set to | ||
* Google Cloud Pub/Sub default | ||
* @return the created subscription | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to have JavaDocs for all the public API, even if it is copy/paste.
Thanks
private static final int DEFAULT_ACK_DEADLINE = 10; | ||
|
||
public PubsubAdmin(GcpProjectIdProvider projectIdProvider) throws IOException { | ||
this(projectIdProvider, TopicAdminClient.create(), SubscriptionAdminClient.create()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have IOException
in the ctor?
It is really anti-pattern to get access to low level resources from ctors
Also how those admins work without credentials?
I mostly mean you auto-config below
Thanjs
|
||
PushConfig.Builder pushConfigBuilder = PushConfig.newBuilder(); | ||
if (pushEndpoint != null) { | ||
Assert.isTrue(new UrlValidator().isValid(pushEndpoint), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why really new Url()
isn't enough here?
Why do we need to extra dependencies almost for nothing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I share your pain with the extra dependencies for seemingly little benefit..
new URL()
doesn't really cut it because it requires us to catch an exception instead of just returning a boolean (and the consequent performance hit, although that's not really important) and it doesn't perform validation by itself as evidenced by this comment.
Looking around the internet, this seems to be the most popular way of validating URLs.
We can also just let the Pubsub library blow up on invalid endpoint. I just tested and it does complain:
Exception in thread "main" com.google.api.gax.grpc.ApiException: io.grpc.StatusRuntimeException: INVALID_ARGUMENT: Invalid push endpoint given (endpoint=asdasd). Refer to https://cloud.google.com/pubsub/subscriber#create for more information.
at com.google.api.gax.grpc.ExceptionTransformingCallable$ExceptionTransformingFuture.onFailure(ExceptionTransformingCallable.java:108)
...
@ConditionalOnMissingBean | ||
public PubsubAdmin pubsubAdmin(GcpProjectIdProvider projectIdProvider) { | ||
try { | ||
return new PubsubAdmin(projectIdProvider); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
??? There is no such a ctor any more...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops! Should've installed it locally before..
|
||
@Bean | ||
@ConditionalOnMissingBean | ||
public PubsubAdmin pubsubAdmin(GcpProjectIdProvider projectIdProvider) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, the real auto-config is missed.
That is what I mean by the comment here: GoogleCloudPlatform/java-docs-samples#755 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, not sure what you mean with the autoconfig is missed?
I removed @IntegrationComponentScan
and @ComponentScan
and PubsubAdmin
is correctly autowired. You were right that I didn't need them, they must had been there from previous experiments...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Sorry. I thought the PubsubAdmin
is in its own auto-config class.
But that is good to hear that everything is fine there already.
Since that is a sample it would be good to keep it as simple as possible to avoid unexpected questions in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally agreed. It was exactly that kind of feedback I was seeking from you in the sample PR!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Sorry. I thought the PubsubAdmin
is in its own auto-config class.
But that is good to hear that everything is fine there already.
Since that is a sample it would be good to keep it as simple as possible to avoid unexpected questions in the future.
@artembilan @meltsufin I think I addressed all the comments. There is the question of whether this should be two classes (a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like that's all.
Only credentials concern.
public PubsubAdmin pubsubAdmin(GcpProjectIdProvider projectIdProvider) { | ||
try { | ||
return new PubsubAdmin(projectIdProvider, TopicAdminClient.create(), | ||
SubscriptionAdminClient.create()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
???
I asked before security.
How all these .create()
understand with what GC user they work?
Why this default, auto-configured PubsubAdmin
doesn't depend on the CredentialsProvider
?
That was really my first concern when I saw the same void create()
in the PubsubAdmin
ctors before.
Thanks
I would say that I don't need this wrapper - the See my comment about Maybe we really ca consider a variant where we hide I think I would go only with a single class anyway, if that is about my opinion. |
Good point on the credentials aspect -- it was my oversight. I think |
private SubscriptionAdminClient subscriptionAdminClient; | ||
|
||
/** Default inspired in the subscription creation web UI. */ | ||
private static final int DEFAULT_ACK_DEADLINE = 10; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to expose it as a configuration property?
Something like @Value("${spring.cloud.gcp.pubsub.defaultAckDeadline:10}")
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The createSubscription()
API lets you specify an ackDeadline
parameter. I didn't add the value here because the user can do that by themselves in another class (i.e., they can get it from the properties or any other source).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that the user can call the version of the method that takes ackDeadline
but being able to configure the default externally could still be useful. Hardcoded defaults seem to be a bit contrary to Spring's philosophy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that we should have setDefaultAckDeadline()
.
Like we have KafkaTemplate.setDefautTopic()
or RabbitTemplate.setReplyTimeout()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaultAckDeadline is now configurable through properties.
Does that address your concerns?
PagedResponseWrappers.ListTopicsPagedResponse topicListPage = | ||
this.topicAdminClient.listTopics(ProjectName.create(this.projectId)); | ||
|
||
return Lists.newArrayList(topicListPage.iterateAll()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name of the local variable topicListPage
seems to imply that it only contains a single page of results. Are you sure that iterateAll()
actually return all results rather than a single page? If so, maybe rename topicListPage
to just topicsList
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does only contain a single page of results. iterateAll()
returns the full collection. Or, better yet, the full collection counting from the current page.
https://github.com/googleapis/gax-java/blob/master/gax-grpc/src/main/java/com/google/api/gax/grpc/AbstractPage.java#L135
https://github.com/googleapis/gax-java/blob/master/gax-grpc/src/main/java/com/google/api/gax/grpc/AbstractPage.java#L173
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, my comment about the variable name still applies I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would make sense for a local variable containing the result of iterateAll()
to be called topicsList
, not the topicAdminClient.listTopics()
result. Also, the object type is ListTopicsPagedResponse
, which also hints to it being a page, rather than the whole thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"paged response" does not necessarily imply "page". You can also call it verbosely listTopicsPagedResponse
. In any case, it's so minor, it's not worth discussing further. :)
try { | ||
return SubscriptionAdminClient.create( | ||
SubscriptionAdminSettings.defaultBuilder() | ||
.setCredentialsProvider(credentialsProvider) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we have an additional PubsubAdmin
ctor based on the CredentialsProvider
to instantiate TopicAdminClient
and SubscriptionAdminClient
internally similar way?
Just in case for flexibility to let end-user to avoid this boilerplate code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I also like that kind of simplicity, but we would then need to have that kind of low level resource handling in the constructor you were opposed to earlier. i.e., we would need to instantiate TopicAdminClient
and SubscriptionAdminClient
in the constructor, and they can throw IOException
.
But if we're OK with it, I can add it. I personally don't mind the IOException
in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking to the private SubscriptionAdminSettings(Builder settingsBuilder) throws IOException {
it is really unclear why an IOException
is there from the day first at all.
So, having all these builders I don't think we really get access to low-level resource.
Anyway what you have here via dependency injection is still " low level resource handling" if you are interested in my opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only concern left.
At least give some answer, please.
Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, didn't mean to not reply here, just went grab lunch in the meantime. :)
IOException
is thrown because of the channel creation, and maybe other cases, although I'm not sure about the exact circumstances it can happen (internet endpoint is unreachable?). Because of that, I think you do have access to low level resources, like a TCP connection, when you're creating a Subscription
.
I really like this way because you still have to handle low level resources, but you do that away from the constructor. If you need to handle the IOException
, you do it away from the constructor. And it should still be easy enough for users to just call new PubsubAdmin(projectIdProvider, TopicAdminClient.create(), SubscriptionAdminClient.create())
.
Although, to be honest, I'm not opposed to offer that constructor on the side too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Fine with me. Looks like we get IOException
from the CredentialsProvider.getCredentials()
when we open a file on the matter.
No more discussion on the matter! I can live with this. 😄
@@ -46,7 +47,8 @@ | |||
private SubscriptionAdminClient subscriptionAdminClient; | |||
|
|||
/** Default inspired in the subscription creation web UI. */ | |||
private static final int DEFAULT_ACK_DEADLINE = 10; | |||
@Value("${spring.cloud.gcp.pubsub.subscriber.ackDeadline:10}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that's wrong.
To have @Value
resolved your must have specific configuration in the application context.
Also this looks like a mix of auto-config and regular components concerns.
This is fully fine to have this property resolution in the auto-config, but here it must be setter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At one point, I used a setter, but got rid of it because I don't want PubsubAdmin to have state. PubsubAdmin
basically becomes not threadsafe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
??? So, in your terms anything with setter is evil?
All the components in Spring are based on setters.
Right, you should always keep in mind if you can change the state of an object it isn't thread-safe.
That's not only for Spring classes.
I insist that we should not use @Value
in target components. That is end-user application feature or maximum auto-config.
And why do you care too much that it won't be thread-safe?.. More over the story is only about an int
value, which can be volatile
and that's all...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know about other Spring components, but IMO PubsubAdmin
being this central class with a bunch of functionality and singleton by default, it's likely to be used from several different threads and inconsistency here is an actual hazard. Also, the added value of setting different defaults hardly justifies risking inconsistency.
I'm not opposed to getting rid of the @Value
either -- setting a new default is a nice to have, but not that useful, and users have the ability to call createSubscription()
with a different default they have declared somewhere else.
Again, this is all IMO -- if you feel strongly about your point of view, I can add the setter...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I think the setter is fine. We make no claims about this class being thread-safe. Besides, this value would typically only be set on app initialization anyway.
return this.defaultAckDeadline; | ||
} | ||
|
||
public void setDefaultAckDeadline(int defaultAckDeadline) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JavaDocs for public API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For setters and getters?
public int getDefaultAckDeadline() { | ||
return this.defaultAckDeadline; | ||
} | ||
|
||
/** | ||
* Sets the default acknowledgement deadline value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit-pick.
Method JavaDocs should be imperative not descriptive.
In other words: Set the default ....
I was trying GCP pub sub form a spring boot app and get this error message on start up. It's relate to pub sub admin and auto configuration class. Has anyone seen this before? org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'pubSubAdmin' defined in class path resource [org/springframework/cloud/gcp/pubsub/autoconfig/GcpPubSubAutoConfiguration.class]: Unsatisfied dependency expressed through method 'pubSubAdmin' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'topicAdminClient' defined in class path resource [org/springframework/cloud/gcp/pubsub/autoconfig/GcpPubSubAutoConfiguration.class]: |
A Spring abstraction to simplify Google Cloud Pub/Sub topic and
subscription programmatic management. Also adds helpers to instantiate
PublisherFactory and SubscriberFactory.
Fixes #26