docs: update notification

This commit is contained in:
Ilkka Seppälä
2024-05-13 21:01:54 +03:00
parent e75fd3743e
commit d27466a542
2 changed files with 77 additions and 199 deletions
+73 -192
View File
@@ -1,251 +1,132 @@
title: Notification
category: Behavioural
category: Behavioral
language: en
tags:
- Decoupling
- Presentation
- Domain
- Asynchronous
- Decoupling
- Event-driven
- Messaging
- Publish/subscribe
---
## Also known as
* Event Listener
## Intent
To capture information about errors and other information that occurs in the domain layer
which acts as the internal logic and validation tool. The notification then communicates this information
back to the presentation for handling and displaying to the user what errors have occurred and why.
The Notification design pattern aims to facilitate communication between different parts of a system by allowing objects to subscribe to specific events and receive updates asynchronously when those events occur.
## Explanation
Real world example
> You need to register a worker for your company. The information submitted needs to be valid
> before the worker can be added to the database, and if there are any errors you need to know about them.
> Consider a weather alert system as a real-world analogous example of the Notification design pattern. In this system, a weather station collects data on weather conditions like temperature, humidity, and storm alerts. Multiple subscribers, such as news agencies, smartphone weather apps, and emergency services, are interested in receiving updates about specific weather events, like severe storms or extreme temperatures.
>
> When the weather station detects a significant event, it publishes this information. All subscribed entities receive these updates automatically without the weather station needing to know the details of these subscribers. For instance, a news agency might use this information to update its weather report, while emergency services might use it to prepare for potential disasters. This system exemplifies the Notification pattern's ability to decouple the publisher (the weather station) from its subscribers and deliver timely updates efficiently.
In plain words
> A notification is simply a way of collecting information about errors and communicating it to the user
> so that they are aware of them
> The Notification design pattern enables an object to automatically notify a list of interested observers about changes or events without knowing the specifics of the subscribers.
**Programatic example**
Building off the example of registering a worker for a company, we can now explore a coded example for a full picture
of how this pattern looks when coded up. For full code please visit the github repository.
To begin with, the user submits information to a form through the presentation layer of our software. The information
given to register a worker is the worker's name, occupation, and date of birth. The program will then make sure none of
these fields are blank (validation) and that the worker is over 18 years old. If there are any errors,
the program will inform the user.
The Notification pattern is used to capture information passed between layers, validate the information, and return any errors to the presentation layer if needed. It reduces coupling between the producer and consumer of events, enhances flexibility and reusability of components, and allows for dynamic subscription and unsubscription to events.
The code for the form is given below. This form acts as our presentation layer, taking input from the user and printing
output using a LOGGER (in this case). The form then gives this information to the service layer RegisterWorkerService
through a data transfer object (DTO).
In this example, we'll use a form submission scenario to demonstrate the Notification pattern. The form is used to register a worker with their name, occupation, and date of birth. The form data is passed to the domain layer for validation, and any errors are returned to the presentation layer.
The service handles information validation and the presentation can then check the notification stored within the DTO for
any errors and display them to the user if necessary. Otherwise, it will inform the user the submission was processed
successfully.
Here's the `RegisterWorkerForm` class, which acts as our presentation layer. It takes the worker's details as input and submits the form.
form:
```java
/**
* The form submitted by the user, part of the presentation layer,
* linked to the domain layer through a data transfer object and
* linked to the service layer directly.
*/
@Slf4j
public class RegisterWorkerForm {
String name;
String occupation;
LocalDate dateOfBirth;
RegisterWorkerDto worker;
/**
* Service super type which the form uses as part of its service layer.
*/
RegisterWorkerService service = new RegisterWorkerService();
class RegisterWorkerForm {
/**
* Creates the form.
*
* @param name name of worker
* @param occupation occupation of the worker
* @param dateOfBirth date of birth of the worker
*/
public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) {
this.name = name;
this.occupation = occupation;
this.dateOfBirth = dateOfBirth;
}
private RegisterWorkerForm registerWorkerForm;
/**
* Attempts to submit the form for registering a worker.
*/
public void submit() {
//Save worker information (like name, occupation, dob) to our transfer object to be communicated between layers
saveToWorker();
//call the service layer to register our worker
service.registerWorker(worker);
//check for any errors
if (worker.getNotification().hasErrors()) {
indicateErrors(); //displays errors to users
LOGGER.info("Not registered, see errors");
} else {
LOGGER.info("Registration Succeeded");
RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) {
// Initialize the form with the worker's details
}
void submit() {
// Submit the form
// If there are any errors, they will be captured in the worker's notification
}
}
...
}
```
The data transfer object (DTO) created stores the information submitted (name, occupation, date of birth), as well as
information on the notification after this data has been validated (stored in the DTO class it extends).
This acts as the link between the service layer and our domain layer which runs the internal logic.
It also holds information on the error types created.
The `RegisterWorker` class acts as our domain layer. It validates the worker's details and returns any errors through the `RegisterWorkerDto`.
DTO:
```java
/**
* Data transfer object which stores information about the worker. This is carried between
* objects and layers to reduce the number of method calls made.
*/
@Getter
@Setter
public class RegisterWorkerDto extends DataTransferObject {
private String name;
private String occupation;
private LocalDate dateOfBirth;
class RegisterWorker {
/**
* Error for when name field is blank or missing.
*/
public static final NotificationError MISSING_NAME =
new NotificationError(1, "Name is missing");
/**
* Error for when occupation field is blank or missing.
*/
public static final NotificationError MISSING_OCCUPATION =
new NotificationError(2, "Occupation is missing");
/**
* Error for when date of birth field is blank or missing.
*/
public static final NotificationError MISSING_DOB =
new NotificationError(3, "Date of birth is missing");
/**
* Error for when date of birth is less than 18 years ago.
*/
public static final NotificationError DOB_TOO_SOON =
new NotificationError(4, "Worker registered must be over 18");
protected RegisterWorkerDto() {
super();
}
...
RegisterWorker(String name, String occupation, LocalDate dateOfBirth) {
// Validate the worker's details
// If there are any errors, add them to the notification
}
}
```
These errors are stored within a simple wrapper class called NotificationError.
Finally, the `App` class is where the form is created and submitted.
Our service layer (RegisterWorkerService) represents the framework of our service layer. Currently, it will
run the commands necessary to validate our Java object without handling any of the internal logic itself,
passing on the work, along with our DTO, to the domain layer.
This validation itself is done in RegisterWorker which works within our domain layer. ServerCommand acts as
a SuperType here for the domain and holds any DTOs needed. If it passes validation, our worker is then added into
the database as submission was successful!
validation:
```java
/**
* Handles internal logic and validation for worker registration.
* Part of the domain layer which collects information and sends it back to the presentation.
*/
@Slf4j
public class RegisterWorker extends ServerCommand {
protected RegisterWorker(RegisterWorkerDto worker) {
super(worker);
}
public class App {
/**
* Validates the data provided and adds it to the database in the backend.
*/
public void run() {
//make sure the information submitted is valid
validate();
if (!super.getNotification().hasErrors()) {
//Add worker to system in backend (not implemented here)
LOGGER.info("Register worker in backend system");
}
public static void main(String[] args) {
var form = new RegisterWorkerForm("John Doe", "Engineer", LocalDate.of(1990, 1, 1));
form.submit();
}
/**
* Validates our data. Checks for any errors and if found, stores them in our notification.
*/
private void validate() {
var ourData = ((RegisterWorkerDto) this.data);
//check if any of submitted data is not given
failIfNullOrBlank(ourData.getName(), RegisterWorkerDto.MISSING_NAME);
failIfNullOrBlank(ourData.getOccupation(), RegisterWorkerDto.MISSING_OCCUPATION);
failIfNullOrBlank(ourData.getDateOfBirth().toString(), RegisterWorkerDto.MISSING_DOB);
//only if DOB is not blank, then check if worker is over 18 to register.
if (!super.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)) {
var dateOfBirth = ourData.getDateOfBirth();
var current = now().minusYears(18);
fail(dateOfBirth.compareTo(current) > 0, RegisterWorkerDto.DOB_TOO_SOON);
}
}
...
}
```
After all of this explanation, we can then simulate the following inputs into the form and submit them:
input:
```java
/**
* Variables to be submitted in the form.
*/
private static final String NAME = "";
private static final String OCCUPATION = "";
private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13);
RegisterWorkerForm form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH);
form.submit();
```
In this example, if the worker's details are invalid (e.g. the name is empty), the `RegisterWorker` class will add an error to the notification. The `RegisterWorkerForm` class can then check the notification for any errors after submission. This demonstrates the Notification pattern, where information is passed between layers and any errors are returned to the presentation layer.
The form then processes the submission and returns these error messages to the user, showing our notification worked.
output:
Example output:
```java
18:10:00.075 [main] INFO com.iluwater.RegisterWorkerForm - Error 1: Name is missing: ""
18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 2: Occupation is missing: ""
18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 4: Worker registered must be over 18: "2016-07-13"
18:10:00.080 [main] INFO com.iluwater.RegisterWorkerForm - Not registered, see errors
18:10:00.075 [main] INFO com.iluwatar.RegisterWorkerForm - Error 1: Name is missing: ""
18:10:00.079 [main] INFO com.iluwatar.RegisterWorkerForm - Error 2: Occupation is missing: ""
18:10:00.079 [main] INFO com.iluwatar.RegisterWorkerForm - Error 4: Worker registered must be over 18: "2016-07-13"
18:10:00.080 [main] INFO com.iluwatar.RegisterWorkerForm - Not registered, see errors
```
## Class diagram
![alt text](./etc/notification.urm.png "Notification")
![Notification](./etc/notification.urm.png "Notification")
## Applicability
Use the notification pattern when:
* When a change to one object requires changing others, and you dont know how many objects need to be changed.
* When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.
* When a system component must be notified of events without making assumptions about the systems other components.
* You wish to communicate information about errors between the domain layer and the presentation layer. This is most applicable when a seperated presentation pattern is being used as this does not allow for direct communication between the domain and presentation.
## Known uses
* GUI frameworks where user actions trigger responses in the application.
* Notification systems in large-scale distributed systems.
* Event management in microservices architecture.
## Consequences
Benefits:
* Reduces coupling between the producer and consumer of events.
* Enhances flexibility and reusability of components.
* Allows for dynamic subscription and unsubscription to events.
Trade-offs:
* Can lead to a complex system if not managed well, due to the dynamic nature of subscriptions.
* Debugging can be challenging due to the asynchronous and decoupled nature of events.
## Related patterns
* [Service Layer](https://java-design-patterns.com/patterns/service-layer/)
* [Data Transfer Object](https://java-design-patterns.com/patterns/data-transfer-object/)
* [Domain Model](https://java-design-patterns.com/patterns/domain-model/)
* [Remote Facade](https://martinfowler.com/eaaCatalog/remoteFacade.html)
* [Autonomous View](https://martinfowler.com/eaaDev/AutonomousView.html)
* [Layer Supertype](https://martinfowler.com/eaaCatalog/layerSupertype.html)
* [Separated Presentation](https://java-design-patterns.com/patterns/data-transfer-object/)
* [Command](https://java-design-patterns.com/patterns/command/): Can be used to encapsulate a request as an object, often used in conjunction with notifications to decouple the sender and receiver.
* [Mediator](https://java-design-patterns.com/patterns/mediator/): Facilitates centralized communication between objects, whereas the Notification pattern is more decentralized.
* [Observer](https://java-design-patterns.com/patterns/observer/): A foundational pattern for the Notification pattern, focusing on one-to-many dependency relationships.
## Credits
* [Martin Fowler - Notification Pattern](https://martinfowler.com/eaaDev/Notification.html)
* [Martin Fowler - Notification Pattern](https://martinfowler.com/eaaDev/Notification.html)
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3WcFVui)
@@ -24,15 +24,12 @@
*/
package com.iluwatar;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.time.LocalDate;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class RegisterWorkerFormTest {