Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Adds PubsubAdmin #69

Merged
merged 14 commits into from
Jul 14, 2017
Merged

Adds PubsubAdmin #69

merged 14 commits into from
Jul 14, 2017

Conversation

joaoandremartins
Copy link
Contributor

A Spring abstraction to simplify Google Cloud Pub/Sub topic and
subscription programmatic management. Also adds helpers to instantiate
PublisherFactory and SubscriberFactory.

Fixes #26

A Spring abstraction to simplify Google Cloud Pub/Sub topic and
subscription programmatic management. Also adds helpers to instantiate
PublisherFactory and SubscriberFactory.

Fixes #26
@joaoandremartins
Copy link
Contributor Author

There were questions on our side about whether this should be two separate classes: TopicAdmin and SubscriptionAdmin. We went with single class PubsubAdmin just for ease of use, but I can be persuaded to go a different way.

@spring-attic spring-attic deleted a comment from meltsufin Jul 11, 2017
<!-- Perl5Util, required by UrlValidator -->
<groupId>oro</groupId>
<artifactId>oro</artifactId>
<version>2.0.8</version>
Copy link
Contributor

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
*/
Copy link
Contributor

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());
Copy link
Contributor

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),
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

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...

Copy link
Contributor Author

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) {
Copy link
Contributor

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)

Copy link
Contributor Author

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...

Copy link
Contributor

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.

Copy link
Contributor Author

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!

Copy link
Contributor

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.

@joaoandremartins
Copy link
Contributor Author

@artembilan @meltsufin I think I addressed all the comments.

There is the question of whether this should be two classes (a SubscriptionAdmin and a TopicAdmin), but I think this simplifies usage and we can probably go with this for now. Later we might even want to make two classes, and one central class that takes the two as inputs.

Copy link
Contributor

@artembilan artembilan left a 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());
Copy link
Contributor

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

@artembilan
Copy link
Contributor

I would say that I don't need this wrapper - the SubscriptionAdminClient and TopicAdminClient looks good to me as they are.
Only a gain of this wrapper that it utilize projectId. So be it.

See my comment about CredentialsProvider.

Maybe we really ca consider a variant where we hide SubscriptionAdminClient and TopicAdminClient instances, but let end-user to specify their options externally?

I think I would go only with a single class anyway, if that is about my opinion.

@joaoandremartins
Copy link
Contributor Author

Good point on the credentials aspect -- it was my oversight.

I think PubsubAdmin actually fits really well within the Spring context. The Google Pub/Sub library uses objects like SubscriptionName, TopicName, etc., that we can easily spare the end user and instead just have them give us Strings.

private SubscriptionAdminClient subscriptionAdminClient;

/** Default inspired in the subscription creation web UI. */
private static final int DEFAULT_ACK_DEADLINE = 10;
Copy link
Contributor

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}").

Copy link
Contributor Author

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).

Copy link
Contributor

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.

Copy link
Contributor

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()

Copy link
Contributor Author

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());
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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)
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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}")
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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...

Copy link
Contributor Author

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...

Copy link
Contributor

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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

JavaDocs for public API.

Copy link
Contributor Author

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.
Copy link
Contributor

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 ....

@joaoandremartins joaoandremartins merged commit 7fffaa5 into master Jul 14, 2017
@joaoandremartins joaoandremartins deleted the gh-26-pubsubadmin branch July 14, 2017 18:25
@santhh
Copy link

santhh commented Jan 15, 2018

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]:

@artembilan
Copy link
Contributor

@santhh,

Please, don’t repeat your problem in different places. We got your issue #307 and will come back to you as soon as possible, next week. Right now it is weekend and even I am typing here from the phone.

Thank you for understanding

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants