docs: embedded value + refactor

This commit is contained in:
Ilkka Seppälä
2024-04-13 16:18:43 +03:00
parent ffffab40ec
commit 75409a9d0d
3 changed files with 80 additions and 60 deletions
+71 -49
View File
@@ -1,26 +1,30 @@
---
title: Embedded Value
category: Structural
category: Behavioral
language: en
tag:
- Data Access
- Enterprise Application Pattern
tag:
- Data access
- Enterprise patterns
- Optimization
- Performance
---
## Also known as
Aggregate Mapping, Composer
* Aggregate Mapping
* Composer
* Inline Value
* Integrated Value
## Intent
Many small objects make sense in an OO system that dont make sense as
tables in a database. An Embedded Value maps the values of an object to fields in the record of the objects owner.
The Embedded Value design pattern aims to enhance performance and reduce memory overhead by storing frequently accessed immutable data directly within the object that uses it, rather than separately.
## Explanation
Real-world example
> Examples include currency-aware money objects and date
ranges. Although the default thinking is to save an object as a table, no sane person would want a table of money values.
> Another example would be the online orders which have a shipping address like street, city, state. We map these values of Shipping address object to fields in record of Order object.
> In a library, the reference desk embeds commonly used resources like dictionaries and encyclopedias directly at the desk for quick and easy access, similar to how the Embedded Value design pattern integrates frequently used data directly within an object for efficiency.
In plain words
@@ -28,24 +32,26 @@ In plain words
**Programmatic Example**
Consider online order's example where we have details of item ordered and shipping address. We have Shipping address embedded in Order object. But in database we map shipping address values in Order record instead of creating a separate table for Shipping address and using foreign key to reference the order object.
Consider an online ordering example where we have details of item ordered and shipping address. We have Shipping address embedded in Order object. But in database we map shipping address values in Order record instead of creating a separate table for Shipping address and using foreign key to reference the order object.
First, we have POJOs `Order` and `ShippingAddress`
```java
public class Order {
private int id;
private String item;
private String orderedBy;
private ShippingAddress ShippingAddress;
private int id;
private String item;
private String orderedBy;
private ShippingAddress ShippingAddress;
public Order(String item, String orderedBy, ShippingAddress ShippingAddress) {
this.item = item;
this.orderedBy = orderedBy;
this.ShippingAddress = ShippingAddress;
}
public Order(String item, String orderedBy, ShippingAddress ShippingAddress) {
this.item = item;
this.orderedBy = orderedBy;
this.ShippingAddress = ShippingAddress;
}
}
```
```java
public class ShippingAddress {
@@ -60,48 +66,56 @@ public class ShippingAddress {
}
}
```
Now, we have to create only one table for Order along with fields for shipping address attributes.
```Sql
CREATE TABLE Orders (Id INT AUTO_INCREMENT, item VARCHAR(50) NOT NULL, orderedBy VARCHAR(50) city VARCHAR(50), state VARCHAR(50), pincode CHAR(6) NOT NULL, PRIMARY KEY(Id))
CREATE TABLE Orders
(
Id INT AUTO_INCREMENT,
item VARCHAR(50) NOT NULL,
orderedBy VARCHAR(50) city VARCHAR (50),
state VARCHAR(50),
pincode CHAR(6) NOT NULL,
PRIMARY KEY (Id)
)
```
While performing the database queries and inserts, we box and unbox shipping address details.
```java
final String INSERT_ORDER = "INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES (?, ?, ?, ?, ?)";
final String INSERT_ORDER="INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES (?, ?, ?, ?, ?)";
public boolean insertOrder(Order order) throws Exception {
var insertOrder = new PreparedStatement(INSERT_ORDER);
var address = order.getShippingAddress();
public boolean insertOrder(Order order)throws Exception{
var insertOrder=new PreparedStatement(INSERT_ORDER);
var address=order.getShippingAddress();
conn.setAutoCommit(false);
insertIntoOrders.setString(1, order.getItem());
insertIntoOrders.setString(2, order.getOrderedBy());
insertIntoOrders.setString(3, address.getCity());
insertIntoOrders.setString(4, address.getState());
insertIntoOrders.setString(5, address.getPincode());
var affectedRows = insertIntoOrders.executeUpdate();
if(affectedRows == 1){
insertIntoOrders.setString(1,order.getItem());
insertIntoOrders.setString(2,order.getOrderedBy());
insertIntoOrders.setString(3,address.getCity());
insertIntoOrders.setString(4,address.getState());
insertIntoOrders.setString(5,address.getPincode());
var affectedRows=insertIntoOrders.executeUpdate();
if(affectedRows==1) {
Logger.info("Inserted successfully");
}else{
Logger.info("Couldn't insert " + order);
} else {
Logger.info("Couldn't insert "+order);
}
}
```
## Class diagram
![alt text](./etc/embedded-value.urm.png "Embedded value class diagram")
![Embedded Value](./etc/embedded-value.urm.png "Embedded Value class diagram")
## Applicability
Use the Embedded value pattern when
Use the Embedded value pattern when:
* Many small objects make sense in an OO system that dont make sense as tables in a database.
* The simplest cases for Embedded Value are the clear, simple Value Objects like money and date range.
* If youre mapping to an existing schema, you can use this pattern when a table contains data that you want to split into more than one object in memory. This can occur when you want to factor out some behaviour in object model.
* In most cases youll only use Embedded Value on a reference object when the association between them is single valued at both ends (a one-to-one association).
* It can only be used for fairly simple dependents. A solitary dependent, or a few separated dependents, works well.
* An application requires high performance and the data involved is immutable.
* Memory footprint reduction is critical, especially in environments with limited resources.
* Objects frequently access a particular piece of immutable data.
## Tutorials
@@ -111,14 +125,22 @@ Use the Embedded value pattern when
## Consequences
* The great advantage of Embedded Value is that it allows SQL queries to be made against the values in the dependent object.
* The embedded value object has no persistence behaviour at all.
* While using this, you have to be careful that any change to the dependent marks the owner as dirty—which isnt an issue with Value Objects that are replaced in the owner.
* Another issue is the loading and saving. If you only load the embedded object memory when you load the owner, thats an argument for saving both in the same table.
* Another question is whether youll want to access the embedded objects' data separately through SQL. This can be important if youre reporting through SQL and dont have a separate database for reporting.
Benefits:
* Reduces the memory overhead by avoiding separate allocations for immutable data.
* Improves performance by minimizing memory accesses and reducing cache misses.
Trade-offs:
* Increases complexity in object design and can lead to tightly coupled systems.
* Modifying the embedded value necessitates changes across all objects that embed this value, which can complicate maintenance.
## Related Patterns
[Flyweight](https://java-design-patterns.com/patterns/flyweight/): Shares objects to support large quantities using a minimal amount of memory, somewhat similar in intent but different in implementation.
[Singleton](https://java-design-patterns.com/patterns/singleton/): Ensures a class has only one instance and provides a global point of access to it, can be used to manage a shared embedded value.
## Credits
* [Fowler, Martin - Patterns of enterprise application architecture-Addison-Wesley](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420)
* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html)
* [Patterns of Enterprise Application Architecture](https://amzn.to/4452Idd)
* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html)
@@ -141,7 +141,6 @@ public class DataSource implements DataSourceInterface {
rSet.getString(6)));
ordersList.add(order);
}
rSet.close();
} catch (SQLException e) {
LOGGER.error(e.getMessage(), e.getCause());
}
@@ -166,7 +165,6 @@ public class DataSource implements DataSourceInterface {
rSet.getString(5), rSet.getString(6));
order = new Order(rSet.getInt(1), rSet.getString(2), rSet.getString(3), address);
}
rSet.close();
} catch (Exception e) {
LOGGER.error(e.getLocalizedMessage(), e.getCause());
}
@@ -192,7 +190,7 @@ public class DataSource implements DataSourceInterface {
}
@Override
public boolean deleteSchema() throws Exception {
public boolean deleteSchema() {
try {
deleteschema.execute(DELETE_SCHEMA);
queryOrders.close();
@@ -36,22 +36,22 @@ import java.util.stream.Stream;
*/
interface DataSourceInterface {
final String JDBC_URL = "jdbc:h2:mem:Embedded-Value";
String JDBC_URL = "jdbc:h2:mem:Embedded-Value";
final String CREATE_SCHEMA = "CREATE TABLE Orders (Id INT AUTO_INCREMENT, item VARCHAR(50) NOT NULL, orderedBy VARCHAR(50)"
String CREATE_SCHEMA = "CREATE TABLE Orders (Id INT AUTO_INCREMENT, item VARCHAR(50) NOT NULL, orderedBy VARCHAR(50)"
+ ", city VARCHAR(50), state VARCHAR(50), pincode CHAR(6) NOT NULL, PRIMARY KEY(Id))";
final String GET_SCHEMA = "SHOW COLUMNS FROM Orders";
String GET_SCHEMA = "SHOW COLUMNS FROM Orders";
final String INSERT_ORDER = "INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES(?, ?, ?, ?, ?)";
String INSERT_ORDER = "INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES(?, ?, ?, ?, ?)";
final String QUERY_ORDERS = "SELECT * FROM Orders";
String QUERY_ORDERS = "SELECT * FROM Orders";
final String QUERY_ORDER = QUERY_ORDERS + " WHERE Id = ?";
String QUERY_ORDER = QUERY_ORDERS + " WHERE Id = ?";
final String REMOVE_ORDER = "DELETE FROM Orders WHERE Id = ?";
String REMOVE_ORDER = "DELETE FROM Orders WHERE Id = ?";
final String DELETE_SCHEMA = "DROP TABLE Orders";
String DELETE_SCHEMA = "DROP TABLE Orders";
boolean createSchema() throws SQLException;