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:
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:
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}'
Last update:
2025-09-05