You could do it without declaring the @Bean
inside NotificationPublisherConfiguration
, but be aware that this makes NotificationService
quite impossible to test because you can't mock the injected beans, you can't use @Value
properties and also each call to publishMessage
will create a new Publisher
but I think this can be solved somehow for example using a static method in a bean as stated here(if this is feasible you can also use injection and @Value
and you will also be able to test NotificationService
Mocking the static methods that will return the beans but I’ve not tryied) or maybe better with a sort of factory that produces singletons (also here you can mock the static methods an so you will be able to test)
public enum SubscriptionType {
WEBSOCKET {
@Override
protected Publisher getPublisher() throws Exception {
return Publisher.newBuilder(
ProjectTopicName.newBuilder()
.setProject("you must hardcode the string")
.setTopic("you must hardcode the string")
.build()
).build();
}
},
GRPC {
@Override
protected Publisher getPublisher() throws Exception {
return Publisher.newBuilder(
ProjectTopicName.newBuilder()
.setProject("you must hardcode the string")
.setTopic("you must hardcode the string")
.build()
).build();
};
protected abstract Publisher getPublisher() throws Exception;
public void publishMessage(String eventBody) {
PubsubMessage pubsubMessage = PubsubMessage.newBuilder()
.setData(eventBody)
.build();
ApiFuture<String> publish;
try {
publish = getPublisher().publish(pubsubMessage);
log.debug("Message published: {}, on {}", pubsubMessage, name());
} catch (Exception e) {}
}
}
and your NotificationService
becomes:
public class NotificationService {
public void post(Map<SubscriptionType, Set<String>, String eventBody> subscriptionIdsByProtocol) throws Exception {
for (Map.Entry<SubscriptionType, Set<String>> entry : subscriptionIdsByProtocol.entrySet()) {
entry.getKey().publishMessage(eventBody);
}
}
You can also write the enum like this it solves the singleton problem but makes the code untestable:
public enum SubscriptionType {
WEBSOCKET(Publisher.newBuilder(
ProjectTopicName.newBuilder()
.setProject("you must hardcode the string")
.setTopic("you must hardcode the string")
.build()),
GRPC(Publisher.newBuilder(
ProjectTopicName.newBuilder()
.setProject("you must hardcode the string")
.setTopic("you must hardcode the string")
.build());
private Publisher publisher;
private SubscriptionType(Publisher publisher) {
this.publisher = publisher;
}
public void publishMessage(String eventBody) {
PubsubMessage pubsubMessage = PubsubMessage.newBuilder()
.setData(eventBody)
.build();
ApiFuture<String> publish;
try {
publish = publisher.publish(pubsubMessage);
log.debug("Message published: {}, on {}", pubsubMessage, name());
} catch (Exception e) {}
}
}
I still prefer a factory, that makes the code testble.
Otherwise you can try to mock Publisher.newBuilder
and this will solve all the testing problems but you still can’t use @Value
Edit: I've had the time to play a little bit with it this is the solution i come with:
- you can have the configuration class:
@Configuration
public class NotificationPublisherConfiguration {
@Bean(name="websocketPublisher")
public Publisher websocketPublisher(@Value("${gcp.projectId}") String gcpProjectId, @Value("${gcp.pubsub.notificationWebsocket}") String topicId) throws Exception {
return Publisher.newBuilder(
ProjectTopicName.newBuilder()
.setProject(gcpProjectId)
.setTopic(topicId)
.build()
).build();
}
@Bean(name="grpcPublisher")
public Publisher grpcPublisher(@Value("${gcp.projectId}") String gcpProjectId, @Value("${gcp.pubsub.notificationGrpc}") String topicId) throws Exception {
return Publisher.newBuilder(
ProjectTopicName.newBuilder()
.setProject(gcpProjectId)
.setTopic(topicId)
.build()
).build();
}
}
- We will use a component as factory:
@Component
public class PublisherFactory implements InitializingBean {
private static final HashMap<SubscriptionType, Publisher> map = new HashMap<>();
@Autowired
@Qualifier("websocketPublisher")
private Publisher webSocketPublisher;
@Autowired
@Qualifier("grpcPublisher")
private Publisher grpcPublisher;
@Override
public void afterPropertiesSet() throws Exception {
map.map(SubscriptionType.WEBSOCKET, webSocketPublisher);
map.put(SubscriptionType.GRPC, grpcPublisher);
}
public static Publisher getPublisher(SubscriptionType protocol) {
return map.get(protocol);
}
}
- the enum will look like this:
public enum SubscriptionType {
WEBSOCKET {
@Override
protected String getPublisher() {
return PublisherFactory.getPublisher(this);
}
},
GRPC {
@Override
protected String getPublisher() {
return PublisherFactory.getPublisher(this);
}
};
protected abstract String getPublisher();
public void publishMessage(String eventBody) {
PubsubMessage pubsubMessage = PubsubMessage.newBuilder()
.setData(eventBody)
.build();
ApiFuture<String> publish;
try {
publish = getPublisher().publish(pubsubMessage);
log.debug("Message published: {}, on {}", pubsubMessage, name());
} catch (Exception e) {}
}
}
- The notification service:
public class NotificationService {
public void post(Map<SubscriptionType, Set<String>, String eventBody> subscriptionIdsByProtocol) throws Exception {
for (Map.Entry<SubscriptionType, Set<String>> entry : subscriptionIdsByProtocol.entrySet()) {
entry.getKey().publishMessage(eventBody);
}
}
Whit this you can test and use injection and @Value