Compare commits
2 Commits
refactorUn
...
updateTram
Author | SHA1 | Date | |
---|---|---|---|
5681340ec7 | |||
c66ca67201 |
@ -9,6 +9,10 @@ tags:
|
||||
- Reactive
|
||||
---
|
||||
|
||||
## Name
|
||||
|
||||
Event Aggregator
|
||||
|
||||
## Intent
|
||||
A system with lots of objects can lead to complexities when a
|
||||
client wants to subscribe to events. The client has to find and register for
|
||||
@ -17,6 +21,136 @@ requires a separate subscription. An Event Aggregator acts as a single source
|
||||
of events for many objects. It registers for all the events of the many objects
|
||||
allowing clients to register with just the aggregator.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
|
||||
> King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most
|
||||
> of his critical information from King's Hand, the second in command. King's hand has many
|
||||
> close advisors himself, feeding him with relevant information about events occurring in the
|
||||
> kingdom.
|
||||
|
||||
In Plain Words
|
||||
|
||||
> Event Aggregator is an event mediator that collects events from multiple sources and delivers
|
||||
> them to registered observers.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of
|
||||
the objects are event listeners, some are event emitters, and the event aggregator does both.
|
||||
|
||||
```java
|
||||
public interface EventObserver {
|
||||
void onEvent(Event e);
|
||||
}
|
||||
|
||||
public abstract class EventEmitter {
|
||||
|
||||
private final Map<Event, List<EventObserver>> observerLists;
|
||||
|
||||
public EventEmitter() {
|
||||
observerLists = new HashMap<>();
|
||||
}
|
||||
|
||||
public final void registerObserver(EventObserver obs, Event e) {
|
||||
...
|
||||
}
|
||||
|
||||
protected void notifyObservers(Event e) {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`KingJoffrey` is listening to events from `KingsHand`.
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class KingJoffrey implements EventObserver {
|
||||
@Override
|
||||
public void onEvent(Event e) {
|
||||
LOGGER.info("Received event from the King's Hand: {}", e.toString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`.
|
||||
Whatever he hears from them, he delivers to `KingJoffrey`.
|
||||
|
||||
```java
|
||||
public class KingsHand extends EventEmitter implements EventObserver {
|
||||
|
||||
public KingsHand() {
|
||||
}
|
||||
|
||||
public KingsHand(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event e) {
|
||||
notifyObservers(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For example, `LordVarys` finds a traitor every Sunday and notifies the `KingsHand`.
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class LordVarys extends EventEmitter implements EventObserver {
|
||||
@Override
|
||||
public void timePasses(Weekday day) {
|
||||
if (day == Weekday.SATURDAY) {
|
||||
notifyObservers(Event.TRAITOR_DETECTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following snippet demonstrates how the objects are constructed and wired together.
|
||||
|
||||
```java
|
||||
var kingJoffrey = new KingJoffrey();
|
||||
|
||||
var kingsHand = new KingsHand();
|
||||
kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED);
|
||||
kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED);
|
||||
kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING);
|
||||
kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED);
|
||||
|
||||
var varys = new LordVarys();
|
||||
varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED);
|
||||
varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED);
|
||||
|
||||
var scout = new Scout();
|
||||
scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING);
|
||||
scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED);
|
||||
|
||||
var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED);
|
||||
|
||||
var emitters = List.of(
|
||||
kingsHand,
|
||||
baelish,
|
||||
varys,
|
||||
scout
|
||||
);
|
||||
|
||||
Arrays.stream(Weekday.values())
|
||||
.<Consumer<? super EventEmitter>>map(day -> emitter -> emitter.timePasses(day))
|
||||
.forEachOrdered(emitters::forEach);
|
||||
```
|
||||
|
||||
The console output after running the example.
|
||||
|
||||
```
|
||||
18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching
|
||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted
|
||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted
|
||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected
|
||||
```
|
||||
|
||||
## Class diagram
|
||||

|
||||
|
||||
@ -26,9 +160,13 @@ Use the Event Aggregator pattern when
|
||||
* Event Aggregator is a good choice when you have lots of objects that are
|
||||
potential event sources. Rather than have the observer deal with registering
|
||||
with them all, you can centralize the registration logic to the Event
|
||||
Aggregator. As well as simplifying registration, a Event Aggregator also
|
||||
Aggregator. As well as simplifying registration, an Event Aggregator also
|
||||
simplifies the memory management issues in using observers.
|
||||
|
||||
## Related patterns
|
||||
|
||||
* [Observer](https://java-design-patterns.com/patterns/observer/)
|
||||
|
||||
## Credits
|
||||
|
||||
* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html)
|
||||
|
@ -17,19 +17,19 @@ and to interleave the execution of functions without hard coding them together.
|
||||
## Explanation
|
||||
|
||||
Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer
|
||||
style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems
|
||||
recursion is more straightforward than their loop counterpart. Furthermore recursion may need less
|
||||
code and looks more concise. There is a saying that every recursion problem can be solved using
|
||||
a loop with the cost of writing code that is more difficult to understand.
|
||||
style. For example, calculating Fibonacci accumulating sum and factorials. In these kinds of
|
||||
problems, recursion is more straightforward than its loop counterpart. Furthermore, recursion may
|
||||
need less code and looks more concise. There is a saying that every recursion problem can be solved
|
||||
using a loop with the cost of writing code that is more difficult to understand.
|
||||
|
||||
However recursion type solutions have one big caveat. For each recursive call it typically needs
|
||||
However, recursion-type solutions have one big caveat. For each recursive call, it typically needs
|
||||
an intermediate value stored and there is a limited amount of stack memory available. Running out of
|
||||
stack memory creates a stack overflow error and halts the program execution.
|
||||
|
||||
Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the
|
||||
Trampoline pattern is a trick that allows defining recursive algorithms in Java without blowing the
|
||||
stack.
|
||||
|
||||
Real world example
|
||||
Real-world example
|
||||
|
||||
> A recursive Fibonacci calculation without the stack overflow problem using the Trampoline pattern.
|
||||
|
||||
@ -105,24 +105,26 @@ public interface Trampoline<T> {
|
||||
Using the `Trampoline` to get Fibonacci values.
|
||||
|
||||
```java
|
||||
public static Trampoline<Integer> loop(int times, int prod) {
|
||||
public static void main(String[] args) {
|
||||
LOGGER.info("Start calculating war casualties");
|
||||
var result = loop(10, 1).result();
|
||||
LOGGER.info("The number of orcs perished in the war: {}", result);
|
||||
}
|
||||
|
||||
public static Trampoline<Integer> loop(int times, int prod) {
|
||||
if (times == 0) {
|
||||
return Trampoline.done(prod);
|
||||
return Trampoline.done(prod);
|
||||
} else {
|
||||
return Trampoline.more(() -> loop(times - 1, prod * times));
|
||||
return Trampoline.more(() -> loop(times - 1, prod * times));
|
||||
}
|
||||
}
|
||||
|
||||
log.info("start pattern");
|
||||
var result = loop(10, 1).result();
|
||||
log.info("result {}", result);
|
||||
}
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```
|
||||
start pattern
|
||||
result 3628800
|
||||
19:22:24.462 [main] INFO com.iluwatar.trampoline.TrampolineApp - Start calculating war casualties
|
||||
19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -133,8 +135,8 @@ result 3628800
|
||||
|
||||
Use the Trampoline pattern when
|
||||
|
||||
* For implementing tail recursive function. This pattern allows to switch on a stackless operation.
|
||||
* For interleaving the execution of two or more functions on the same thread.
|
||||
* For implementing tail-recursive functions. This pattern allows to switch on a stackless operation.
|
||||
* For interleaving execution of two or more functions on the same thread.
|
||||
|
||||
## Known uses
|
||||
|
||||
|
@ -107,6 +107,4 @@ public interface Trampoline<T> {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ public class TrampolineApp {
|
||||
* Main program for showing pattern. It does loop with factorial function.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
LOGGER.info("start pattern");
|
||||
LOGGER.info("Start calculating war casualties");
|
||||
var result = loop(10, 1).result();
|
||||
LOGGER.info("result {}", result);
|
||||
LOGGER.info("The number of orcs perished in the war: {}", result);
|
||||
|
||||
}
|
||||
|
||||
@ -55,5 +55,4 @@ public class TrampolineApp {
|
||||
return Trampoline.more(() -> loop(times - 1, prod * times));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,20 +12,20 @@ tags:
|
||||
|
||||
## Intent
|
||||
|
||||
When a business transaction is completed, all the updates are sent as one big unit of work to be
|
||||
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.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
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
|
||||
> 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.
|
||||
|
||||
In plain words
|
||||
|
||||
> Unit of Work merges many small database updates in a single batch to optimize the number of
|
||||
> Unit of Work merges many small database updates in single batch to optimize the number of
|
||||
> round-trips.
|
||||
|
||||
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
|
||||
@ -35,20 +35,37 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Here's the `Weapon` entity that is being persisted in the database.
|
||||
Here's the `Student` entity that is being persisted to the database.
|
||||
|
||||
```java
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class Weapon {
|
||||
private final Integer id;
|
||||
private final String name;
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern.
|
||||
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 a single batch.
|
||||
called it applies them in single batch.
|
||||
|
||||
```java
|
||||
public interface IUnitOfWork<T> {
|
||||
@ -67,117 +84,96 @@ public interface IUnitOfWork<T> {
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ArmsDealer implements IUnitOfWork<Weapon> {
|
||||
public class StudentRepository implements IUnitOfWork<Student> {
|
||||
|
||||
private final Map<String, List<Weapon>> context;
|
||||
private final WeaponDatabase weaponDatabase;
|
||||
private final Map<String, List<Student>> context;
|
||||
private final StudentDatabase studentDatabase;
|
||||
|
||||
@Override
|
||||
public void registerNew(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for insert in context.", weapon.getName());
|
||||
register(weapon, UnitActions.INSERT.getActionValue());
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerModified(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for modify in context.", weapon.getName());
|
||||
register(weapon, UnitActions.MODIFY.getActionValue());
|
||||
|
||||
if (context.containsKey(IUnitOfWork.MODIFY)) {
|
||||
commitModify();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleted(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for delete in context.", weapon.getName());
|
||||
register(weapon, UnitActions.DELETE.getActionValue());
|
||||
if (context.containsKey(IUnitOfWork.DELETE)) {
|
||||
commitDelete();
|
||||
}
|
||||
LOGGER.info("Commit finished.");
|
||||
}
|
||||
|
||||
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);
|
||||
private void commitInsert() {
|
||||
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
|
||||
for (var student : studentsToBeInserted) {
|
||||
LOGGER.info("Saving {} to database.", student.getName());
|
||||
studentDatabase.insert(student);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitDelete() {
|
||||
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
|
||||
for (var weapon : deletedWeapons) {
|
||||
LOGGER.info("Scrapping {}.", weapon.getName());
|
||||
weaponDatabase.delete(weapon);
|
||||
}
|
||||
private void commitDelete() {
|
||||
var deletedStudents = context.get(IUnitOfWork.DELETE);
|
||||
for (var student : deletedStudents) {
|
||||
LOGGER.info("Deleting {} to database.", student.getName());
|
||||
studentDatabase.delete(student);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is how the whole app is put together.
|
||||
Finally, here's how we use the `StudentRepository` and `commit` the transaction.
|
||||
|
||||
```java
|
||||
// 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.
|
||||
studentRepository.registerNew(ram);
|
||||
studentRepository.registerModified(shyam);
|
||||
studentRepository.registerDeleted(gopi);
|
||||
studentRepository.commit();
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -190,7 +186,7 @@ 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.
|
||||
* To reduce number of database calls.
|
||||
|
||||
## Tutorials
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
|
||||
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
|
||||
<class id="1" language="java" name="com.iluwatar.unitofwork.WeaponDatabase" project="unit-of-work"
|
||||
<class id="1" language="java" name="com.iluwatar.unitofwork.StudentDatabase" project="unit-of-work"
|
||||
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="170" y="406"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
@ -28,7 +28,7 @@
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="4" language="java" name="com.iluwatar.unitofwork.ArmsDealer" project="unit-of-work"
|
||||
<class id="4" language="java" name="com.iluwatar.unitofwork.StudentRepository" project="unit-of-work"
|
||||
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="377" y="166"/>
|
||||
@ -38,7 +38,7 @@
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="5" language="java" name="com.iluwatar.unitofwork.Weapon" project="unit-of-work"
|
||||
<class id="5" language="java" name="com.iluwatar.unitofwork.Student" project="unit-of-work"
|
||||
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="696" y="130"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
|
@ -27,7 +27,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link App} Application demonstrating unit of work pattern.
|
||||
* {@link App} Application for managing student data.
|
||||
*/
|
||||
public class App {
|
||||
/**
|
||||
@ -37,19 +37,17 @@ public class App {
|
||||
*/
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 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");
|
||||
var ram = new Student(1, "Ram", "Street 9, Cupertino");
|
||||
var shyam = new Student(2, "Shyam", "Z bridge, Pune");
|
||||
var gopi = new Student(3, "Gopi", "Street 10, Mumbai");
|
||||
|
||||
// create repository
|
||||
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(),
|
||||
new WeaponDatabase());
|
||||
var context = new HashMap<String, List<Student>>();
|
||||
var studentDatabase = new StudentDatabase();
|
||||
var studentRepository = new StudentRepository(context, studentDatabase);
|
||||
|
||||
// perform operations on the weapons
|
||||
weaponRepository.registerNew(enchantedHammer);
|
||||
weaponRepository.registerModified(silverTrident);
|
||||
weaponRepository.registerDeleted(brokenGreatSword);
|
||||
weaponRepository.commit();
|
||||
studentRepository.registerNew(ram);
|
||||
studentRepository.registerModified(shyam);
|
||||
studentRepository.registerDeleted(gopi);
|
||||
studentRepository.commit();
|
||||
}
|
||||
}
|
||||
|
@ -27,12 +27,14 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* {@link Weapon} is an entity.
|
||||
* {@link Student} is an entity.
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class Weapon {
|
||||
public class Student {
|
||||
|
||||
private final Integer id;
|
||||
private final String name;
|
||||
private final String address;
|
||||
|
||||
}
|
@ -24,19 +24,19 @@
|
||||
package com.iluwatar.unitofwork;
|
||||
|
||||
/**
|
||||
* Act as database for weapon records.
|
||||
* Act as Database for student records.
|
||||
*/
|
||||
public class WeaponDatabase {
|
||||
public class StudentDatabase {
|
||||
|
||||
public void insert(Weapon weapon) {
|
||||
public void insert(Student student) {
|
||||
//Some insert logic to DB
|
||||
}
|
||||
|
||||
public void modify(Weapon weapon) {
|
||||
public void modify(Student student) {
|
||||
//Some modify logic to DB
|
||||
}
|
||||
|
||||
public void delete(Weapon weapon) {
|
||||
public void delete(Student student) {
|
||||
//Some delete logic to DB
|
||||
}
|
||||
}
|
@ -30,41 +30,41 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* {@link ArmsDealer} Weapon repository that supports unit of work for weapons.
|
||||
* {@link StudentRepository} Student database repository. supports unit of work for student data.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ArmsDealer implements IUnitOfWork<Weapon> {
|
||||
public class StudentRepository implements IUnitOfWork<Student> {
|
||||
|
||||
private final Map<String, List<Weapon>> context;
|
||||
private final WeaponDatabase weaponDatabase;
|
||||
private final Map<String, List<Student>> context;
|
||||
private final StudentDatabase studentDatabase;
|
||||
|
||||
@Override
|
||||
public void registerNew(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for insert in context.", weapon.getName());
|
||||
register(weapon, UnitActions.INSERT.getActionValue());
|
||||
public void registerNew(Student student) {
|
||||
LOGGER.info("Registering {} for insert in context.", student.getName());
|
||||
register(student, UnitActions.INSERT.getActionValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerModified(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for modify in context.", weapon.getName());
|
||||
register(weapon, UnitActions.MODIFY.getActionValue());
|
||||
public void registerModified(Student student) {
|
||||
LOGGER.info("Registering {} for modify in context.", student.getName());
|
||||
register(student, UnitActions.MODIFY.getActionValue());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleted(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for delete in context.", weapon.getName());
|
||||
register(weapon, UnitActions.DELETE.getActionValue());
|
||||
public void registerDeleted(Student student) {
|
||||
LOGGER.info("Registering {} for delete in context.", student.getName());
|
||||
register(student, UnitActions.DELETE.getActionValue());
|
||||
}
|
||||
|
||||
private void register(Weapon weapon, String operation) {
|
||||
var weaponsToOperate = context.get(operation);
|
||||
if (weaponsToOperate == null) {
|
||||
weaponsToOperate = new ArrayList<>();
|
||||
private void register(Student student, String operation) {
|
||||
var studentsToOperate = context.get(operation);
|
||||
if (studentsToOperate == null) {
|
||||
studentsToOperate = new ArrayList<>();
|
||||
}
|
||||
weaponsToOperate.add(weapon);
|
||||
context.put(operation, weaponsToOperate);
|
||||
studentsToOperate.add(student);
|
||||
context.put(operation, studentsToOperate);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,26 +90,26 @@ public class ArmsDealer implements IUnitOfWork<Weapon> {
|
||||
}
|
||||
|
||||
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);
|
||||
var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
|
||||
for (var student : studentsToBeInserted) {
|
||||
LOGGER.info("Saving {} to database.", student.getName());
|
||||
studentDatabase.insert(student);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue());
|
||||
for (var student : modifiedStudents) {
|
||||
LOGGER.info("Modifying {} to database.", student.getName());
|
||||
studentDatabase.modify(student);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitDelete() {
|
||||
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
|
||||
for (var weapon : deletedWeapons) {
|
||||
LOGGER.info("Scrapping {}.", weapon.getName());
|
||||
weaponDatabase.delete(weapon);
|
||||
var deletedStudents = context.get(UnitActions.DELETE.getActionValue());
|
||||
for (var student : deletedStudents) {
|
||||
LOGGER.info("Deleting {} to database.", student.getName());
|
||||
studentDatabase.delete(student);
|
||||
}
|
||||
}
|
||||
}
|
@ -36,102 +36,102 @@ import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* tests {@link ArmsDealer}
|
||||
* tests {@link StudentRepository}
|
||||
*/
|
||||
|
||||
class ArmsDealerTest {
|
||||
private final Weapon weapon1 = new Weapon(1, "battle ram");
|
||||
private final Weapon weapon2 = new Weapon(1, "wooden lance");
|
||||
class StudentRepositoryTest {
|
||||
private final Student student1 = new Student(1, "Ram", "street 9, cupertino");
|
||||
private final Student student2 = new Student(1, "Sham", "Z bridge, pune");
|
||||
|
||||
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 Map<String, List<Student>> context = new HashMap<>();
|
||||
private final StudentDatabase studentDatabase = mock(StudentDatabase.class);
|
||||
private final StudentRepository studentRepository = new StudentRepository(context, studentDatabase);;
|
||||
|
||||
@Test
|
||||
void shouldSaveNewStudentWithoutWritingToDb() {
|
||||
armsDealer.registerNew(weapon1);
|
||||
armsDealer.registerNew(weapon2);
|
||||
studentRepository.registerNew(student1);
|
||||
studentRepository.registerNew(student2);
|
||||
|
||||
assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size());
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveDeletedStudentWithoutWritingToDb() {
|
||||
armsDealer.registerDeleted(weapon1);
|
||||
armsDealer.registerDeleted(weapon2);
|
||||
studentRepository.registerDeleted(student1);
|
||||
studentRepository.registerDeleted(student2);
|
||||
|
||||
assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size());
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveModifiedStudentWithoutWritingToDb() {
|
||||
armsDealer.registerModified(weapon1);
|
||||
armsDealer.registerModified(weapon2);
|
||||
studentRepository.registerModified(student1);
|
||||
studentRepository.registerModified(student2);
|
||||
|
||||
assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size());
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveAllLocalChangesToDb() {
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||
|
||||
armsDealer.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verify(weaponDatabase, times(1)).insert(weapon1);
|
||||
verify(weaponDatabase, times(1)).modify(weapon1);
|
||||
verify(weaponDatabase, times(1)).delete(weapon1);
|
||||
verify(studentDatabase, times(1)).insert(student1);
|
||||
verify(studentDatabase, times(1)).modify(student1);
|
||||
verify(studentDatabase, times(1)).delete(student1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWriteToDbIfContextIsNull() {
|
||||
var weaponRepository = new ArmsDealer(null, weaponDatabase);
|
||||
var studentRepository = new StudentRepository(null, studentDatabase);
|
||||
|
||||
weaponRepository.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWriteToDbIfNothingToCommit() {
|
||||
var weaponRepository = new ArmsDealer(new HashMap<>(), weaponDatabase);
|
||||
var studentRepository = new StudentRepository(new HashMap<>(), studentDatabase);
|
||||
|
||||
weaponRepository.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() {
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||
|
||||
armsDealer.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verify(weaponDatabase, never()).insert(weapon1);
|
||||
verify(studentDatabase, never()).insert(student1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() {
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||
|
||||
armsDealer.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verify(weaponDatabase, never()).modify(weapon1);
|
||||
verify(studentDatabase, never()).modify(student1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() {
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||
|
||||
armsDealer.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verify(weaponDatabase, never()).delete(weapon1);
|
||||
verify(studentDatabase, never()).delete(student1);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user