Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions adapter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
Expand Down Expand Up @@ -61,6 +65,22 @@
<artifactId>mysql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>


<!-- To use the "attached test JAR" from the "model" module -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

/**
* MongoDB document class for a shopping cart line item.
*
* @author Alfredo Rueda & Francisco José Nebrera
*/
@Document(collection = "CartLineItem")
@Getter
@Setter
public class CartLineItemMongoEntity {

@DBRef private ProductMongoEntity product;

private int quantity;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import eu.happycoders.shop.model.cart.Cart;
import eu.happycoders.shop.model.cart.CartLineItem;
import eu.happycoders.shop.model.customer.CustomerId;

/**
* Maps model carts and line items to MongoDB carts and line items - and vice versa.
*
* @author Alfredo Rueda & Francisco José Nebrera
*/
final class CartMapper {

private CartMapper() {}

static CartMongoEntity toMongoEntity(Cart cart) {
CartMongoEntity cartMongoEntity = new CartMongoEntity();
cartMongoEntity.setCustomerId(cart.id().value());

cartMongoEntity.setLineItems(
cart.lineItems().stream()
.map(lineItem -> toMongoEntity(cartMongoEntity, lineItem))
.toList());

return cartMongoEntity;
}

static CartLineItemMongoEntity toMongoEntity(
CartMongoEntity cartMongoEntity, CartLineItem lineItem) {
ProductMongoEntity productMongoEntity = new ProductMongoEntity();
productMongoEntity.setId(lineItem.product().id().value());

CartLineItemMongoEntity entity = new CartLineItemMongoEntity();
entity.setProduct(productMongoEntity);
entity.setQuantity(lineItem.quantity());

return entity;
}

static Cart toModelEntity(CartMongoEntity cartMongoEntity) {
CustomerId customerId = new CustomerId(cartMongoEntity.getCustomerId());
Cart cart = new Cart(customerId);

for (CartLineItemMongoEntity lineItemMongoEntity : cartMongoEntity.getLineItems()) {
cart.putProductIgnoringNotEnoughItemsInStock(
ProductMapper.toModelEntity(lineItemMongoEntity.getProduct()),
lineItemMongoEntity.getQuantity());
}

return cart;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.List;

/**
* MongoDB document class for a shopping cart.
*
* @author Alfredo Rueda & Francisco José Nebrera
*/
@Document(collection = "Cart")
@Getter
@Setter
public class CartMongoEntity {

@Id private int customerId;

private List<CartLineItemMongoEntity> lineItems;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import eu.happycoders.shop.application.port.out.persistence.CartRepository;
import eu.happycoders.shop.model.cart.Cart;
import eu.happycoders.shop.model.customer.CustomerId;
import jakarta.transaction.Transactional;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Repository;

import java.util.Optional;

/**
* Persistence adapter: Stores carts via MongoDB in a database.
* @author Alfredo Rueda & Francisco José Nebrera
*/
@ConditionalOnProperty(name = "persistence", havingValue = "mongodb")
@Repository
public class MongoCartRepository implements CartRepository {

private final MongoCartSpringDataRepository mongoCartSpringDataRepository;

public MongoCartRepository(
MongoCartSpringDataRepository mongoCartSpringDataRepository) {
this.mongoCartSpringDataRepository = mongoCartSpringDataRepository;
}

@Override
@Transactional
public void save(Cart cart) {
mongoCartSpringDataRepository.save(CartMapper.toMongoEntity(cart));
}

@Override
@Transactional
public Optional<Cart> findByCustomerId(CustomerId customerId) {
Optional<CartMongoEntity> cartMongoEntity =
mongoCartSpringDataRepository.findById(customerId.value());
return cartMongoEntity.map(CartMapper::toModelEntity);
}

@Override
@Transactional
public void deleteByCustomerId(CustomerId customerId) {
mongoCartSpringDataRepository.deleteById(customerId.value());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

/**
* Spring Data repository for {@link CartMongoEntity}.
*
* @author Alfredo Rueda & Francisco José Nebrera
*/
@Repository
public interface MongoCartSpringDataRepository extends MongoRepository<CartMongoEntity, Integer> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import eu.happycoders.shop.adapter.out.persistence.DemoProducts;
import eu.happycoders.shop.application.port.out.persistence.ProductRepository;
import eu.happycoders.shop.model.product.Product;
import eu.happycoders.shop.model.product.ProductId;
import jakarta.annotation.PostConstruct;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

/**
* Persistence adapter: Stores products via MongoDB.
*
* @author Alfredo Rueda & Francisco José Nebrera
*/
@ConditionalOnProperty(name = "persistence", havingValue = "mongodb")
@Repository
public class MongoProductRepository implements ProductRepository {

private final MongoProductSpringDataRepository springDataRepository;

public MongoProductRepository(MongoProductSpringDataRepository springDataRepository) {
this.springDataRepository = springDataRepository;
}

@PostConstruct
void createDemoProducts() {
DemoProducts.DEMO_PRODUCTS.forEach(this::save);
}

@Override
@Transactional
public void save(Product product) {
springDataRepository.save(ProductMapper.toMongoEntity(product));
}

@Override
@Transactional
public Optional<Product> findById(ProductId productId) {
Optional<ProductMongoEntity> mongoEntity = springDataRepository.findById(productId.value());
return mongoEntity.map(ProductMapper::toModelEntity);
}

@Override
@Transactional
public List<Product> findByNameOrDescription(String queryString) {
List<ProductMongoEntity> entities =
springDataRepository.findByNameOrDescriptionLike(queryString);

return ProductMapper.toModelEntities(entities);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* Spring Data repository for {@link ProductMongoEntity}.
*
* @author Alfredo Rueda & Francisco José Nebrera
*/
@Repository
public interface MongoProductSpringDataRepository
extends MongoRepository<ProductMongoEntity, String> {

@Query("{ '$or': [ { 'name': { '$regex': ?0, '$options': 'i' } }, { 'description': { '$regex': ?0, '$options': 'i' } } ] }")
List<ProductMongoEntity> findByNameOrDescriptionLike(String pattern);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import eu.happycoders.shop.model.money.Money;
import eu.happycoders.shop.model.product.Product;
import eu.happycoders.shop.model.product.ProductId;

import java.util.Currency;
import java.util.List;

/**
* Maps a model product to a MongoDB product and vice versa.
*
* @author Alfredo Rueda & Francisco José Nebrera
*/
final class ProductMapper {

private ProductMapper() {}

static ProductMongoEntity toMongoEntity(Product product) {
ProductMongoEntity mongoEntity = new ProductMongoEntity();

mongoEntity.setId(product.id().value());
mongoEntity.setName(product.name());
mongoEntity.setDescription(product.description());
mongoEntity.setPriceCurrency(product.price().currency().getCurrencyCode());
mongoEntity.setPriceAmount(product.price().amount());
mongoEntity.setItemsInStock(product.itemsInStock());

return mongoEntity;
}

static Product toModelEntity(ProductMongoEntity mongoEntity) {
return new Product(
new ProductId(mongoEntity.getId()),
mongoEntity.getName(),
mongoEntity.getDescription(),
new Money(
Currency.getInstance(mongoEntity.getPriceCurrency()), mongoEntity.getPriceAmount()),
mongoEntity.getItemsInStock());
}

static List<Product> toModelEntities(List<ProductMongoEntity> mongoEntities) {
return mongoEntities.stream().map(ProductMapper::toModelEntity).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.math.BigDecimal;

/**
* MongoDB document class for a product.
*
* @author Alfredo Rueda & Francisco José Nebrera
*/
@Document(collection = "Product")
@Getter
@Setter
public class ProductMongoEntity {

@Id private String id;

@Field("name")
private String name;

@Field("description")
private String description;

@Field("price_currency")
private String priceCurrency;

@Field("price_amount")
private BigDecimal priceAmount;

@Field("items_in_stock")
private int itemsInStock;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package eu.happycoders.shop.adapter.out.persistence.mongo;

import eu.happycoders.shop.adapter.out.persistence.AbstractCartRepositoryTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@ActiveProfiles("test-with-mongodb")
class MongoDBCartRepositoryTest extends AbstractCartRepositoryTest {

@DynamicPropertySource
static void mongoDbProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", () -> MongoDBTestContainerConfig.getInstance().getReplicaSetUrl());
}
}
Loading