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

存储架构 2018-02-14

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("welcome@hackingcode.com");
            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!

您可能感兴趣的

httpfetch-一款java语言编写优雅的http接口调用组件... 当我们提到java调用http请求时,我们想到的是HttpClient或是内置的HttpUrlConnention。 然后会写下如下一串的代码访问http接口: HttpClient client = new HttpClient(); client.getHostCo...
Java反序列化漏洞学习实践三:理解Java的动态代理机制... 0x01 基础 代理模式:为其他对象提供一种代理以便控制对这个对象的访问(所谓控制,就是可以在其调用行为,前后分别加入一些操作)。 代理模式分类: 静态代理(其实质是类的继承,比较容易理解) 动态代理(这是我们需要关注的重点,)比较重要!! 0x02 静态代理De...
Java动态代理 我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂...
Go does not inline functions when it should I have designed a benchmark that I run in different programming languages. Two languages that I like are Go (from Google) and Java (from Oracle). My e...
java利用WatchService实时监控某个目录下的文件变化并按行解析(注:附源代码)... 首先说下需求:通过ftp上传约定格式的文件到服务器指定目录下,应用程序能实时监控该目录下文件变化,如果上传的文件格式符合要求,将将按照每一行读取解析再写入到数据库,解析完之后再将文件改名。 一. 一开始的思路 设置一个定时任务,每隔一分钟读取下指定目录下的文件变化,如果有满足格式...