CDI (Part 3): Events and Observers for Low Coupling and High Cohesion

综合编程 DZone

Hello!

This is the Part 3
of the CDI Series in Java
that contains:

  • Part 1: Factory in CDI with @Produces annotation
  • Part 2: CDI Qualifiers in Java: Polymorphism in DI

  • Part 3
    : CDI Events and Observers for Low Coupling and High Cohesion
  • Part 4
    : CDI and Lazy Initialization
  • Part 5
    : Interceptors in CDI
  • Part 6
    : CDI Dependency Injection and Alternatives
  • Part 7
    : Working with CDI Decorators

In this part of the CDI Series
, we’ll explore Events and Observers
used in CDI to have code with Low Coupling
and High Cohesion
.

Let’s contextualize our application problem.

  • We must have a Checkout class
  • We must send an email when a checkout is finished
  • We must send a metric when a checkout is finished

Let’s code!

Step 1: Creating the Entire Scenario

This first class will use CDI 2.0, which allows us to use CDI in a standalone mode:

public class MainApplication {

    public static void main(String[] args) {
        try (CDI container = new Weld().initialize()) {
            Checkout checkout = container.select(Checkout.class).get();

            Buyer buyer = new Buyer("[email protected]");
            Order order = new Order(buyer, 10L, 80.0);

            checkout.finishCheckout(order);
        }
    }

}

This code won’t compile, since we don’t have the Order
class yet.

Next, we have this Order
class with a few fields:

public class Order {

    private double price;
    private Long id;
    private Buyer buyer;

    public Order(Buyer buyer, Long id, double price) {
        this.buyer = buyer;
        this.id = id;
        this.price = price;
    }

    public double getPrice() {
        return price;
    }

    public Long getId() {
        return id;
    }

    public Buyer getBuyer() {
        return buyer;
    }

}

As you can see, an Order
has a Buyer
. Let’s create this class:

public class Buyer {

    private String email;

    public Buyer(String email) {
        this.email = email;
    }

    public String getEmail() {
        return email;
    }

}

Great!

It’s time to create the class that sends metrics
. This is good cohesion, since this class is responsible only for sending metrics:

public class MetricCreator {

    public void createMetricFor(Order order) {
        System.out.println("Creating new Metric for OrderId: " + order.getId());
    }

}

Following the same thought, we’ll create the EmailSender
class:

public class EmailSender {

    public void sendEmailFor(Buyer buyer) {
        System.out.println("Sending email to: " + buyer.getEmail());
    }

}

Finally, it’s time to create the Checkout
class. In finishCheckout()
, we’re receiving an Order as a parameter and, with this object, we send an email
and metrics
:

public class Checkout {

    @Inject
    private EmailSender emailSender;

    @Inject
    private MetricCreator metrics;

    public void finishCheckout(Order order) {
        System.out.println("Finishing Checkout with Id: " + order.getId());

        emailSender.sendEmailFor(order.getBuyer());

        metrics.createMetricFor(order);
    }

}

Nice!

So far, we have good cohesion and low coupling because the Checkout
class just knows the methods to send email
and metrics
but it doesn’t know how
.

But what if we need to do more? For example:

  • Send a push notification
  • Send Kafka messages for other systems
  • Send events to Amazon SQS for third-party integrations

Can we follow the same pattern as above? Can we just inject the responsible class in the Checkout
class?

public class Checkout {

    @Inject
    private EmailSender emailSender;

    @Inject
    private MetricCreator metrics;

    @Inject
    private PushNotification pushNotification;    

    @Inject
    private KafkaMessager kafka;    

    @Inject
    private AmazonSqs amazonSqs;    

    public void finishCheckout(Order order) {
        System.out.println("Finishing Checkout with Id: " + order.getId());

        emailSender.sendEmailFor(order.getBuyer());

        metrics.createMetricFor(order);

        pushNotification.send(order);

        kafka.send(order.getId(), order.getBuyer());

        amazonSqs.send(order);
    }

}

As you can see, we can quickly fall into a bunch of injections that don’t matter to the Checkout itself.

Every timewe need to use the Order in Checkout, we should open
the Checkout class to add an @Inject annotation with the new object.

To follow good cohesion, in this case, I’d like to just see code related to a checkout being finished.

Let’s refactor this code with more CDI!

Step 2: Refactoring to Use CDI Events and Observers

Do you remember the Observer Pattern
? So, we will use that to notify that one action has happened
.

We’ll create a class called CheckoutEvent
that will indicate the checkout event.

public class CheckoutEvent {

    private Order order;

    public CheckoutEvent(Order order) {
        this.order = order;
    }

    public Order getOrder() {
        return order;
    }

}

Now it’s time to get help from CDI. We must use the Event Interface
to notify all subscribers
interested in the checkout event:

public class Checkout {

    @Inject
    private Event event;

    public void finishCheckout(Order order) {
        System.out.println("Finishing Checkout with Id: " + order.getId());

        event.fire(new CheckoutEvent(order));
    }

}

As you can see, we’ve used the method fire()
to trigger
the action to notify
all subscribers in the event

Step 3: Observing the CDI Event

When a Checkout action is taken, we would like to have two actions executed:

  • Send an email
  • Create a metric

We should indicate that the EmailSender
class is interested in the checkout event. We can do that by using the @Observes
annotation from CDI, as you can see below:

public class EmailSender {

    public void sendEmailFor(@Observes CheckoutEvent event) {
        Buyer buyer = event.getOrder().getBuyer();

        System.out.println("Sending email to: " + buyer.getEmail());
    }

}

Of course, we can subscribe
more interested classes, as we will see with the MetricCreator
class below:

public class EmailSender {

    public void sendEmailFor(@Observes CheckoutEvent event) {
        Buyer buyer = event.getOrder().getBuyer();

        System.out.println("Sending email to: " + buyer.getEmail());
    }

}

Excellent! Notice that your Checkout
class doesn’t
know about the EmailSender
and MetricCreator
classes. The finishCheckout()
method is just worried about its logic and will notify everyone who is interested in this event, but without knowing them
. Pretty cool!

That’s it! I hope that this part of the CDI series
is useful to you!

In the next post we’re going to explore Lazy Initialization in CDI: Part 4: CDI and Lazy Initialization

Thanks!

DZone稿源:DZone (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合编程 » CDI (Part 3): Events and Observers for Low Coupling and High Cohesion

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录