docs: update unit of work

This commit is contained in:
Ilkka Seppälä
2024-05-23 09:27:34 +03:00
parent 84df9ad48a
commit e5e406a71f
4 changed files with 52 additions and 30 deletions
+49 -27
View File
@@ -1,37 +1,36 @@
---
title: Unit Of Work
category: Architectural
category: Data access
language: en
tag:
- Data access
- Performance
- Data access
- Decoupling
- Persistence
- Transactions
---
## Intent
When a business transaction is completed, all the updates are sent as one big unit of work to be
persisted in one go to minimize database round-trips.
The Unit of Work pattern maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
## Explanation
Real-world example
> Arms dealer has a database containing weapon information. Merchants all over the town are
> constantly updating this information and it causes a high load on the database server. To make the
> load more manageable we apply to Unit of Work pattern to send many small updates in batches.
> Imagine a library where a librarian tracks all the books that are borrowed and returned throughout the day. Instead of updating the library's inventory system every time a single transaction occurs, the librarian keeps a list of all the changes and updates the system once at the end of the day. This approach ensures that all changes are processed together, maintaining the integrity of the inventory and reducing the number of individual updates needed. This is analogous to the Unit of Work pattern in software, where all changes to a set of objects are tracked and committed as a single transaction to maintain consistency and efficiency.
In plain words
> Unit of Work merges many small database updates in a single batch to optimize the number of
> round-trips.
> The Unit of Work pattern tracks changes to objects during a transaction and commits all changes as a single unit to ensure consistency and efficiency.
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
> Maintains a list of objects affected by a business transaction and coordinates the writing out of
> changes and the resolution of concurrency problems.
> Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
**Programmatic Example**
Arms dealer has a database containing weapon information. Merchants all over the town are constantly updating this information causing a high load on the database server. To make the load more manageable we apply to Unit of Work pattern to send many small updates in batches.
Here's the `Weapon` entity that is being persisted in the database.
```java
@@ -43,9 +42,7 @@ public class Weapon {
}
```
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern.
It maintains a map of database operations (`context`) that need to be done and when `commit` is
called it applies them in a single batch.
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern. It maintains a map of database operations (`context`) that need to be done and when `commit` is called it applies them in a single batch.
```java
public interface IUnitOfWork<T> {
@@ -103,7 +100,7 @@ public class ArmsDealer implements IUnitOfWork<Weapon> {
*/
@Override
public void commit() {
if (context == null || context.size() == 0) {
if (context == null || context.isEmpty()) {
return;
}
LOGGER.info("Commit started");
@@ -179,23 +176,48 @@ Here is the console output.
## Class diagram
![alt text](./etc/unit-of-work.urm.png "unit-of-work")
![Unit of Work](./etc/unit-of-work.urm.png "Unit of Work")
## Applicability
Use the Unit Of Work pattern when
* To optimize the time taken for database transactions.
* To send changes to database as a unit of work which ensures atomicity of the transaction.
* To reduce the number of database calls.
* Use when you need to manage multiple operations that need to be treated as a single transaction.
* Ideal in scenarios where changes to the business objects must be tracked and saved in a coordinated manner.
* Useful when working with object-relational mapping (ORM) frameworks in Java such as Hibernate.
## Tutorials
* [Repository and Unit of Work Pattern](https://www.programmingwithwolfgang.com/repository-and-unit-of-work-pattern/)
* [Unit of Work - a Design Pattern](https://mono.software/2017/01/13/unit-of-work-a-design-pattern/)
* [Repository and Unit of Work Pattern - Wolfgang Ofner](https://www.programmingwithwolfgang.com/repository-and-unit-of-work-pattern/)
* [Unit of Work - a Design Pattern - Mono](https://mono.software/2017/01/13/unit-of-work-a-design-pattern/)
## Known Uses
* Implementations in Java-based ORM frameworks like Hibernate.
* Enterprise applications where multiple database operations need to be atomic.
* Complex transactional systems where multiple objects are modified and persisted together.
## Consequences
Benefits:
* Ensures data integrity by managing transactions effectively.
* Reduces the number of database calls by batching them together.
* Simplifies the persistence logic by decoupling transaction management from the business logic.
Trade-offs:
* Can introduce complexity in managing the life cycle of objects within the unit of work.
* Potential performance overhead if not managed properly, especially with large datasets.
## Related Patterns
* [Identity Map](https://java-design-patterns.com/patterns/identity-map/): Helps to ensure that each object is only loaded once per transaction, reducing redundancy and improving performance.
* [Repository](https://java-design-patterns.com/patterns/repository/): Often used in conjunction with Unit of Work to abstract the persistence logic and provide a cleaner way to access data.
* [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/): While different in its procedural approach, it can complement Unit of Work by managing transactional logic at a higher level.
## Credits
* [Design Pattern - Unit Of Work Pattern](https://www.codeproject.com/Articles/581487/Unit-of-Work-Design-Pattern)
* [Unit Of Work](https://martinfowler.com/eaaCatalog/unitOfWork.html)
* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a)
* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze)
* [Java Persistence with Hibernate](https://amzn.to/44tP1ox)
* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
* [Unit Of Work Design Pattern - Code Project](https://www.codeproject.com/Articles/581487/Unit-of-Work-Design-Pattern)
* [Unit Of Work - Martin Fowler](https://martinfowler.com/eaaCatalog/unitOfWork.html)
@@ -44,7 +44,7 @@ public class App {
var silverTrident = new Weapon(3, "silver trident");
// create repository
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(),
var weaponRepository = new ArmsDealer(new HashMap<>(),
new WeaponDatabase());
// perform operations on the weapons
@@ -73,7 +73,7 @@ public class ArmsDealer implements UnitOfWork<Weapon> {
*/
@Override
public void commit() {
if (context == null || context.size() == 0) {
if (context == null || context.isEmpty()) {
return;
}
LOGGER.info("Commit started");
@@ -46,7 +46,7 @@ class ArmsDealerTest {
private final Map<String, List<Weapon>> context = new HashMap<>();
private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class);
private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);;
private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);
@Test
void shouldSaveNewStudentWithoutWritingToDb() {