Skip to content

6. 5 -Lab 3 -Events on CDI

In this event lab we will learn how to use DDD events exploring CDI events.

6.1 Step 1: Create the event:

import java.math.BigDecimal;

public record ProductPriceChanged(String productId, BigDecimal oldPrice, BigDecimal newPrice) {
}

6.2 Step 2: Create Update Product price domain event:

package com.example.ecommerce.domain;

import com.example.ecommerce.annotations.DomainService;
import com.example.ecommerce.domain.events.ProductPriceChanged;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;

import java.math.BigDecimal;
import java.util.Objects;

@DomainService
public class UpdateProductPrice {

    private final ProductRepository repository;

    private final Event<ProductPriceChanged> productPriceChangedEvent;


    @Inject
    public UpdateProductPrice(ProductRepository repository, Event<ProductPriceChanged> productPriceChangedEvent) {
        this.repository = repository;
        this.productPriceChangedEvent = productPriceChangedEvent;
    }

    public void execute(Product product, BigDecimal price) {
        Objects.requireNonNull(product, "Product must not be null.");
        Objects.requireNonNull(price, "Price must not be null.");
        if (price.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Price must be > 0.");
        }
        var event = new ProductPriceChanged(product.getId(), product.getPrice().amount(), price);
        this.productPriceChangedEvent.fire(event);
        product.update(price);
        this.repository.save(product);
    }
}

Update the Application class to call the new UpdateProductPrice service, thus update the class ProductService:

import java.math.BigDecimal;

public record UpdatePriceRequest(BigDecimal price) {
}
package com.example.ecommerce.domain;

import com.example.ecommerce.annotations.ApplicationService;
import jakarta.inject.Inject;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

@ApplicationService
public class ProductService {

    private final ProductRepository repository;

    private final UpdateProductPrice updateProductPrice;

    @Inject
    public ProductService(ProductRepository repository, UpdateProductPrice updateProductPrice) {
        this.repository = repository;
        this.updateProductPrice = updateProductPrice;
    }

    public Product save(Product product) {
        return repository.save(product);
    }

    public void deleteById(String id) {
        repository.deleteById(id);
    }

    public Optional<Product> findById(String id) {
        return repository.findById(id);
    }

    public List<Product> findAll() {
        return repository.findAll();
    }

    public void changePrice(String id, BigDecimal price) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Product with id " + id + " not found"));
        //we could improve this exception as well.
        this.updateProductPrice.execute(product, price);
    }
}

Update the Resource class to call the new method:

@PATCH
@Path("{id}/price")
public void changePrice(@PathParam("id") String id, UpdatePriceRequest body) {
    service.changePrice(id, body.price());
}

On the Product class also include the update method:

  public void update(BigDecimal amount) {
    this.price = new Money(price.currency(), amount);
}

Create a new class to listen to the event:

package com.example.ecommerce.marketing;


import com.example.ecommerce.domain.events.ProductPriceChanged;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

import java.math.BigDecimal;

@ApplicationScoped
public class SocialMediaPriceChangePublisher {

    public void onPriceChange(@Observes ProductPriceChanged event) {
        BigDecimal oldP = event.oldPrice();
        BigDecimal newP = event.newPrice();

        // demo behavior: print only when the price dropped
        if (newP.compareTo(oldP) < 0) {
            System.out.println("[SOCIAL] 🎉 Promo! Product " + event.productId()
                    + " dropped from " + oldP + " to " + newP + ". #Sale #Deal");
        } else {
            // keep it simple for the lab—no op on increases
            System.out.println("[SOCIAL] (ignored) price increased for " + event.productId()
                    + " from " + oldP + " to " + newP);
        }
    }
}

6.3 Step 3: Execute the application and test the new endpoint:

mvn package
java -jar target/ecommerce.jar

6.4 Step 4: Test the new endpoint:

curl --location 'http://localhost:8181/products' \
--header 'Content-Type: application/json' \
--data '{"name": "Pen", "currency": "USD", "amount": 10}'
curl --location --request PATCH 'http://localhost:8181/products/61c848a9-3bb8-4668-89c1-227cc6a4e049/price' \
--header 'Content-Type: application/json' \
--data '{"price": 6}'

Last update: 2025-09-05