I have two tables: tblCustomer, tblProduct:
tblCustomer:
Id: Integer, auto-increament
Name: Varchar(30)
....
tblProduct
Id: Integer, auto-increament
Name: Varchar(50)
customerId: Integer
....
And two classes: Customer, Product:
public class Product
{
private int id;
private int name;
/* Other stuffs */
}
public class Customer
{
private int id;
private String name;
private String phoneNumber;
/* get-set and others stuffs */
public static boolean add(Customer cus) {
/* This is for insert a customer to tblCustomer */
}
public boolean addProduct(Product pd) {
/* This is for insert a product to tblProduct with current customer Id */
}
}
When customer register account, it call:
Customer cus = new Customer(/* ... */);
Customer.add(cus);
and when customer buy a product:
Product pd = new Product(/* ... */);
currentCustomer.addProduct(pd);
But my teacher said it not correct in OOAD (and even OOP) because Customer.addProduct is operate on tblProduct table, is it right? What is good design for this case?
** Update: **
Product haven't a pre-defined yet, when a customer buy a product, the store will make it and delivery to customer, so two same products is rare happen, so is tblCustomerProduct need?
What your teacher probably means is that you need a third table, named something like "CustomerProduct". This table contains the links between customers and which products they've bought.
tblCustomerProduct:
Id: Integer, auto-increament
CustomerId: Varchar(30)
ProductId: Varchar(30)
so, when a customer buys a product, you add this data to the table CustomerProduct. This will remove redundant data and also make deletion alot easier
Your teacher is right. It is not good Design because A class that you create should be cohesive. So that it should be responsible for doing the things that it can do. Here in your product class you have added customer id which is essentially a bad practice because A customer may purchase multiple products. This design will fail in that case. And also Product class doesn't need to know anything about the customer. All it has to know about is itself.
The relation between customer and product is an association which can be expressed as "customer buys product". So that customer id should not be there in product table.
Regarding the currentCustomer.addProduct(pd); method, it is not well suited because it is more suitable to product class rather than customer class.
The simple solution to your problem can be create a new class which can relate product and the customer.
For e.g.
CustomerProduct
customerid
productid
Inside this class you can add method like "AddProductForCustomer" and can write your database logic which will maintain the consistency.
Hope this clears your doubt.
Add a DAO tier that will contain the logical part of the methods save, delete, update, etc.
Here is how I usually do:
basepackage.domain: contains all your entities (data only, no logical part - in your case Product and Customer)
basepackage.dao: contains all your DAOs, only used to access the data, basically one per entity, each one containing methods such as findAll() : List<E>, findOne(K id) : E, save(E e) : void, etc.
basepackage.service: contains all your services, the logical part of the app. The services are the only ones that are calling the DAOs.
basepackage.presentation (or basepackage.web for a webapp): contains the HMI/web services/... implementation.
In the table, the customer is assigned to a product, but in the method you give a customer and add a product.
So I would go for either a method pd.setCustomer(currentCustomer) instead of currentCustomer.addProduct(pd)
Or change the customer field in the product table to a products field in the customer table.
Related
I read some of the publications and threads about DDD. There are many voices about connection between aggregate and entities. I understood that aggregate should be as simple as possible (one entity per aggregate). But what about situation when aggregate has collection of entities?
Let's say, we have one aggregate called "Month" which contains a collection of "Day" objects (that are domain entities, because they need an identity to be
distinguishable - to let aggregate know which "Day" to modify).
So I have two questions:
Is this a proper approach? Just a normal situation and I shouldn't be concerned?
What about "visibility" outside? In my approach, an aggregate is "package-private" to not let anyone use it in different parts of the system. But what about entities? Should they be visible just like Value Objects for different parts of the system? Or just create another VO to represent entities outside (for example: when entities are stored in events)?
Thank you for all the answers
Modeling days and months as entities depends a lot on the context. It might not be the best example to explain aggregates and entities but let's give it a try.
Let's assume that in our context a day can not exist on its own. It has to be within a month. If you want to refer to a day, you must specify the month first. That is how we use dates in real life, January 1st, May 8th ... Even though the days are entities they don't need a global uniques identifier. They only need an identifier within the month [1 .. 31].
Aggregates should be as small as possible however it is not a rule that you should only have one entity per aggregate. You just need to have an aggregate root (month) that has a unique identifier across all the systems. Within the aggregate, you can have entities(days) that have a unique identifier within the aggregate[1 .. 31]. If you want to refer to or access these entities you should always go through the aggregate root.
Aggregate, Entity and ValueObject
In my vision, an entity is a child with a name (Id) of an Aggregate which, in turn, is the Father with a name (Id).
An aggregate can have NO entity.
You can think well is an entity, NO: an entity exist ONLY within an Aggregate.
The entity (child) is a small aggregate (father) without other entities (children's).
Fathers and children can use transparent box (I don't have a better idea to translate the concept of ValueObject, sorry): when you create a transparent box you cannot change anything but you can read the content, if you want to update the box you MUST create a new one.
The aggregate is responsible for manage the entities, this means: add, update, remove and query.
If you want to talk (query) with an entity or entities you must ask the aggregate, consequentially, when you load the aggregate you MUST load all entities.
An aggregate can have multiple types of entities, how many? Well, this depends on you, on the design, and on the system.
Obviously, a big aggregate of many fields with many entities with each of them many rows maybe is not efficient, in this case, maybe you can choose the biggest entities and turn in Aggregates with or without children.
Pratical example
I wrote the example on csharp but is not much different from Java.
class Invoice : ValueObject
{
public string Number { get; private set; }
public DateTime Date { get; private set; }
public decimal TaxableAmount { get; private set; }
public decimal VatAmount { get; private set; }
public decimal TotalAmount => TaxableAmount + VatAmount;
public Invoice(string number, DateTime date, decimal taxableAmount, decimal
vatAmount)
{
// validation
Number = number;
[..]
}
}
class Taxonomy : Entity
{
public int Id {get; private set;}
public decimal Amount {get; private set;}
public string Classfication {get; private set;}
public Taxonomy(int id, decimal amount, string classification)
{
// validation
Id = id;
[..]
}
}
class SaleAggregate : AggregateRoot
{
private List<Taxonomy> _taxonomies;
public int Id {get; private set;}
public Invoice Invoice {get; private set;}
public IReadOnlyCollection<Taxonomy> Taxonomies => _taxonomies.AsReadOnly();
public SaleAggregate(int id, string number, DateTime date, decimal
taxableAmount, decimal vatAmount)
{
_taxonomies = new List<Taxonomy>();
// I prefer to pass ALWAYS primitive types to not rely on valueObject
// validation
Id = id;
Invoice = new Invoice(number, date, taxableAmount, vatAmount)
[..]
}
public void AddTaxonomy(int id, decimal amount, string classification)
{
// validation
_taxonomies.Add(new Taxonomy(id, amount, classification);
}
public void UpdateTaxonomy(int id, decimal amount, string classification)
{
// validation
var entity = _taxonomies.FirstOrDefault(p=> p.Id == id);
entity.Amount = amount;
entity.Classification = classification;
}
public void RemoveTaxonomy(int id)
{
// validation
var entity = _taxonomies.FirstOrDefault(p=> p.Id == id);
_taxonomy.Remove(entity);
}
public void UpdateVatAmount(decimal vatAmount)
{
// validation
Invoice = new Invoice(Invoice.Number, Invoice.Date,
Invoice.TaxableAmount, vatAmount);
}
}
Again: this is my vision about Aggregate, Entities, and ValueObject, other developers reading this can feel free to correct me.
First of all, I'm not taking about the primary id of the record. I'm talking about an field that is used by users to identify the record that's automatically generated but changeable by the user, not sequential and not a UUID. For example, starting with an account entity:
#Entity
#Data
class Account {
#Id
#GeneratedValue
private int id;
#Column(unique=true)
#NotNull
private String slug;
#Column
private String name;
}
and then I simply create a record:
#Autowired
private AccountRepository accountRepository;
Account account = new Account();
account.setName("ACME");
accountRepository.saveAndFlush(account);
At that point, the slug should have been generated, either completely randomly, or by doing something based on the name. How should that be done?
I know without locking the whole table it's impossible to ensure that the insertion won't result in an exception due to the uniqueness constrain being violated. I'm actually OK blocking the whole table or even letting the exception happen (you need a lot of requests per second fora conflict to happen between the check for availability and the insert).
If you separate the slug from the Account table and put it in a (id, slug) table by itself, you can generate the slug first (retrying until you succeed) and then persist the Account with a link to the just generated slug id.
You can't achieve this in a #PrePersist method, so your service needs to create the slug whenever you're creating an new Account. However it does simplify things on the application side (e.g. you don't need to wonder which constraint was violated when persisting an Account).
Depending on your other code, you can also get around locking the Account table and even the Slug table if you go for the optimistic approach.
A pseudo-code example of a service method that creates a new account (providing new Slug() creates the random slug):
#Autowired SlugRepository slugRepository;
#Autowired AccountRepository accountRepository;
public void createAccount(Account a) {
Slug s = null;
while(s == null) {
try {
s = slugRepository.save(new Slug());
} catch(Exception e) {
}
}
a.setSlug(s);
accountRepository.save(a);
}
I can think of JPA callbacks to generate the slug. In your case #PrePersist could be useful.
That said, why you need to make sure the value is available with a select before inserting the record, so the window for a collision to occur is tiny? You do have unique constraint on the column, right?
Update
Personally I would prefer to address it like this:
Use JPA callback #PrePersist when generating the the slug. Use to random UUID or timestamp to minimise the possibility of collision. No checking for collision as chances are minimal.
When updating the Account for user generated slug, always check first using query for collision. This check will offcourse happen in service update method itself.
This way I can be DB agnostic and also don't have to use repository/service in entity or listener classes.
I will do something like a separate Bean, helper or service class like this.
public class SlugService {
public String generateSlug(String slug)
{
if (accountRepo.getBySlug(slug) != null){ //check if it is already
return slug
} else {
slug.append("-"); //whatever the syntax
generateSlug();
}
}
public String makeSlug()
{
String slug = split by " ", replace by "_"(accountObject.getName);
generateSlug(slug)
}
}
Call the makeSlug(); method.
I am creating an application with database access. I do not use JPA nor JooQ or other frameworks for reasons (and do not want to. Also this is not important for my question). So I use JDBC and write plain sql.
My model looks like this:
public class Contact{
AddressBook addressBok;
String name;
...
}
I now created 2 DAOs, one for Contact and AddressBook. My Contact table has a foreign key to the AddressBook table (address_book_id).
I have a Service Class (e.g. ContactService) which would read each object using the corrsponding DAO and combine it to one Contact.
The Problem now is: I have the address_book_id in the ResultSet in the ContactDAO. How do I pass it over to the service class which then reads the correspondig AddressBook using the AddressBookDAO? As the model is shared, it is not a good solution to put a String addressBookId to Contact as clients using this model may do not know anything about the database.
I know these questions, but there is no answer on how to do it:
Using DAOs with composite Objects
Can a DAO call DAO?
The best practice is to use POJO domain object per each table where you can save your relationship fields like address_book_id. So you will have tree independent classes Contact, Address, AddressBook and independent DAO ContractDAO, AddressDAO, AddressBookDAO. Your ContactService will manipulate with these 6 objects to load, save, modify related data.
you can use the decorator pattern to wrap the entity(model class) that you want to add a foreign key to it, like this:
how to use:
for example: save(insert into db) a contact (here where I found the problem last time, so i fixed it by this solution)--> problem:how to insert a contact without knowing the fk address_book_id, in the args of save
public class ContactDAO extends DAO<Contact>{
// ....
#Override
public int save(IEntity contact){
ForeignKeyWrapper ctFk = (ForeignKeyWrapper)contact;
int address_book_id = ctFk.getFK();
Contact ct = (Contact)ctFk.getEntity();
String name = ct.getName();
// retrieve other fields of contact here
//use some sql to insert the contact in the db, now you have also the fk
String insertQuery = "INSERT INTO CONTACT(name,..other fields of contact.. , address_book_id) VALUES("+
name + "," +
/*other fields*/
address_book_id + ")";
//execute it here and fetch the id of the inserted row
}
// ....
}
public class clientClass{
//....
public static void main(String[] args) {
IEntity contact = new Contact(/* init fields*/);
IEntity addressBook = new AddressBook(/* init fields*/);
ForeignKeyWrapper ctFKW = new ForeignKeyWrapper(contact);
//link contact to addressBook (e.g: when creating a contact to insert it into the db)
ctFKW.setFK(addressBook.getId());
ContactDAO ctDAO = new ContactDAO(/*connection*/);
ctDAO.save(ctFKW);
}
//....
}
you can add a builder class to build your contact objects and link their foreign keys to addressBook objects Ids, to encapsulate the building logic, and simplify the process
CREATE TABLE order (
order_id INT
,...
,CONSTRAINT ... PRIMARY KEY (order_id)
);
CREATE TABLE item (
item_id INT
,item_list_price DECIMAL(10,2)
,...
, CONSTRAINT ... PRIMARY KEY (item_id)
);
CREATE TABLE order_item (
order_id INT
,item_id INT
,order_item_current_price DECIMAL(10,2);
,CONSTRAINT ... PRIMARY KEY (order_id, item_id)
,CONSTRAINT ... FOREIGN KEY (order_id) ...
,CONSTRAINT ... FOREIGN KEY (item_id) ...
);
An Order has Many Items; Each Item may belong to Many Orders.
An Item's price can change at any time. In order for a Customer to check how much he paid for an Item in the past, the order_item_cost field exists in the order_item table.
When the end user checks the Items for sale, it would pull a List of Items with the item id, name, and listPrice, but not the currentPrice (as the currentPrice only relates to historical reporting in conjunction with an order, not purchasing a new item)
.
When the customer wishes to check his historical purchases and what he paid for each item at the time, it would pull an Order with a List of Items including the item id, name, and currentPrice, but not the listPrice (as the listPrice only relates to purchasing a new item, not historical reporting).
So even though my relational schema has a Many to Many relationship between Order and Item, my POJOs think that there is a One to Many relationship between Order and Item, because there will never be an access pattern that requires the Many to Many
Here are my current POJOs (stripped down to be concise):
public class Order {
private int id;
private String status;
private Customer customer;
#OneToMany(mappedBy="order")
private List<Item> items;
...
}
public class Item {
private int id;
private String name;
#ManyToOne
private Order order;
private double listPrice; // List price of item, capable of changing over time
private double currentPrice; // represents the order_item_cost field in order_item table
...
}
As you can notice, I did not put a List<Order> in the Item class, because I can't imagine ever needing to know every possible Order ever used that purchased and Item.
This feels wrong, however. I'm wondering if I should take out the currentPrice member variable from Item and make a new POJO called OrderItem which reflects the order_item table:
public class OrderItem {
private Order order;
private Item item;
private double cost;
}
public class Order {
#OneToMany(mappedBy="order_item")
private List<OrderItem> orderItems;
}
public class Item {
#OneToMany(mappedBy="order_item")
private List<OrderItem> orderItems;
}
But, again, the access patterns would never require an Item to find all orders associated with it. Should the Item class instead be something like:
public class Item {
#OneToMany
private Order order;
}
Or, is there a more appropriate way to use Hibernate to handle this?
In summary: My DB has a Many to Many schema, as one item can belong to many orders, and each order can have many items, and I wish to record the price at which the customer bought the item at the time, because the list price of the item can change. The access patterns for my application however only require it to look like a One to Many relationship between order and item. I'm not sure how this is supposed to be handled with POJOs or hibernate.
You definitely need to model the OrderItem object, because it is a relationship object with its own properties (cost). You should model Order 1->n OrderItem n->1 Item. The DB should be modeled similarly -- an order does not contain items, because the price isn't the item price, it's the order_item price.
Based on object-oriented approach, I write the following entities:
#Entity
public class Customer {
#Id
private String id;
#OneToMany
private List<Order> orders;
public BigDecimal getTotal() {
// iterate over orders and sum the total
BigDecimal total = BigDecimal.ZERO;
for (Order o: orders) {
total = total.add(o.getTotal());
}
return total;
}
... // getter & setter
}
#Entity
public class Order {
#Id
private String id;
private BigDecimal total;
...
}
I realized that when calling getTotal() method for a Customer, Hibernate will issue a SELECT * FROM Order query to retrieve all Orders. The number of Order will surely increase as years passed. I believe a SELECT SUM(o.total) FROM Order o will give a better performance, CMIIMW. I just don't know where should I put the query? Rules of object oriented design suggest that getTotal() should be part of Customer, but the framework I'm using (Spring Transaction) doesn't allow transaction in domain objects.
In the first case, select * from orders.
You are just getting the list of orders and you need to calculate the sum in Server side code with iterating over the orders.
In the second case, select sum(o.total) from orders where order.customer_ id = 1234;
Database is doing the calculation for you. In terms of performance also,
this is better.
Why the database needs to delegate to some upper layer, when It can do it.
So I suggest you with the second case only.
As per OO, It might suggest to encapsulate both properties and related methods.
But its a Domain Class, which gets directly mapped to fields in Database.
To separate Data Access Logic, We can have a separate layer i.e DAO and put the desired logic in it.