2017-09-19 20:39:48 +05:30
|
|
|
---
|
|
|
|
layout: pattern
|
|
|
|
title: Unit Of Work
|
|
|
|
folder: unit-of-work
|
|
|
|
permalink: /patterns/unit-of-work/
|
|
|
|
categories: Architectural
|
2021-05-19 10:49:05 -06:00
|
|
|
language: en
|
2017-09-19 20:39:48 +05:30
|
|
|
tags:
|
2019-12-13 21:09:28 +02:00
|
|
|
- Data access
|
2020-07-26 12:10:48 +03:00
|
|
|
- Performance
|
2017-09-19 20:39:48 +05:30
|
|
|
---
|
|
|
|
|
|
|
|
## Intent
|
2020-08-18 20:07:47 +03:00
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
When a business transaction is completed, all the updates are sent as one big unit of work to be
|
2020-08-18 20:07:47 +03:00
|
|
|
persisted in one go to minimize database round-trips.
|
2020-07-26 12:10:48 +03:00
|
|
|
|
|
|
|
## Explanation
|
2020-08-18 20:07:47 +03:00
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
Real-world example
|
2020-07-26 12:10:48 +03:00
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
> 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
|
2020-08-18 20:07:47 +03:00
|
|
|
> load more manageable we apply to Unit of Work pattern to send many small updates in batches.
|
2020-07-26 12:10:48 +03:00
|
|
|
|
|
|
|
In plain words
|
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
> Unit of Work merges many small database updates in a single batch to optimize the number of
|
2020-08-18 20:07:47 +03:00
|
|
|
> round-trips.
|
2020-07-26 12:10:48 +03:00
|
|
|
|
|
|
|
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
|
|
|
|
|
2020-08-18 20:07:47 +03:00
|
|
|
> Maintains a list of objects affected by a business transaction and coordinates the writing out of
|
|
|
|
> changes and the resolution of concurrency problems.
|
2020-07-26 12:10:48 +03:00
|
|
|
|
|
|
|
**Programmatic Example**
|
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
Here's the `Weapon` entity that is being persisted in the database.
|
2020-07-26 12:10:48 +03:00
|
|
|
|
|
|
|
```java
|
2022-01-08 14:27:11 +02:00
|
|
|
@Getter
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
public class Weapon {
|
|
|
|
private final Integer id;
|
|
|
|
private final String name;
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern.
|
2020-08-18 20:07:47 +03:00
|
|
|
It maintains a map of database operations (`context`) that need to be done and when `commit` is
|
2022-01-08 14:27:11 +02:00
|
|
|
called it applies them in a single batch.
|
2020-07-26 12:10:48 +03:00
|
|
|
|
|
|
|
```java
|
|
|
|
public interface IUnitOfWork<T> {
|
|
|
|
|
|
|
|
String INSERT = "INSERT";
|
|
|
|
String DELETE = "DELETE";
|
|
|
|
String MODIFY = "MODIFY";
|
|
|
|
|
|
|
|
void registerNew(T entity);
|
|
|
|
|
|
|
|
void registerModified(T entity);
|
|
|
|
|
|
|
|
void registerDeleted(T entity);
|
|
|
|
|
|
|
|
void commit();
|
|
|
|
}
|
|
|
|
|
2021-03-13 13:19:21 +01:00
|
|
|
@Slf4j
|
2022-01-08 14:27:11 +02:00
|
|
|
@RequiredArgsConstructor
|
|
|
|
public class ArmsDealer implements IUnitOfWork<Weapon> {
|
|
|
|
|
|
|
|
private final Map<String, List<Weapon>> context;
|
|
|
|
private final WeaponDatabase weaponDatabase;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void registerNew(Weapon weapon) {
|
|
|
|
LOGGER.info("Registering {} for insert in context.", weapon.getName());
|
|
|
|
register(weapon, UnitActions.INSERT.getActionValue());
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
2022-01-08 14:27:11 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void registerModified(Weapon weapon) {
|
|
|
|
LOGGER.info("Registering {} for modify in context.", weapon.getName());
|
|
|
|
register(weapon, UnitActions.MODIFY.getActionValue());
|
|
|
|
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
2022-01-08 14:27:11 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void registerDeleted(Weapon weapon) {
|
|
|
|
LOGGER.info("Registering {} for delete in context.", weapon.getName());
|
|
|
|
register(weapon, UnitActions.DELETE.getActionValue());
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
private void register(Weapon weapon, String operation) {
|
|
|
|
var weaponsToOperate = context.get(operation);
|
|
|
|
if (weaponsToOperate == null) {
|
|
|
|
weaponsToOperate = new ArrayList<>();
|
|
|
|
}
|
|
|
|
weaponsToOperate.add(weapon);
|
|
|
|
context.put(operation, weaponsToOperate);
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
2022-01-08 14:27:11 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* All UnitOfWork operations are batched and executed together on commit only.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void commit() {
|
|
|
|
if (context == null || context.size() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LOGGER.info("Commit started");
|
|
|
|
if (context.containsKey(UnitActions.INSERT.getActionValue())) {
|
|
|
|
commitInsert();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (context.containsKey(UnitActions.MODIFY.getActionValue())) {
|
|
|
|
commitModify();
|
|
|
|
}
|
|
|
|
if (context.containsKey(UnitActions.DELETE.getActionValue())) {
|
|
|
|
commitDelete();
|
|
|
|
}
|
|
|
|
LOGGER.info("Commit finished.");
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
2022-01-08 14:27:11 +02:00
|
|
|
|
|
|
|
private void commitInsert() {
|
|
|
|
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
|
|
|
|
for (var weapon : weaponsToBeInserted) {
|
|
|
|
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
|
|
|
|
weaponDatabase.insert(weapon);
|
|
|
|
}
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
private void commitModify() {
|
|
|
|
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
|
|
|
|
for (var weapon : modifiedWeapons) {
|
|
|
|
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
|
|
|
|
weaponDatabase.modify(weapon);
|
|
|
|
}
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
private void commitDelete() {
|
|
|
|
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
|
|
|
|
for (var weapon : deletedWeapons) {
|
|
|
|
LOGGER.info("Scrapping {}.", weapon.getName());
|
|
|
|
weaponDatabase.delete(weapon);
|
|
|
|
}
|
2020-07-26 12:10:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-01-08 14:27:11 +02:00
|
|
|
Here is how the whole app is put together.
|
2020-07-26 12:10:48 +03:00
|
|
|
|
|
|
|
```java
|
2022-01-08 14:27:11 +02:00
|
|
|
// create some weapons
|
|
|
|
var enchantedHammer = new Weapon(1, "enchanted hammer");
|
|
|
|
var brokenGreatSword = new Weapon(2, "broken great sword");
|
|
|
|
var silverTrident = new Weapon(3, "silver trident");
|
|
|
|
|
|
|
|
// create repository
|
|
|
|
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(), new WeaponDatabase());
|
|
|
|
|
|
|
|
// perform operations on the weapons
|
|
|
|
weaponRepository.registerNew(enchantedHammer);
|
|
|
|
weaponRepository.registerModified(silverTrident);
|
|
|
|
weaponRepository.registerDeleted(brokenGreatSword);
|
|
|
|
weaponRepository.commit();
|
|
|
|
```
|
|
|
|
|
|
|
|
Here is the console output.
|
|
|
|
|
|
|
|
```
|
|
|
|
21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context.
|
|
|
|
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context.
|
|
|
|
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context.
|
|
|
|
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started
|
|
|
|
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack.
|
|
|
|
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work.
|
|
|
|
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword.
|
|
|
|
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished.
|
2020-07-26 12:10:48 +03:00
|
|
|
```
|
2017-09-19 20:39:48 +05:30
|
|
|
|
2019-12-07 20:01:13 +02:00
|
|
|
## Class diagram
|
2020-08-18 20:07:47 +03:00
|
|
|
|
2017-09-19 20:39:48 +05:30
|
|
|

|
|
|
|
|
|
|
|
## Applicability
|
2020-08-18 20:07:47 +03:00
|
|
|
|
2017-09-19 20:39:48 +05:30
|
|
|
Use the Unit Of Work pattern when
|
|
|
|
|
2017-09-26 23:25:36 +05:30
|
|
|
* To optimize the time taken for database transactions.
|
2017-09-25 21:23:12 +05:30
|
|
|
* To send changes to database as a unit of work which ensures atomicity of the transaction.
|
2022-01-08 14:27:11 +02:00
|
|
|
* To reduce the number of database calls.
|
2017-09-19 20:39:48 +05:30
|
|
|
|
2020-07-26 12:10:48 +03:00
|
|
|
## 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/)
|
|
|
|
|
2017-09-19 20:39:48 +05:30
|
|
|
## 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)
|
2020-07-07 21:19:14 +03:00
|
|
|
* [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)
|