Skip to content

5. 4 -Lab 2 -Stereotypes on CDI

5.1 Step 1: Including Map struct in the project:

At the pomx.xml file, at the dependencies section, add the following dependency:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.6.3</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.6.3</version>
    <scope>provided</scope>
</dependency>

5.2 Step 2: Include the annotations in the project:

To represent easily the DDD concepts, we will use the stereotypes provided by CDI.

At the package com.example.ecommerce.annotations we will create the following classes:

@Stereotype
@ApplicationScoped
@Inherited
@Retention(RUNTIME)
@Target(ElementType.TYPE)
public @interface Repository {
}
@Stereotype
@ApplicationScoped
@Inherited
@Retention(RUNTIME)
@Target(ElementType.TYPE)
public @interface DomainService {
}
@Stereotype
@ApplicationScoped
@Inherited
@Retention(RUNTIME)
@Target(ElementType.TYPE)
public @interface ApplicationService {
}

5.3 Step 3: Define the Domain classes:

At the package com.example.ecommerce.domain create the following classes:

public record Money(String currency, BigDecimal amount) {
}
package com.example.ecommerce.domain;


import java.util.Objects;

public class Product {

    private String id;
    private String name;
    private Money price;


    Product(String id, String name, Money price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Money getPrice() {
        return price;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Product product = (Product) o;
        return Objects.equals(id, product.id);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(id);
    }

    @Override
    public String toString() {
        return "Product{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }

    public static ProductBuilder builder() {
        return new ProductBuilder();
    }
}
package com.example.ecommerce.domain;

public class ProductBuilder {
    private String id;
    private String name;
    private Money price;

    public ProductBuilder id(String id) {
        this.id = id;
        return this;
    }

    public ProductBuilder name(String name) {
        this.name = name;
        return this;
    }

    public ProductBuilder price(Money price) {
        this.price = price;
        return this;
    }

    public Product build() {
        return new Product(id, name, price);
    }
}
public interface ProductRepository {

    Product save(Product product);
    Optional<Product> findById(String id);
    List<Product> findAll();
    void deleteById(String id);

}
@ApplicationService
public class ProductService {

    private final ProductRepository repository;

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

    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();
    }
}
package com.example.ecommerce.infrastructure.persistence;

import com.example.ecommerce.annotations.Repository;
import com.example.ecommerce.domain.Product;
import com.example.ecommerce.domain.ProductRepository;
import java.util.*;

@Repository
public class InMemoryProductRepository implements ProductRepository {

    private final Map<String, Product> store = new HashMap<>();

    @Override
    public Product save(Product product) {
        store.put(product.getId(), product);
        return product;
    }

    @Override
    public Optional<Product> findById(String id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public List<Product> findAll() {
        return new ArrayList<>(store.values());
    }

    @Override
    public void deleteById(String id) {
        store.remove(id);
    }
}

5.4 Step 4: Define the presentation classes:

At the package com.example.ecommerce.presentation create the following classes:

import java.math.BigDecimal;

public record ProductRequest(String name, String currency, BigDecimal amount) {
}
import java.math.BigDecimal;

public record ProductResponse(String id, String name, String currency, BigDecimal amount) {
}
import com.example.ecommerce.domain.Product;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

import java.util.List;

@Mapper(componentModel = "cdi")
public interface ProductMapper {

    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID().toString())")
    @Mapping(target = "price.currency", source = "dto.currency")
    @Mapping(target = "price.amount", source = "dto.amount")
    Product toDomain(ProductRequest dto);

    @Mapping(target = "currency", source = "price.currency")
    @Mapping(target = "amount", source = "price.amount")
    ProductResponse toResponse(Product product);

    List<ProductResponse> toResponseList(List<Product> products);
}
package com.example.ecommerce.presentation;


import com.example.ecommerce.domain.Product;
import com.example.ecommerce.domain.ProductService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;

import java.util.List;

@ApplicationScoped
@Path("/products")
public class ProductResource {

    private final ProductMapper mapper;

    private final ProductService service;

    @Inject
    public ProductResource(ProductMapper mapper, ProductService service) {
        this.mapper = mapper;
        this.service = service;
    }


    @GET
    public List<ProductResponse> getAllProducts() {
        return service.findAll().stream()
                .map(mapper::toResponse)
                .toList();
    }

    @GET
    @Path("/{id}")
    public ProductResponse getProductById(@PathParam("id") String id) {
        return service.findById(id).map(this.mapper::toResponse).orElseThrow(
                () -> new WebApplicationException("Product with id " + id + " not found",
                        Response.Status.NOT_FOUND));
    }


    @DELETE
    @Path("/{id}")
    public void deleteById(@PathParam("id") String id) {
        service.deleteById(id);
    }

    @POST
    public ProductResponse insert(ProductRequest request) {
        Product product = service.save(mapper.toDomain(request));
        return mapper.toResponse(product);
    }

}

5.5 Step 5: Run the application:

mvn package
java -jar target/ecommerce.jar

5.6 Step 6: Test the application:

curl --location 'http://localhost:8181/products' \
--header 'Content-Type: application/json' \
--data '{"name": "Pen", "currency": "USD", "amount": 10}'
curl --location 'http://localhost:8181/products'
curl --location 'http://localhost:8181/products'

Last update: 2025-09-05