201 lines
5.5 KiB
Markdown
Raw Permalink Normal View History

---
layout: pattern
title: Unit Of Work
folder: unit-of-work
permalink: /patterns/unit-of-work/
2017-09-24 12:56:06 +05:30
categories: Architectural
tags:
- Data access
2020-07-26 12:10:48 +03:00
- Performance
---
## Intent
2020-08-18 20:07:47 +03:00
When a business transaction is completed, all the the updates are sent as one big unit of work to be
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
2020-07-26 12:10:48 +03:00
Real world example
2020-08-18 20:07:47 +03:00
> We have a database containing student information. Administrators all over the country are
> constantly updating this information and it causes 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.
2020-07-26 12:10:48 +03:00
In plain words
2020-08-18 20:07:47 +03:00
> Unit of Work merges many small database updates in single batch to optimize the number of
> 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**
Here's the `Student` entity that is being persisted to the database.
```java
public class Student {
private final Integer id;
private final String name;
private final String address;
public Student(Integer id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Integer getId() {
return id;
}
public String getAddress() {
return address;
}
}
```
2020-08-18 20:07:47 +03:00
The essence of the implementation is the `StudentRepository` 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 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();
}
@Slf4j
2020-07-26 12:10:48 +03:00
public class StudentRepository implements IUnitOfWork<Student> {
private final Map<String, List<Student>> context;
private final StudentDatabase studentDatabase;
2020-07-26 12:10:48 +03:00
public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
this.context = context;
this.studentDatabase = studentDatabase;
}
@Override
public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", student.getName());
register(student, IUnitOfWork.INSERT);
}
@Override
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, IUnitOfWork.MODIFY);
}
@Override
public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", student.getName());
register(student, IUnitOfWork.DELETE);
}
private void register(Student student, String operation) {
var studentsToOperate = context.get(operation);
if (studentsToOperate == null) {
studentsToOperate = new ArrayList<>();
}
studentsToOperate.add(student);
context.put(operation, studentsToOperate);
}
@Override
public void commit() {
if (context == null || context.size() == 0) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(IUnitOfWork.INSERT)) {
commitInsert();
}
if (context.containsKey(IUnitOfWork.MODIFY)) {
commitModify();
}
if (context.containsKey(IUnitOfWork.DELETE)) {
commitDelete();
}
LOGGER.info("Commit finished.");
}
private void commitInsert() {
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
for (var student : studentsToBeInserted) {
LOGGER.info("Saving {} to database.", student.getName());
studentDatabase.insert(student);
}
}
private void commitModify() {
var modifiedStudents = context.get(IUnitOfWork.MODIFY);
for (var student : modifiedStudents) {
LOGGER.info("Modifying {} to database.", student.getName());
studentDatabase.modify(student);
}
}
private void commitDelete() {
var deletedStudents = context.get(IUnitOfWork.DELETE);
for (var student : deletedStudents) {
LOGGER.info("Deleting {} to database.", student.getName());
studentDatabase.delete(student);
}
}
}
```
2020-08-18 20:07:47 +03:00
Finally, here's how we use the `StudentRepository` and `commit` the transaction.
2020-07-26 12:10:48 +03:00
```java
studentRepository.registerNew(ram);
studentRepository.registerModified(shyam);
studentRepository.registerDeleted(gopi);
studentRepository.commit();
```
## Class diagram
2020-08-18 20:07:47 +03:00
![alt text](etc/unit-of-work.urm.png "unit-of-work")
## Applicability
2020-08-18 20:07:47 +03:00
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.
* To reduce number of database calls.
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/)
## 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)