Compare commits

..

2 Commits

11 changed files with 361 additions and 228 deletions

View File

@ -9,6 +9,10 @@ tags:
- Reactive - Reactive
--- ---
## Name
Event Aggregator
## Intent ## Intent
A system with lots of objects can lead to complexities when a 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 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 of events for many objects. It registers for all the events of the many objects
allowing clients to register with just the aggregator. 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 ## Class diagram
![alt text](./etc/classes.png "Event Aggregator") ![alt text](./etc/classes.png "Event Aggregator")
@ -26,9 +160,13 @@ Use the Event Aggregator pattern when
* Event Aggregator is a good choice when you have lots of objects that are * 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 potential event sources. Rather than have the observer deal with registering
with them all, you can centralize the registration logic to the Event 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. simplifies the memory management issues in using observers.
## Related patterns
* [Observer](https://java-design-patterns.com/patterns/observer/)
## Credits ## Credits
* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html) * [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html)

View File

@ -17,19 +17,19 @@ and to interleave the execution of functions without hard coding them together.
## Explanation ## Explanation
Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer 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 style. For example, calculating Fibonacci accumulating sum and factorials. In these kinds of
recursion is more straightforward than their loop counterpart. Furthermore recursion may need less problems, recursion is more straightforward than its loop counterpart. Furthermore, recursion may
code and looks more concise. There is a saying that every recursion problem can be solved using need less code and looks more concise. There is a saying that every recursion problem can be solved
a loop with the cost of writing code that is more difficult to understand. 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 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. 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. stack.
Real world example Real-world example
> A recursive Fibonacci calculation without the stack overflow problem using the Trampoline pattern. > 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. Using the `Trampoline` to get Fibonacci values.
```java ```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) { if (times == 0) {
return Trampoline.done(prod); return Trampoline.done(prod);
} else { } 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: Program output:
``` ```
start pattern 19:22:24.462 [main] INFO com.iluwatar.trampoline.TrampolineApp - Start calculating war casualties
result 3628800 19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800
``` ```
## Class diagram ## Class diagram
@ -133,8 +135,8 @@ result 3628800
Use the Trampoline pattern when Use the Trampoline pattern when
* For implementing tail recursive function. This pattern allows to switch on a stackless operation. * For implementing tail-recursive functions. This pattern allows to switch on a stackless operation.
* For interleaving the execution of two or more functions on the same thread. * For interleaving execution of two or more functions on the same thread.
## Known uses ## Known uses

View File

@ -107,6 +107,4 @@ public interface Trampoline<T> {
} }
}; };
} }
} }

View File

@ -39,9 +39,9 @@ public class TrampolineApp {
* Main program for showing pattern. It does loop with factorial function. * Main program for showing pattern. It does loop with factorial function.
*/ */
public static void main(String[] args) { public static void main(String[] args) {
LOGGER.info("start pattern"); LOGGER.info("Start calculating war casualties");
var result = loop(10, 1).result(); 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)); return Trampoline.more(() -> loop(times - 1, prod * times));
} }
} }
} }

View File

@ -12,20 +12,20 @@ tags:
## Intent ## 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. persisted in one go to minimize database round-trips.
## Explanation ## Explanation
Real-world example Real world example
> Arms dealer has a database containing weapon information. Merchants all over the town are > We have a database containing student information. Administrators all over the country are
> constantly updating this information and it causes a high load on the database server. To make the > 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. > load more manageable we apply to Unit of Work pattern to send many small updates in batches.
In plain words 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. > round-trips.
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says [MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
@ -35,20 +35,37 @@ In plain words
**Programmatic Example** **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 ```java
@Getter public class Student {
@RequiredArgsConstructor
public class Weapon {
private final Integer id; private final Integer id;
private final String name; 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 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 ```java
public interface IUnitOfWork<T> { public interface IUnitOfWork<T> {
@ -67,117 +84,96 @@ public interface IUnitOfWork<T> {
} }
@Slf4j @Slf4j
@RequiredArgsConstructor public class StudentRepository implements IUnitOfWork<Student> {
public class ArmsDealer implements IUnitOfWork<Weapon> {
private final Map<String, List<Weapon>> context; private final Map<String, List<Student>> context;
private final WeaponDatabase weaponDatabase; private final StudentDatabase studentDatabase;
@Override public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
public void registerNew(Weapon weapon) { this.context = context;
LOGGER.info("Registering {} for insert in context.", weapon.getName()); this.studentDatabase = studentDatabase;
register(weapon, UnitActions.INSERT.getActionValue());
} }
@Override @Override
public void registerModified(Weapon weapon) { public void registerNew(Student student) {
LOGGER.info("Registering {} for modify in context.", weapon.getName()); LOGGER.info("Registering {} for insert in context.", student.getName());
register(weapon, UnitActions.MODIFY.getActionValue()); register(student, IUnitOfWork.INSERT);
}
@Override
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, IUnitOfWork.MODIFY);
} }
@Override @Override
public void registerDeleted(Weapon weapon) { public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", weapon.getName()); LOGGER.info("Registering {} for delete in context.", student.getName());
register(weapon, UnitActions.DELETE.getActionValue()); register(student, IUnitOfWork.DELETE);
} }
private void register(Weapon weapon, String operation) { private void register(Student student, String operation) {
var weaponsToOperate = context.get(operation); var studentsToOperate = context.get(operation);
if (weaponsToOperate == null) { if (studentsToOperate == null) {
weaponsToOperate = new ArrayList<>(); studentsToOperate = new ArrayList<>();
} }
weaponsToOperate.add(weapon); studentsToOperate.add(student);
context.put(operation, weaponsToOperate); context.put(operation, studentsToOperate);
} }
/**
* All UnitOfWork operations are batched and executed together on commit only.
*/
@Override @Override
public void commit() { public void commit() {
if (context == null || context.size() == 0) { if (context == null || context.size() == 0) {
return; return;
} }
LOGGER.info("Commit started"); LOGGER.info("Commit started");
if (context.containsKey(UnitActions.INSERT.getActionValue())) { if (context.containsKey(IUnitOfWork.INSERT)) {
commitInsert(); commitInsert();
} }
if (context.containsKey(UnitActions.MODIFY.getActionValue())) { if (context.containsKey(IUnitOfWork.MODIFY)) {
commitModify(); commitModify();
} }
if (context.containsKey(UnitActions.DELETE.getActionValue())) { if (context.containsKey(IUnitOfWork.DELETE)) {
commitDelete(); commitDelete();
} }
LOGGER.info("Commit finished."); LOGGER.info("Commit finished.");
} }
private void commitInsert() { private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue()); var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
for (var weapon : weaponsToBeInserted) { for (var student : studentsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName()); LOGGER.info("Saving {} to database.", student.getName());
weaponDatabase.insert(weapon); studentDatabase.insert(student);
} }
} }
private void commitModify() { private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue()); var modifiedStudents = context.get(IUnitOfWork.MODIFY);
for (var weapon : modifiedWeapons) { for (var student : modifiedStudents) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName()); LOGGER.info("Modifying {} to database.", student.getName());
weaponDatabase.modify(weapon); studentDatabase.modify(student);
} }
} }
private void commitDelete() { private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue()); var deletedStudents = context.get(IUnitOfWork.DELETE);
for (var weapon : deletedWeapons) { for (var student : deletedStudents) {
LOGGER.info("Scrapping {}.", weapon.getName()); LOGGER.info("Deleting {} to database.", student.getName());
weaponDatabase.delete(weapon); 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 ```java
// create some weapons studentRepository.registerNew(ram);
var enchantedHammer = new Weapon(1, "enchanted hammer"); studentRepository.registerModified(shyam);
var brokenGreatSword = new Weapon(2, "broken great sword"); studentRepository.registerDeleted(gopi);
var silverTrident = new Weapon(3, "silver trident"); studentRepository.commit();
// 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.
``` ```
## Class diagram ## Class diagram
@ -190,7 +186,7 @@ Use the Unit Of Work pattern when
* To optimize the time taken for database transactions. * 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 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 ## Tutorials

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true" <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"> 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"> 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"/> <position height="-1" width="-1" x="170" y="406"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" <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"/> <operations public="true" package="true" protected="true" private="true" static="true"/>
</display> </display>
</class> </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" file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java" binary="false"
corner="BOTTOM_RIGHT"> corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="377" y="166"/> <position height="-1" width="-1" x="377" y="166"/>
@ -38,7 +38,7 @@
<operations public="true" package="true" protected="true" private="true" static="true"/> <operations public="true" package="true" protected="true" private="true" static="true"/>
</display> </display>
</class> </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"> 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"/> <position height="-1" width="-1" x="696" y="130"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"

View File

@ -27,7 +27,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
/** /**
* {@link App} Application demonstrating unit of work pattern. * {@link App} Application for managing student data.
*/ */
public class App { public class App {
/** /**
@ -37,19 +37,17 @@ public class App {
*/ */
public static void main(String[] args) { public static void main(String[] args) {
// create some weapons var ram = new Student(1, "Ram", "Street 9, Cupertino");
var enchantedHammer = new Weapon(1, "enchanted hammer"); var shyam = new Student(2, "Shyam", "Z bridge, Pune");
var brokenGreatSword = new Weapon(2, "broken great sword"); var gopi = new Student(3, "Gopi", "Street 10, Mumbai");
var silverTrident = new Weapon(3, "silver trident");
// create repository var context = new HashMap<String, List<Student>>();
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(), var studentDatabase = new StudentDatabase();
new WeaponDatabase()); var studentRepository = new StudentRepository(context, studentDatabase);
// perform operations on the weapons studentRepository.registerNew(ram);
weaponRepository.registerNew(enchantedHammer); studentRepository.registerModified(shyam);
weaponRepository.registerModified(silverTrident); studentRepository.registerDeleted(gopi);
weaponRepository.registerDeleted(brokenGreatSword); studentRepository.commit();
weaponRepository.commit();
} }
} }

View File

@ -27,12 +27,14 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
/** /**
* {@link Weapon} is an entity. * {@link Student} is an entity.
*/ */
@Getter @Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public class Weapon { public class Student {
private final Integer id; private final Integer id;
private final String name; private final String name;
private final String address;
} }

View File

@ -24,19 +24,19 @@
package com.iluwatar.unitofwork; 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 //Some insert logic to DB
} }
public void modify(Weapon weapon) { public void modify(Student student) {
//Some modify logic to DB //Some modify logic to DB
} }
public void delete(Weapon weapon) { public void delete(Student student) {
//Some delete logic to DB //Some delete logic to DB
} }
} }

View File

@ -30,41 +30,41 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; 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 @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class ArmsDealer implements IUnitOfWork<Weapon> { public class StudentRepository implements IUnitOfWork<Student> {
private final Map<String, List<Weapon>> context; private final Map<String, List<Student>> context;
private final WeaponDatabase weaponDatabase; private final StudentDatabase studentDatabase;
@Override @Override
public void registerNew(Weapon weapon) { public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", weapon.getName()); LOGGER.info("Registering {} for insert in context.", student.getName());
register(weapon, UnitActions.INSERT.getActionValue()); register(student, UnitActions.INSERT.getActionValue());
} }
@Override @Override
public void registerModified(Weapon weapon) { public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", weapon.getName()); LOGGER.info("Registering {} for modify in context.", student.getName());
register(weapon, UnitActions.MODIFY.getActionValue()); register(student, UnitActions.MODIFY.getActionValue());
} }
@Override @Override
public void registerDeleted(Weapon weapon) { public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", weapon.getName()); LOGGER.info("Registering {} for delete in context.", student.getName());
register(weapon, UnitActions.DELETE.getActionValue()); register(student, UnitActions.DELETE.getActionValue());
} }
private void register(Weapon weapon, String operation) { private void register(Student student, String operation) {
var weaponsToOperate = context.get(operation); var studentsToOperate = context.get(operation);
if (weaponsToOperate == null) { if (studentsToOperate == null) {
weaponsToOperate = new ArrayList<>(); studentsToOperate = new ArrayList<>();
} }
weaponsToOperate.add(weapon); studentsToOperate.add(student);
context.put(operation, weaponsToOperate); context.put(operation, studentsToOperate);
} }
/** /**
@ -90,26 +90,26 @@ public class ArmsDealer implements IUnitOfWork<Weapon> {
} }
private void commitInsert() { private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue()); var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var weapon : weaponsToBeInserted) { for (var student : studentsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName()); LOGGER.info("Saving {} to database.", student.getName());
weaponDatabase.insert(weapon); studentDatabase.insert(student);
} }
} }
private void commitModify() { private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue()); var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue());
for (var weapon : modifiedWeapons) { for (var student : modifiedStudents) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName()); LOGGER.info("Modifying {} to database.", student.getName());
weaponDatabase.modify(weapon); studentDatabase.modify(student);
} }
} }
private void commitDelete() { private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue()); var deletedStudents = context.get(UnitActions.DELETE.getActionValue());
for (var weapon : deletedWeapons) { for (var student : deletedStudents) {
LOGGER.info("Scrapping {}.", weapon.getName()); LOGGER.info("Deleting {} to database.", student.getName());
weaponDatabase.delete(weapon); studentDatabase.delete(student);
} }
} }
} }

View File

@ -36,102 +36,102 @@ import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
/** /**
* tests {@link ArmsDealer} * tests {@link StudentRepository}
*/ */
class ArmsDealerTest { class StudentRepositoryTest {
private final Weapon weapon1 = new Weapon(1, "battle ram"); private final Student student1 = new Student(1, "Ram", "street 9, cupertino");
private final Weapon weapon2 = new Weapon(1, "wooden lance"); private final Student student2 = new Student(1, "Sham", "Z bridge, pune");
private final Map<String, List<Weapon>> context = new HashMap<>(); private final Map<String, List<Student>> context = new HashMap<>();
private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class); private final StudentDatabase studentDatabase = mock(StudentDatabase.class);
private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);; private final StudentRepository studentRepository = new StudentRepository(context, studentDatabase);;
@Test @Test
void shouldSaveNewStudentWithoutWritingToDb() { void shouldSaveNewStudentWithoutWritingToDb() {
armsDealer.registerNew(weapon1); studentRepository.registerNew(student1);
armsDealer.registerNew(weapon2); studentRepository.registerNew(student2);
assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size()); assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase); verifyNoMoreInteractions(studentDatabase);
} }
@Test @Test
void shouldSaveDeletedStudentWithoutWritingToDb() { void shouldSaveDeletedStudentWithoutWritingToDb() {
armsDealer.registerDeleted(weapon1); studentRepository.registerDeleted(student1);
armsDealer.registerDeleted(weapon2); studentRepository.registerDeleted(student2);
assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size()); assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase); verifyNoMoreInteractions(studentDatabase);
} }
@Test @Test
void shouldSaveModifiedStudentWithoutWritingToDb() { void shouldSaveModifiedStudentWithoutWritingToDb() {
armsDealer.registerModified(weapon1); studentRepository.registerModified(student1);
armsDealer.registerModified(weapon2); studentRepository.registerModified(student2);
assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size()); assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase); verifyNoMoreInteractions(studentDatabase);
} }
@Test @Test
void shouldSaveAllLocalChangesToDb() { void shouldSaveAllLocalChangesToDb() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1)); context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1)); context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1)); context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit(); studentRepository.commit();
verify(weaponDatabase, times(1)).insert(weapon1); verify(studentDatabase, times(1)).insert(student1);
verify(weaponDatabase, times(1)).modify(weapon1); verify(studentDatabase, times(1)).modify(student1);
verify(weaponDatabase, times(1)).delete(weapon1); verify(studentDatabase, times(1)).delete(student1);
} }
@Test @Test
void shouldNotWriteToDbIfContextIsNull() { void shouldNotWriteToDbIfContextIsNull() {
var weaponRepository = new ArmsDealer(null, weaponDatabase); var studentRepository = new StudentRepository(null, studentDatabase);
weaponRepository.commit(); studentRepository.commit();
verifyNoMoreInteractions(weaponDatabase); verifyNoMoreInteractions(studentDatabase);
} }
@Test @Test
void shouldNotWriteToDbIfNothingToCommit() { 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 @Test
void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() { void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() {
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1)); context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1)); context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit(); studentRepository.commit();
verify(weaponDatabase, never()).insert(weapon1); verify(studentDatabase, never()).insert(student1);
} }
@Test @Test
void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() { void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1)); context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1)); context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit(); studentRepository.commit();
verify(weaponDatabase, never()).modify(weapon1); verify(studentDatabase, never()).modify(student1);
} }
@Test @Test
void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() { void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1)); context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1)); context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
armsDealer.commit(); studentRepository.commit();
verify(weaponDatabase, never()).delete(weapon1); verify(studentDatabase, never()).delete(student1);
} }
} }