Skip to content

Commit 3cc9bc2

Browse files
iluwatarohbus
andauthored
refactoring: unit of work (#1940)
Co-authored-by: Subhrodip Mohanta <[email protected]>
1 parent 11f2059 commit 3cc9bc2

File tree

7 files changed

+204
-200
lines changed

7 files changed

+204
-200
lines changed

unit-of-work/README.md

+109-105
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ tags:
1212

1313
## Intent
1414

15-
When a business transaction is completed, all the the updates are sent as one big unit of work to be
15+
When a business transaction is completed, all the updates are sent as one big unit of work to be
1616
persisted in one go to minimize database round-trips.
1717

1818
## Explanation
1919

20-
Real world example
20+
Real-world example
2121

22-
> We have a database containing student information. Administrators all over the country are
23-
> constantly updating this information and it causes high load on the database server. To make the
22+
> Arms dealer has a database containing weapon information. Merchants all over the town are
23+
> constantly updating this information and it causes a high load on the database server. To make the
2424
> load more manageable we apply to Unit of Work pattern to send many small updates in batches.
2525
2626
In plain words
2727

28-
> Unit of Work merges many small database updates in single batch to optimize the number of
28+
> Unit of Work merges many small database updates in a single batch to optimize the number of
2929
> round-trips.
3030
3131
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
@@ -35,37 +35,20 @@ In plain words
3535
3636
**Programmatic Example**
3737

38-
Here's the `Student` entity that is being persisted to the database.
38+
Here's the `Weapon` entity that is being persisted in the database.
3939

4040
```java
41-
public class Student {
42-
private final Integer id;
43-
private final String name;
44-
private final String address;
45-
46-
public Student(Integer id, String name, String address) {
47-
this.id = id;
48-
this.name = name;
49-
this.address = address;
50-
}
51-
52-
public String getName() {
53-
return name;
54-
}
55-
56-
public Integer getId() {
57-
return id;
58-
}
59-
60-
public String getAddress() {
61-
return address;
62-
}
41+
@Getter
42+
@RequiredArgsConstructor
43+
public class Weapon {
44+
private final Integer id;
45+
private final String name;
6346
}
6447
```
6548

66-
The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern.
49+
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern.
6750
It maintains a map of database operations (`context`) that need to be done and when `commit` is
68-
called it applies them in single batch.
51+
called it applies them in a single batch.
6952

7053
```java
7154
public interface IUnitOfWork<T> {
@@ -84,96 +67,117 @@ public interface IUnitOfWork<T> {
8467
}
8568

8669
@Slf4j
87-
public class StudentRepository implements IUnitOfWork<Student> {
88-
89-
private final Map<String, List<Student>> context;
90-
private final StudentDatabase studentDatabase;
91-
92-
public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
93-
this.context = context;
94-
this.studentDatabase = studentDatabase;
95-
}
96-
97-
@Override
98-
public void registerNew(Student student) {
99-
LOGGER.info("Registering {} for insert in context.", student.getName());
100-
register(student, IUnitOfWork.INSERT);
101-
}
102-
103-
@Override
104-
public void registerModified(Student student) {
105-
LOGGER.info("Registering {} for modify in context.", student.getName());
106-
register(student, IUnitOfWork.MODIFY);
107-
108-
}
109-
110-
@Override
111-
public void registerDeleted(Student student) {
112-
LOGGER.info("Registering {} for delete in context.", student.getName());
113-
register(student, IUnitOfWork.DELETE);
114-
}
115-
116-
private void register(Student student, String operation) {
117-
var studentsToOperate = context.get(operation);
118-
if (studentsToOperate == null) {
119-
studentsToOperate = new ArrayList<>();
70+
@RequiredArgsConstructor
71+
public class ArmsDealer implements IUnitOfWork<Weapon> {
72+
73+
private final Map<String, List<Weapon>> context;
74+
private final WeaponDatabase weaponDatabase;
75+
76+
@Override
77+
public void registerNew(Weapon weapon) {
78+
LOGGER.info("Registering {} for insert in context.", weapon.getName());
79+
register(weapon, UnitActions.INSERT.getActionValue());
12080
}
121-
studentsToOperate.add(student);
122-
context.put(operation, studentsToOperate);
123-
}
124-
125-
@Override
126-
public void commit() {
127-
if (context == null || context.size() == 0) {
128-
return;
81+
82+
@Override
83+
public void registerModified(Weapon weapon) {
84+
LOGGER.info("Registering {} for modify in context.", weapon.getName());
85+
register(weapon, UnitActions.MODIFY.getActionValue());
86+
12987
}
130-
LOGGER.info("Commit started");
131-
if (context.containsKey(IUnitOfWork.INSERT)) {
132-
commitInsert();
88+
89+
@Override
90+
public void registerDeleted(Weapon weapon) {
91+
LOGGER.info("Registering {} for delete in context.", weapon.getName());
92+
register(weapon, UnitActions.DELETE.getActionValue());
13393
}
13494

135-
if (context.containsKey(IUnitOfWork.MODIFY)) {
136-
commitModify();
95+
private void register(Weapon weapon, String operation) {
96+
var weaponsToOperate = context.get(operation);
97+
if (weaponsToOperate == null) {
98+
weaponsToOperate = new ArrayList<>();
99+
}
100+
weaponsToOperate.add(weapon);
101+
context.put(operation, weaponsToOperate);
137102
}
138-
if (context.containsKey(IUnitOfWork.DELETE)) {
139-
commitDelete();
103+
104+
/**
105+
* All UnitOfWork operations are batched and executed together on commit only.
106+
*/
107+
@Override
108+
public void commit() {
109+
if (context == null || context.size() == 0) {
110+
return;
111+
}
112+
LOGGER.info("Commit started");
113+
if (context.containsKey(UnitActions.INSERT.getActionValue())) {
114+
commitInsert();
115+
}
116+
117+
if (context.containsKey(UnitActions.MODIFY.getActionValue())) {
118+
commitModify();
119+
}
120+
if (context.containsKey(UnitActions.DELETE.getActionValue())) {
121+
commitDelete();
122+
}
123+
LOGGER.info("Commit finished.");
140124
}
141-
LOGGER.info("Commit finished.");
142-
}
143-
144-
private void commitInsert() {
145-
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
146-
for (var student : studentsToBeInserted) {
147-
LOGGER.info("Saving {} to database.", student.getName());
148-
studentDatabase.insert(student);
125+
126+
private void commitInsert() {
127+
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
128+
for (var weapon : weaponsToBeInserted) {
129+
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
130+
weaponDatabase.insert(weapon);
131+
}
149132
}
150-
}
151133

152-
private void commitModify() {
153-
var modifiedStudents = context.get(IUnitOfWork.MODIFY);
154-
for (var student : modifiedStudents) {
155-
LOGGER.info("Modifying {} to database.", student.getName());
156-
studentDatabase.modify(student);
134+
private void commitModify() {
135+
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
136+
for (var weapon : modifiedWeapons) {
137+
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
138+
weaponDatabase.modify(weapon);
139+
}
157140
}
158-
}
159141

160-
private void commitDelete() {
161-
var deletedStudents = context.get(IUnitOfWork.DELETE);
162-
for (var student : deletedStudents) {
163-
LOGGER.info("Deleting {} to database.", student.getName());
164-
studentDatabase.delete(student);
142+
private void commitDelete() {
143+
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
144+
for (var weapon : deletedWeapons) {
145+
LOGGER.info("Scrapping {}.", weapon.getName());
146+
weaponDatabase.delete(weapon);
147+
}
165148
}
166-
}
167149
}
168150
```
169151

170-
Finally, here's how we use the `StudentRepository` and `commit` the transaction.
152+
Here is how the whole app is put together.
171153

172154
```java
173-
studentRepository.registerNew(ram);
174-
studentRepository.registerModified(shyam);
175-
studentRepository.registerDeleted(gopi);
176-
studentRepository.commit();
155+
// create some weapons
156+
var enchantedHammer = new Weapon(1, "enchanted hammer");
157+
var brokenGreatSword = new Weapon(2, "broken great sword");
158+
var silverTrident = new Weapon(3, "silver trident");
159+
160+
// create repository
161+
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(), new WeaponDatabase());
162+
163+
// perform operations on the weapons
164+
weaponRepository.registerNew(enchantedHammer);
165+
weaponRepository.registerModified(silverTrident);
166+
weaponRepository.registerDeleted(brokenGreatSword);
167+
weaponRepository.commit();
168+
```
169+
170+
Here is the console output.
171+
172+
```
173+
21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context.
174+
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context.
175+
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context.
176+
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started
177+
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack.
178+
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work.
179+
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword.
180+
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished.
177181
```
178182

179183
## Class diagram
@@ -186,7 +190,7 @@ Use the Unit Of Work pattern when
186190

187191
* To optimize the time taken for database transactions.
188192
* To send changes to database as a unit of work which ensures atomicity of the transaction.
189-
* To reduce number of database calls.
193+
* To reduce the number of database calls.
190194

191195
## Tutorials
192196

unit-of-work/etc/unit-of-work.ucls

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
33
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
4-
<class id="1" language="java" name="com.iluwatar.unitofwork.StudentDatabase" project="unit-of-work"
4+
<class id="1" language="java" name="com.iluwatar.unitofwork.WeaponDatabase" project="unit-of-work"
55
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java" binary="false" corner="BOTTOM_RIGHT">
66
<position height="-1" width="-1" x="170" y="406"/>
77
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
@@ -28,7 +28,7 @@
2828
<operations public="true" package="true" protected="true" private="true" static="true"/>
2929
</display>
3030
</class>
31-
<class id="4" language="java" name="com.iluwatar.unitofwork.StudentRepository" project="unit-of-work"
31+
<class id="4" language="java" name="com.iluwatar.unitofwork.ArmsDealer" project="unit-of-work"
3232
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java" binary="false"
3333
corner="BOTTOM_RIGHT">
3434
<position height="-1" width="-1" x="377" y="166"/>
@@ -38,7 +38,7 @@
3838
<operations public="true" package="true" protected="true" private="true" static="true"/>
3939
</display>
4040
</class>
41-
<class id="5" language="java" name="com.iluwatar.unitofwork.Student" project="unit-of-work"
41+
<class id="5" language="java" name="com.iluwatar.unitofwork.Weapon" project="unit-of-work"
4242
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java" binary="false" corner="BOTTOM_RIGHT">
4343
<position height="-1" width="-1" x="696" y="130"/>
4444
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"

unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java

+13-11
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import java.util.List;
2828

2929
/**
30-
* {@link App} Application for managing student data.
30+
* {@link App} Application demonstrating unit of work pattern.
3131
*/
3232
public class App {
3333
/**
@@ -37,17 +37,19 @@ public class App {
3737
*/
3838

3939
public static void main(String[] args) {
40-
var ram = new Student(1, "Ram", "Street 9, Cupertino");
41-
var shyam = new Student(2, "Shyam", "Z bridge, Pune");
42-
var gopi = new Student(3, "Gopi", "Street 10, Mumbai");
40+
// create some weapons
41+
var enchantedHammer = new Weapon(1, "enchanted hammer");
42+
var brokenGreatSword = new Weapon(2, "broken great sword");
43+
var silverTrident = new Weapon(3, "silver trident");
4344

44-
var context = new HashMap<String, List<Student>>();
45-
var studentDatabase = new StudentDatabase();
46-
var studentRepository = new StudentRepository(context, studentDatabase);
45+
// create repository
46+
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(),
47+
new WeaponDatabase());
4748

48-
studentRepository.registerNew(ram);
49-
studentRepository.registerModified(shyam);
50-
studentRepository.registerDeleted(gopi);
51-
studentRepository.commit();
49+
// perform operations on the weapons
50+
weaponRepository.registerNew(enchantedHammer);
51+
weaponRepository.registerModified(silverTrident);
52+
weaponRepository.registerDeleted(brokenGreatSword);
53+
weaponRepository.commit();
5254
}
5355
}

0 commit comments

Comments
 (0)