Home > Net >  Choose dependency object based on Enum
Choose dependency object based on Enum

Time:08-18

My service needs to put a message on a PubSub based on the Enum of Protocol in the message.

These are the PubSub Config

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

Now in my service class, I have set it up below.

public class NotificationService {


private final Publisher websocketPublisher;
private final Publisher grpcPublisher;



public void post(Map<SubscriptionType, Set<String>, String eventBody> subscriptionIdsByProtocol) throws Exception {

    for (Map.Entry<SubscriptionType, Set<String>> entry : subscriptionIdsByProtocol.entrySet()) {

        if (entry.getKey().equals(SubscriptionType.WEBSOCKET)) {
            publishMessage (eventBody, websocketPublisher, entry.getKey());
        } else if (entry.getKey().equals(SubscriptionType.GRPC)) {
            publishMessage(eventBody, grpcPublisher, entry.getKey());
        }
    }
}


private void publishMessage(String eventBody, Publisher publisher, SubscriptionType subscriptionType) {


    PubsubMessage pubsubMessage = PubsubMessage.newBuilder()
            .setData(eventBody)
            .build();

    ApiFuture<String> publish;

    try {
        publish = publisher.publish(pubsubMessage);
        log.debug("Message published: {}, on {}", pubsubMessage, subscriptionType.toString());
    } catch (Exception e) {}
  }
}

I am pretty sure there is a better way to do this so that I don't need to change a lot of code when a new protocol is introduced, and we need to put the message on a new PubSub as well. Can someone suggest what design pattern I can use here?

Thanks

CodePudding user response:

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

CodePudding user response:

You can use Strategy pattern to select a desired algorithm at runtime. However, it is necessary to store these algorithms somewhere. This is a place where Factory pattern can be used.

So let me show an example. I am sorry, I do not know Java, but let me show an example via C#. Code does not use special features of language, so you can apply it to Java.

So let's create an abstract class that will define common behaviour for all Publishers:

public abstract class Publisher
{ 
    public abstract void Publish();
}

And its concrete implementations WebSocketPublisher and GrpcPublisher :

public class WebSocketPublisher : Publisher
{
    public override void Publish()
    {
        Console.WriteLine("Message published through WebSocket.");
    }
}

public class GrpcPublisher : Publisher
{
    public override void Publish()
    {
        Console.WriteLine($"Message published through Grpc.");
    }
}

Then we need a place to store these strategies and take it when it is necessary. This is a place where Factory pattern can be used:

public enum SubscriptionType 
{
    WebSocket, Grpc
}


public class PublisherFactory
{
    private Dictionary<SubscriptionType, Publisher> _pubisherByType = 
        new Dictionary<SubscriptionType, Publisher>()
    {
        { SubscriptionType.WebSocket, new  WebSocketPublisher () },
        { SubscriptionType.Grpc, new GrpcPublisher () },
    };

    public Publisher GetInstanceByType(SubscriptionType courtType) => 
        _pubisherByType[courtType];
}

And then you can call the above code like this:

PublisherFactory courtFactory = new PublisherFactory();
Publisher publisher = courtFactory.GetInstanceByType(SubscriptionType.WebSocket);
publisher.Publish(); // OUTPUT: "Message published through WebSocket."

So, here we've applied open closed principle. I mean you will add new functionality by adding new classes that will be derived from Court class.

  • Related