feature: Added Domain Model pattern (#1795)
* domain-model pattern * fixed optional get before check isPresent * readme minor changes * change currency representation with joda money * changed names of test methods * fixed code smells * Update domain-model/README.md Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com> * Update domain-model/README.md Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com> * updated readme and diagrams Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
This commit is contained in:
173
domain-model/src/main/java/com/iluwatar/domainmodel/App.java
Normal file
173
domain-model/src/main/java/com/iluwatar/domainmodel/App.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.joda.money.Money;
|
||||
|
||||
|
||||
/**
|
||||
* Domain Model pattern is a more complex solution for organizing domain logic than Transaction
|
||||
* Script and Table Module. It provides an object-oriented way of dealing with complicated logic.
|
||||
* Instead of having one procedure that handles all business logic for a user action like
|
||||
* Transaction Script there are multiple objects and each of them handles a slice of domain logic
|
||||
* that is relevant to it. The significant difference between Domain Model and Table Module pattern
|
||||
* is that in Table Module a single class encapsulates all the domain logic for all records stored
|
||||
* in table when in Domain Model every single class represents only one record in underlying table.
|
||||
*
|
||||
* <p>In this example, we will use the Domain Model pattern to implement buying of products
|
||||
* by customers in a Shop. The main method will create a customer and a few products.
|
||||
* Customer will do a few purchases, try to buy product which are too expensive for him,
|
||||
* return product which he bought to return money.</p>
|
||||
*/
|
||||
public class App {
|
||||
|
||||
public static final String H2_DB_URL = "jdbc:h2:~/test";
|
||||
|
||||
public static final String CREATE_SCHEMA_SQL =
|
||||
"CREATE TABLE CUSTOMERS (name varchar primary key, money decimal);"
|
||||
+ "CREATE TABLE PRODUCTS (name varchar primary key, price decimal, expiration_date date);"
|
||||
+ "CREATE TABLE PURCHASES ("
|
||||
+ "product_name varchar references PRODUCTS(name),"
|
||||
+ "customer_name varchar references CUSTOMERS(name));";
|
||||
|
||||
public static final String DELETE_SCHEMA_SQL =
|
||||
"DROP TABLE CUSTOMERS IF EXISTS;"
|
||||
+ "DROP TABLE PURCHASES IF EXISTS;"
|
||||
+ "DROP TABLE PRODUCTS IF EXISTS;";
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line arguments
|
||||
* @throws Exception if any error occurs
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
// Create data source and create the customers, products and purchases tables
|
||||
final var dataSource = createDataSource();
|
||||
deleteSchema(dataSource);
|
||||
createSchema(dataSource);
|
||||
|
||||
// create customer
|
||||
var customerDao = new CustomerDaoImpl(dataSource);
|
||||
|
||||
var tom =
|
||||
Customer.builder()
|
||||
.name("Tom")
|
||||
.money(Money.of(USD, 30))
|
||||
.customerDao(customerDao)
|
||||
.build();
|
||||
|
||||
tom.save();
|
||||
|
||||
// create products
|
||||
var productDao = new ProductDaoImpl(dataSource);
|
||||
|
||||
var eggs =
|
||||
Product.builder()
|
||||
.name("Eggs")
|
||||
.price(Money.of(USD, 10.0))
|
||||
.expirationDate(LocalDate.now().plusDays(7))
|
||||
.productDao(productDao)
|
||||
.build();
|
||||
|
||||
var butter =
|
||||
Product.builder()
|
||||
.name("Butter")
|
||||
.price(Money.of(USD, 20.00))
|
||||
.expirationDate(LocalDate.now().plusDays(9))
|
||||
.productDao(productDao)
|
||||
.build();
|
||||
|
||||
var cheese =
|
||||
Product.builder()
|
||||
.name("Cheese")
|
||||
.price(Money.of(USD, 25.0))
|
||||
.expirationDate(LocalDate.now().plusDays(2))
|
||||
.productDao(productDao)
|
||||
.build();
|
||||
|
||||
eggs.save();
|
||||
butter.save();
|
||||
cheese.save();
|
||||
|
||||
// show money balance of customer after each purchase
|
||||
tom.showBalance();
|
||||
tom.showPurchases();
|
||||
|
||||
// buy eggs
|
||||
tom.buyProduct(eggs);
|
||||
tom.showBalance();
|
||||
|
||||
// buy butter
|
||||
tom.buyProduct(butter);
|
||||
tom.showBalance();
|
||||
|
||||
// trying to buy cheese, but receive a refusal
|
||||
// because he didn't have enough money
|
||||
tom.buyProduct(cheese);
|
||||
tom.showBalance();
|
||||
|
||||
// return butter and get money back
|
||||
tom.returnProduct(butter);
|
||||
tom.showBalance();
|
||||
|
||||
// Tom can buy cheese now because he has enough money
|
||||
// and there is a discount on cheese because it expires in 2 days
|
||||
tom.buyProduct(cheese);
|
||||
|
||||
tom.save();
|
||||
|
||||
// show money balance and purchases after shopping
|
||||
tom.showBalance();
|
||||
tom.showPurchases();
|
||||
}
|
||||
|
||||
private static DataSource createDataSource() {
|
||||
var dataSource = new JdbcDataSource();
|
||||
dataSource.setUrl(H2_DB_URL);
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private static void deleteSchema(DataSource dataSource) throws SQLException {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(DELETE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createSchema(DataSource dataSource) throws SQLException {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(CREATE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.joda.money.Money;
|
||||
|
||||
/**
|
||||
* This class organizes domain logic of customer.
|
||||
* A single instance of this class
|
||||
* contains both the data and behavior of a single customer.
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class Customer {
|
||||
|
||||
@NonNull private final CustomerDao customerDao;
|
||||
@Builder.Default private List<Product> purchases = new ArrayList<>();
|
||||
@NonNull private String name;
|
||||
@NonNull private Money money;
|
||||
|
||||
/**
|
||||
* Save customer or update if customer already exist.
|
||||
*/
|
||||
public void save() {
|
||||
try {
|
||||
Optional<Customer> customer = customerDao.findByName(name);
|
||||
if (customer.isPresent()) {
|
||||
customerDao.update(this);
|
||||
} else {
|
||||
customerDao.save(this);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
LOGGER.error(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add product to purchases, save to db and withdraw money.
|
||||
*
|
||||
* @param product to buy.
|
||||
*/
|
||||
public void buyProduct(Product product) {
|
||||
LOGGER.info(
|
||||
String.format(
|
||||
"%s want to buy %s($%.2f)...",
|
||||
name, product.getName(), product.getSalePrice().getAmount()));
|
||||
try {
|
||||
withdraw(product.getSalePrice());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
LOGGER.error(ex.getMessage());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
customerDao.addProduct(product, this);
|
||||
purchases.add(product);
|
||||
LOGGER.info(String.format("%s bought %s!", name, product.getName()));
|
||||
} catch (SQLException exception) {
|
||||
receiveMoney(product.getSalePrice());
|
||||
LOGGER.error(exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove product from purchases, delete from db and return money.
|
||||
*
|
||||
* @param product to return.
|
||||
*/
|
||||
public void returnProduct(Product product) {
|
||||
LOGGER.info(
|
||||
String.format(
|
||||
"%s want to return %s($%.2f)...",
|
||||
name, product.getName(), product.getSalePrice().getAmount()));
|
||||
if (purchases.contains(product)) {
|
||||
try {
|
||||
customerDao.deleteProduct(product, this);
|
||||
purchases.remove(product);
|
||||
receiveMoney(product.getSalePrice());
|
||||
LOGGER.info(String.format("%s returned %s!", name, product.getName()));
|
||||
} catch (SQLException ex) {
|
||||
LOGGER.error(ex.getMessage());
|
||||
}
|
||||
} else {
|
||||
LOGGER.error(String.format("%s didn't buy %s...", name, product.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print customer's purchases.
|
||||
*/
|
||||
public void showPurchases() {
|
||||
Optional<String> purchasesToShow =
|
||||
purchases.stream()
|
||||
.map(p -> p.getName() + " - $" + p.getSalePrice().getAmount())
|
||||
.reduce((p1, p2) -> p1 + ", " + p2);
|
||||
|
||||
if (purchasesToShow.isPresent()) {
|
||||
LOGGER.info(name + " bought: " + purchasesToShow.get());
|
||||
} else {
|
||||
LOGGER.info(name + " didn't bought anything");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print customer's money balance.
|
||||
*/
|
||||
public void showBalance() {
|
||||
LOGGER.info(name + " balance: " + money);
|
||||
}
|
||||
|
||||
private void withdraw(Money amount) throws IllegalArgumentException {
|
||||
if (money.compareTo(amount) < 0) {
|
||||
throw new IllegalArgumentException("Not enough money!");
|
||||
}
|
||||
money = money.minus(amount);
|
||||
}
|
||||
|
||||
private void receiveMoney(Money amount) {
|
||||
money = money.plus(amount);
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CustomerDao {
|
||||
|
||||
Optional<Customer> findByName(String name) throws SQLException;
|
||||
|
||||
void update(Customer customer) throws SQLException;
|
||||
|
||||
void save(Customer customer) throws SQLException;
|
||||
|
||||
void addProduct(Product product, Customer customer) throws SQLException;
|
||||
|
||||
void deleteProduct(Product product, Customer customer) throws SQLException;
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.joda.money.Money;
|
||||
|
||||
public class CustomerDaoImpl implements CustomerDao {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
public CustomerDaoImpl(final DataSource userDataSource) {
|
||||
this.dataSource = userDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Customer> findByName(String name) throws SQLException {
|
||||
var sql = "select * from CUSTOMERS where name = ?;";
|
||||
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement = connection.prepareStatement(sql)) {
|
||||
preparedStatement.setString(1, name);
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
return Optional.of(
|
||||
Customer.builder()
|
||||
.name(rs.getString("name"))
|
||||
.money(Money.of(USD, rs.getBigDecimal("money")))
|
||||
.customerDao(this)
|
||||
.build());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Customer customer) throws SQLException {
|
||||
var sql = "update CUSTOMERS set money = ? where name = ?;";
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement = connection.prepareStatement(sql)) {
|
||||
preparedStatement.setBigDecimal(1, customer.getMoney().getAmount());
|
||||
preparedStatement.setString(2, customer.getName());
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Customer customer) throws SQLException {
|
||||
var sql = "insert into CUSTOMERS (name, money) values (?, ?)";
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement = connection.prepareStatement(sql)) {
|
||||
preparedStatement.setString(1, customer.getName());
|
||||
preparedStatement.setBigDecimal(2, customer.getMoney().getAmount());
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProduct(Product product, Customer customer) throws SQLException {
|
||||
var sql = "insert into PURCHASES (product_name, customer_name) values (?,?)";
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement = connection.prepareStatement(sql)) {
|
||||
preparedStatement.setString(1, product.getName());
|
||||
preparedStatement.setString(2, customer.getName());
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProduct(Product product, Customer customer) throws SQLException {
|
||||
var sql = "delete from PURCHASES where product_name = ? and customer_name = ?";
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement = connection.prepareStatement(sql)) {
|
||||
preparedStatement.setString(1, product.getName());
|
||||
preparedStatement.setString(2, customer.getName());
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.joda.money.Money;
|
||||
|
||||
/**
|
||||
* This class organizes domain logic of product.
|
||||
* A single instance of this class
|
||||
* contains both the data and behavior of a single product.
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class Product {
|
||||
|
||||
private static final int DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE = 4;
|
||||
private static final double DISCOUNT_RATE = 0.2;
|
||||
|
||||
@NonNull private final ProductDao productDao;
|
||||
@NonNull private String name;
|
||||
@NonNull private Money price;
|
||||
@NonNull private LocalDate expirationDate;
|
||||
|
||||
/**
|
||||
* Save product or update if product already exist.
|
||||
*/
|
||||
public void save() {
|
||||
try {
|
||||
Optional<Product> product = productDao.findByName(name);
|
||||
if (product.isPresent()) {
|
||||
productDao.update(this);
|
||||
} else {
|
||||
productDao.save(this);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
LOGGER.error(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate sale price of product with discount.
|
||||
*/
|
||||
public Money getSalePrice() {
|
||||
return price.minus(calculateDiscount());
|
||||
}
|
||||
|
||||
private Money calculateDiscount() {
|
||||
if (ChronoUnit.DAYS.between(LocalDate.now(), expirationDate)
|
||||
< DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE) {
|
||||
|
||||
return price.multipliedBy(DISCOUNT_RATE, RoundingMode.DOWN);
|
||||
}
|
||||
|
||||
return Money.zero(USD);
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProductDao {
|
||||
|
||||
Optional<Product> findByName(String name) throws SQLException;
|
||||
|
||||
void save(Product product) throws SQLException;
|
||||
|
||||
void update(Product product) throws SQLException;
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.joda.money.Money;
|
||||
|
||||
|
||||
public class ProductDaoImpl implements ProductDao {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
public ProductDaoImpl(final DataSource userDataSource) {
|
||||
this.dataSource = userDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Product> findByName(String name) throws SQLException {
|
||||
var sql = "select * from PRODUCTS where name = ?;";
|
||||
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement = connection.prepareStatement(sql)) {
|
||||
preparedStatement.setString(1, name);
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
return Optional.of(
|
||||
Product.builder()
|
||||
.name(rs.getString("name"))
|
||||
.price(Money.of(USD, rs.getBigDecimal("price")))
|
||||
.expirationDate(rs.getDate("expiration_date").toLocalDate())
|
||||
.productDao(this)
|
||||
.build());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Product product) throws SQLException {
|
||||
var sql = "insert into PRODUCTS (name, price, expiration_date) values (?, ?, ?)";
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement = connection.prepareStatement(sql)) {
|
||||
preparedStatement.setString(1, product.getName());
|
||||
preparedStatement.setBigDecimal(2, product.getPrice().getAmount());
|
||||
preparedStatement.setDate(3, Date.valueOf(product.getExpirationDate()));
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Product product) throws SQLException {
|
||||
var sql = "update PRODUCTS set price = ?, expiration_date = ? where name = ?;";
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement = connection.prepareStatement(sql)) {
|
||||
preparedStatement.setBigDecimal(1, product.getPrice().getAmount());
|
||||
preparedStatement.setDate(2, Date.valueOf(product.getExpirationDate()));
|
||||
preparedStatement.setString(3, product.getName());
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/** Tests that Domain Model example runs without errors. */
|
||||
final class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[] {}));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class CustomerDaoImplTest {
|
||||
|
||||
public static final String INSERT_CUSTOMER_SQL = "insert into CUSTOMERS values('customer', 100)";
|
||||
public static final String SELECT_CUSTOMERS_SQL = "select name, money from CUSTOMERS";
|
||||
public static final String INSERT_PURCHASES_SQL =
|
||||
"insert into PURCHASES values('product', 'customer')";
|
||||
public static final String SELECT_PURCHASES_SQL =
|
||||
"select product_name, customer_name from PURCHASES";
|
||||
|
||||
private DataSource dataSource;
|
||||
private Product product;
|
||||
private Customer customer;
|
||||
private CustomerDao customerDao;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws SQLException {
|
||||
// create db schema
|
||||
dataSource = TestUtils.createDataSource();
|
||||
|
||||
TestUtils.deleteSchema(dataSource);
|
||||
TestUtils.createSchema(dataSource);
|
||||
|
||||
// setup objects
|
||||
customerDao = new CustomerDaoImpl(dataSource);
|
||||
|
||||
customer = Customer.builder().name("customer").money(Money.of(CurrencyUnit.USD,100.0)).customerDao(customerDao).build();
|
||||
|
||||
product =
|
||||
Product.builder()
|
||||
.name("product")
|
||||
.price(Money.of(USD, 100.0))
|
||||
.expirationDate(LocalDate.parse("2021-06-27"))
|
||||
.productDao(new ProductDaoImpl(dataSource))
|
||||
.build();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws SQLException {
|
||||
TestUtils.deleteSchema(dataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindCustomerByName() throws SQLException {
|
||||
var customer = customerDao.findByName("customer");
|
||||
|
||||
assertTrue(customer.isEmpty());
|
||||
|
||||
TestUtils.executeSQL(INSERT_CUSTOMER_SQL, dataSource);
|
||||
|
||||
customer = customerDao.findByName("customer");
|
||||
|
||||
assertTrue(customer.isPresent());
|
||||
assertEquals("customer", customer.get().getName());
|
||||
assertEquals(Money.of(USD, 100), customer.get().getMoney());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveCustomer() throws SQLException {
|
||||
customerDao.save(customer);
|
||||
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement();
|
||||
ResultSet rs = statement.executeQuery(SELECT_CUSTOMERS_SQL)) {
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertEquals(customer.getName(), rs.getString("name"));
|
||||
assertEquals(customer.getMoney(), Money.of(USD, rs.getBigDecimal("money")));
|
||||
}
|
||||
|
||||
assertThrows(SQLException.class, () -> customerDao.save(customer));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateCustomer() throws SQLException {
|
||||
TestUtils.executeSQL(INSERT_CUSTOMER_SQL, dataSource);
|
||||
|
||||
customer.setMoney(Money.of(CurrencyUnit.USD, 99));
|
||||
|
||||
customerDao.update(customer);
|
||||
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement();
|
||||
ResultSet rs = statement.executeQuery(SELECT_CUSTOMERS_SQL)) {
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertEquals(customer.getName(), rs.getString("name"));
|
||||
assertEquals(customer.getMoney(), Money.of(USD, rs.getBigDecimal("money")));
|
||||
assertFalse(rs.next());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddProductToPurchases() throws SQLException {
|
||||
TestUtils.executeSQL(INSERT_CUSTOMER_SQL, dataSource);
|
||||
TestUtils.executeSQL(ProductDaoImplTest.INSERT_PRODUCT_SQL, dataSource);
|
||||
|
||||
customerDao.addProduct(product, customer);
|
||||
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement();
|
||||
ResultSet rs = statement.executeQuery(SELECT_PURCHASES_SQL)) {
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertEquals(product.getName(), rs.getString("product_name"));
|
||||
assertEquals(customer.getName(), rs.getString("customer_name"));
|
||||
assertFalse(rs.next());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteProductFromPurchases() throws SQLException {
|
||||
TestUtils.executeSQL(INSERT_CUSTOMER_SQL, dataSource);
|
||||
TestUtils.executeSQL(ProductDaoImplTest.INSERT_PRODUCT_SQL, dataSource);
|
||||
TestUtils.executeSQL(INSERT_PURCHASES_SQL, dataSource);
|
||||
|
||||
customerDao.deleteProduct(product, customer);
|
||||
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement();
|
||||
ResultSet rs = statement.executeQuery(SELECT_PURCHASES_SQL)) {
|
||||
|
||||
assertFalse(rs.next());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class CustomerTest {
|
||||
|
||||
private CustomerDao customerDao;
|
||||
private Customer customer;
|
||||
private Product product;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
customerDao = mock(CustomerDao.class);
|
||||
|
||||
customer = Customer.builder()
|
||||
.name("customer")
|
||||
.money(Money.of(CurrencyUnit.USD, 100.0))
|
||||
.customerDao(customerDao)
|
||||
.build();
|
||||
|
||||
product = Product.builder()
|
||||
.name("product")
|
||||
.price(Money.of(USD, 100.0))
|
||||
.expirationDate(LocalDate.now().plusDays(10))
|
||||
.productDao(mock(ProductDao.class))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveCustomer() throws SQLException {
|
||||
when(customerDao.findByName("customer")).thenReturn(Optional.empty());
|
||||
|
||||
customer.save();
|
||||
|
||||
verify(customerDao, times(1)).save(customer);
|
||||
|
||||
when(customerDao.findByName("customer")).thenReturn(Optional.of(customer));
|
||||
|
||||
customer.save();
|
||||
|
||||
verify(customerDao, times(1)).update(customer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddProductToPurchases() {
|
||||
product.setPrice(Money.of(USD, 200.0));
|
||||
|
||||
customer.buyProduct(product);
|
||||
|
||||
assertEquals(customer.getPurchases(), new ArrayList<>());
|
||||
assertEquals(customer.getMoney(), Money.of(USD,100));
|
||||
|
||||
product.setPrice(Money.of(USD, 100.0));
|
||||
|
||||
customer.buyProduct(product);
|
||||
|
||||
assertEquals(new ArrayList<>(Arrays.asList(product)), customer.getPurchases());
|
||||
assertEquals(Money.zero(USD), customer.getMoney());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveProductFromPurchases() {
|
||||
customer.setPurchases(new ArrayList<>(Arrays.asList(product)));
|
||||
|
||||
customer.returnProduct(product);
|
||||
|
||||
assertEquals(new ArrayList<>(), customer.getPurchases());
|
||||
assertEquals(Money.of(USD, 200), customer.getMoney());
|
||||
|
||||
customer.returnProduct(product);
|
||||
|
||||
assertEquals(new ArrayList<>(), customer.getPurchases());
|
||||
assertEquals(Money.of(USD, 200), customer.getMoney());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ProductDaoImplTest {
|
||||
|
||||
public static final String INSERT_PRODUCT_SQL =
|
||||
"insert into PRODUCTS values('product', 100, DATE '2021-06-27')";
|
||||
public static final String SELECT_PRODUCTS_SQL =
|
||||
"select name, price, expiration_date from PRODUCTS";
|
||||
|
||||
private DataSource dataSource;
|
||||
private ProductDao productDao;
|
||||
private Product product;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws SQLException {
|
||||
// create schema
|
||||
dataSource = TestUtils.createDataSource();
|
||||
|
||||
TestUtils.deleteSchema(dataSource);
|
||||
TestUtils.createSchema(dataSource);
|
||||
|
||||
// setup objects
|
||||
productDao = new ProductDaoImpl(dataSource);
|
||||
|
||||
product =
|
||||
Product.builder()
|
||||
.name("product")
|
||||
.price(Money.of(USD, 100.0))
|
||||
.expirationDate(LocalDate.parse("2021-06-27"))
|
||||
.productDao(productDao)
|
||||
.build();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws SQLException {
|
||||
TestUtils.deleteSchema(dataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindProductByName() throws SQLException {
|
||||
var product = productDao.findByName("product");
|
||||
|
||||
assertTrue(product.isEmpty());
|
||||
|
||||
TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource);
|
||||
|
||||
product = productDao.findByName("product");
|
||||
|
||||
assertTrue(product.isPresent());
|
||||
assertEquals("product", product.get().getName());
|
||||
assertEquals(Money.of(USD, 100), product.get().getPrice());
|
||||
assertEquals(LocalDate.parse("2021-06-27"), product.get().getExpirationDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveProduct() throws SQLException {
|
||||
|
||||
productDao.save(product);
|
||||
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement();
|
||||
ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) {
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertEquals(product.getName(), rs.getString("name"));
|
||||
assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price")));
|
||||
assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate());
|
||||
}
|
||||
|
||||
assertThrows(SQLException.class, () -> productDao.save(product));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateProduct() throws SQLException {
|
||||
TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource);
|
||||
|
||||
product.setPrice(Money.of(USD, 99.0));
|
||||
|
||||
productDao.update(product);
|
||||
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement();
|
||||
ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) {
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertEquals(product.getName(), rs.getString("name"));
|
||||
assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price")));
|
||||
assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class ProductTest {
|
||||
|
||||
private ProductDao productDao;
|
||||
private Product product;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
productDao = mock(ProductDaoImpl.class);
|
||||
|
||||
product = Product.builder()
|
||||
.name("product")
|
||||
.price(Money.of(USD, 100.0))
|
||||
.expirationDate(LocalDate.now().plusDays(10))
|
||||
.productDao(productDao)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveProduct() throws SQLException {
|
||||
when(productDao.findByName("product")).thenReturn(Optional.empty());
|
||||
|
||||
product.save();
|
||||
|
||||
verify(productDao, times(1)).save(product);
|
||||
|
||||
when(productDao.findByName("product")).thenReturn(Optional.of(product));
|
||||
|
||||
product.save();
|
||||
|
||||
verify(productDao, times(1)).update(product);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetSalePriceOfProduct() {
|
||||
assertEquals(Money.of(USD, 100), product.getSalePrice());
|
||||
|
||||
product.setExpirationDate(LocalDate.now().plusDays(2));
|
||||
|
||||
assertEquals(Money.of(USD, 80), product.getSalePrice());
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.domainmodel;
|
||||
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
public static void executeSQL( String sql, DataSource dataSource) throws SQLException {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.executeUpdate(sql);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createSchema(DataSource dataSource) throws SQLException {
|
||||
TestUtils.executeSQL(App.CREATE_SCHEMA_SQL, dataSource);
|
||||
}
|
||||
|
||||
public static void deleteSchema(DataSource dataSource) throws SQLException {
|
||||
TestUtils.executeSQL(App.DELETE_SCHEMA_SQL, dataSource);
|
||||
}
|
||||
|
||||
public static DataSource createDataSource() {
|
||||
var dataSource = new JdbcDataSource();
|
||||
dataSource.setURL(App.H2_DB_URL);
|
||||
return dataSource;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user