Skip to content

Commit 249efd1

Browse files
sugan0techiluwatar
andauthored
feat: added notification pattern (iluwatar#2629)
Co-authored-by: Ilkka Seppälä <[email protected]>
1 parent b2ca49a commit 249efd1

17 files changed

+849
-0
lines changed

notification/README.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
title: Notification
2+
category: Behavioural
3+
language: en
4+
tags:
5+
- Decoupling
6+
- Presentation
7+
- Domain
8+
---
9+
10+
## Intent
11+
12+
To capture information about errors and other information that occurs in the domain layer
13+
which acts as the internal logic and validation tool. The notification then communicates this information
14+
back to the presentation for handling and displaying to the user what errors have occurred and why.
15+
16+
## Explanation
17+
18+
Real world example
19+
20+
> You need to register a worker for your company. The information submitted needs to be valid
21+
> before the worker can be added to the database, and if there are any errors you need to know about them.
22+
23+
In plain words
24+
25+
> A notification is simply a way of collecting information about errors and communicating it to the user
26+
> so that they are aware of them
27+
28+
**Programatic example**
29+
Building off the example of registering a worker for a company, we can now explore a coded example for a full picture
30+
of how this pattern looks when coded up. For full code please visit the github repository.
31+
32+
To begin with, the user submits information to a form through the presentation layer of our software. The information
33+
given to register a worker is the worker's name, occupation, and date of birth. The program will then make sure none of
34+
these fields are blank (validation) and that the worker is over 18 years old. If there are any errors,
35+
the program will inform the user.
36+
37+
The code for the form is given below. This form acts as our presentation layer, taking input from the user and printing
38+
output using a LOGGER (in this case). The form then gives this information to the service layer RegisterWorkerService
39+
through a data transfer object (DTO).
40+
41+
The service handles information validation and the presentation can then check the notification stored within the DTO for
42+
any errors and display them to the user if necessary. Otherwise, it will inform the user the submission was processed
43+
successfully.
44+
45+
form:
46+
```java
47+
/**
48+
* The form submitted by the user, part of the presentation layer,
49+
* linked to the domain layer through a data transfer object and
50+
* linked to the service layer directly.
51+
*/
52+
@Slf4j
53+
public class RegisterWorkerForm {
54+
String name;
55+
String occupation;
56+
LocalDate dateOfBirth;
57+
RegisterWorkerDto worker;
58+
/**
59+
* Service super type which the form uses as part of its service layer.
60+
*/
61+
RegisterWorkerService service = new RegisterWorkerService();
62+
63+
/**
64+
* Creates the form.
65+
*
66+
* @param name name of worker
67+
* @param occupation occupation of the worker
68+
* @param dateOfBirth date of birth of the worker
69+
*/
70+
public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) {
71+
this.name = name;
72+
this.occupation = occupation;
73+
this.dateOfBirth = dateOfBirth;
74+
}
75+
76+
/**
77+
* Attempts to submit the form for registering a worker.
78+
*/
79+
public void submit() {
80+
//Save worker information (like name, occupation, dob) to our transfer object to be communicated between layers
81+
saveToWorker();
82+
//call the service layer to register our worker
83+
service.registerWorker(worker);
84+
85+
//check for any errors
86+
if (worker.getNotification().hasErrors()) {
87+
indicateErrors(); //displays errors to users
88+
LOGGER.info("Not registered, see errors");
89+
} else {
90+
LOGGER.info("Registration Succeeded");
91+
}
92+
}
93+
94+
...
95+
}
96+
```
97+
98+
The data transfer object (DTO) created stores the information submitted (name, occupation, date of birth), as well as
99+
information on the notification after this data has been validated (stored in the DTO class it extends).
100+
This acts as the link between the service layer and our domain layer which runs the internal logic.
101+
It also holds information on the error types created.
102+
103+
DTO:
104+
```java
105+
/**
106+
* Data transfer object which stores information about the worker. This is carried between
107+
* objects and layers to reduce the number of method calls made.
108+
*/
109+
@Getter
110+
@Setter
111+
public class RegisterWorkerDto extends DataTransferObject {
112+
private String name;
113+
private String occupation;
114+
private LocalDate dateOfBirth;
115+
116+
/**
117+
* Error for when name field is blank or missing.
118+
*/
119+
public static final NotificationError MISSING_NAME =
120+
new NotificationError(1, "Name is missing");
121+
122+
/**
123+
* Error for when occupation field is blank or missing.
124+
*/
125+
public static final NotificationError MISSING_OCCUPATION =
126+
new NotificationError(2, "Occupation is missing");
127+
128+
/**
129+
* Error for when date of birth field is blank or missing.
130+
*/
131+
public static final NotificationError MISSING_DOB =
132+
new NotificationError(3, "Date of birth is missing");
133+
134+
/**
135+
* Error for when date of birth is less than 18 years ago.
136+
*/
137+
public static final NotificationError DOB_TOO_SOON =
138+
new NotificationError(4, "Worker registered must be over 18");
139+
140+
141+
protected RegisterWorkerDto() {
142+
super();
143+
}
144+
145+
...
146+
}
147+
```
148+
149+
These errors are stored within a simple wrapper class called NotificationError.
150+
151+
Our service layer (RegisterWorkerService) represents the framework of our service layer. Currently, it will
152+
run the commands necessary to validate our Java object without handling any of the internal logic itself,
153+
passing on the work, along with our DTO, to the domain layer.
154+
155+
This validation itself is done in RegisterWorker which works within our domain layer. ServerCommand acts as
156+
a SuperType here for the domain and holds any DTOs needed. If it passes validation, our worker is then added into
157+
the database as submission was successful!
158+
159+
validation:
160+
```java
161+
/**
162+
* Handles internal logic and validation for worker registration.
163+
* Part of the domain layer which collects information and sends it back to the presentation.
164+
*/
165+
@Slf4j
166+
public class RegisterWorker extends ServerCommand {
167+
protected RegisterWorker(RegisterWorkerDto worker) {
168+
super(worker);
169+
}
170+
171+
/**
172+
* Validates the data provided and adds it to the database in the backend.
173+
*/
174+
public void run() {
175+
//make sure the information submitted is valid
176+
validate();
177+
if (!super.getNotification().hasErrors()) {
178+
//Add worker to system in backend (not implemented here)
179+
LOGGER.info("Register worker in backend system");
180+
}
181+
}
182+
183+
/**
184+
* Validates our data. Checks for any errors and if found, stores them in our notification.
185+
*/
186+
private void validate() {
187+
var ourData = ((RegisterWorkerDto) this.data);
188+
//check if any of submitted data is not given
189+
failIfNullOrBlank(ourData.getName(), RegisterWorkerDto.MISSING_NAME);
190+
failIfNullOrBlank(ourData.getOccupation(), RegisterWorkerDto.MISSING_OCCUPATION);
191+
failIfNullOrBlank(ourData.getDateOfBirth().toString(), RegisterWorkerDto.MISSING_DOB);
192+
//only if DOB is not blank, then check if worker is over 18 to register.
193+
if (!super.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)) {
194+
var dateOfBirth = ourData.getDateOfBirth();
195+
var current = now().minusYears(18);
196+
fail(dateOfBirth.compareTo(current) > 0, RegisterWorkerDto.DOB_TOO_SOON);
197+
}
198+
}
199+
200+
...
201+
}
202+
```
203+
204+
After all of this explanation, we can then simulate the following inputs into the form and submit them:
205+
206+
input:
207+
```java
208+
/**
209+
* Variables to be submitted in the form.
210+
*/
211+
private static final String NAME = "";
212+
private static final String OCCUPATION = "";
213+
private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13);
214+
215+
RegisterWorkerForm form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH);
216+
form.submit();
217+
```
218+
219+
The form then processes the submission and returns these error messages to the user, showing our notification worked.
220+
221+
output:
222+
```java
223+
18:10:00.075 [main] INFO com.iluwater.RegisterWorkerForm - Error 1: Name is missing: ""
224+
18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 2: Occupation is missing: ""
225+
18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 4: Worker registered must be over 18: "2016-07-13"
226+
18:10:00.080 [main] INFO com.iluwater.RegisterWorkerForm - Not registered, see errors
227+
```
228+
229+
## Class diagram
230+
231+
![alt text](./etc/notification.urm.png "Notification")
232+
233+
## Applicability
234+
235+
Use the notification pattern when:
236+
237+
* 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.
238+
239+
## Related patterns
240+
241+
* [Service Layer](https://java-design-patterns.com/patterns/service-layer/)
242+
* [Data Transfer Object](https://java-design-patterns.com/patterns/data-transfer-object/)
243+
* [Domain Model](https://java-design-patterns.com/patterns/domain-model/)
244+
* [Remote Facade](https://martinfowler.com/eaaCatalog/remoteFacade.html)
245+
* [Autonomous View](https://martinfowler.com/eaaDev/AutonomousView.html)
246+
* [Layer Supertype](https://martinfowler.com/eaaCatalog/layerSupertype.html)
247+
* [Separated Presentation](https://java-design-patterns.com/patterns/data-transfer-object/)
248+
249+
## Credits
250+
251+
* [Martin Fowler - Notification Pattern](https://martinfowler.com/eaaDev/Notification.html)
143 KB
Loading
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
@startuml
2+
package com.iluwatar {
3+
class App {
4+
- DATE_OF_BIRTH : LocalDate {static}
5+
- NAME : String {static}
6+
- OCCUPATION : String {static}
7+
- App()
8+
+ main(args : String[]) {static}
9+
}
10+
class DataTransferObject {
11+
# notification : Notification
12+
+ DataTransferObject()
13+
+ getNotification() : Notification
14+
}
15+
class Notification {
16+
- errors : List<NotificationError>
17+
+ Notification()
18+
+ addError(error : NotificationError)
19+
+ getErrors() : List<NotificationError>
20+
+ hasErrors() : boolean
21+
}
22+
class NotificationError {
23+
- errorId : int
24+
- errorMessage : String
25+
+ NotificationError(errorId : int, errorMessage : String)
26+
+ getErrorId() : int
27+
+ getErrorMessage() : String
28+
+ toString() : String
29+
}
30+
class RegisterWorker {
31+
- LOGGER : Logger {static}
32+
# RegisterWorker(worker : RegisterWorkerDto)
33+
# fail(condition : boolean, error : NotificationError)
34+
# failIfNullOrBlank(s : String, error : NotificationError)
35+
# isNullOrBlank(s : String) : boolean
36+
+ run()
37+
- validate()
38+
}
39+
class RegisterWorkerDto {
40+
+ DOB_TOO_SOON : NotificationError {static}
41+
+ MISSING_DOB : NotificationError {static}
42+
+ MISSING_NAME : NotificationError {static}
43+
+ MISSING_OCCUPATION : NotificationError {static}
44+
- dateOfBirth : LocalDate
45+
- name : String
46+
- occupation : String
47+
# RegisterWorkerDto()
48+
+ getDateOfBirth() : LocalDate
49+
+ getName() : String
50+
+ getOccupation() : String
51+
+ setDateOfBirth(dateOfBirth : LocalDate)
52+
+ setName(name : String)
53+
+ setOccupation(occupation : String)
54+
+ setupWorkerDto(name : String, occupation : String, dateOfBirth : LocalDate)
55+
}
56+
class RegisterWorkerForm {
57+
- LOGGER : Logger {static}
58+
~ dateOfBirth : LocalDate
59+
~ name : String
60+
~ occupation : String
61+
~ service : RegisterWorkerService
62+
~ worker : RegisterWorkerDto
63+
+ RegisterWorkerForm(name : String, occupation : String, dateOfBirth : LocalDate)
64+
- checkError(error : NotificationError, info : String)
65+
- indicateErrors()
66+
- saveToWorker()
67+
~ showError(info : String, message : String)
68+
+ submit()
69+
}
70+
class RegisterWorkerService {
71+
+ RegisterWorkerService()
72+
+ registerWorker(registration : RegisterWorkerDto)
73+
}
74+
class ServerCommand {
75+
# data : DataTransferObject
76+
+ ServerCommand(data : DataTransferObject)
77+
+ getNotification() : Notification
78+
}
79+
}
80+
Notification --> "-errors" NotificationError
81+
DataTransferObject --> "-notification" Notification
82+
RegisterWorkerForm --> "-service" RegisterWorkerService
83+
RegisterWorkerForm --> "-worker" RegisterWorkerDto
84+
ServerCommand --> "-data" DataTransferObject
85+
RegisterWorkerDto --> "-MISSING_DOB" NotificationError
86+
RegisterWorkerDto --> "-MISSING_NAME" NotificationError
87+
RegisterWorker --|> ServerCommand
88+
RegisterWorkerDto --|> DataTransferObject
89+
@enduml

notification/pom.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>com.iluwatar</groupId>
8+
<artifactId>java-design-patterns</artifactId>
9+
<version>1.26.0-SNAPSHOT</version>
10+
</parent>
11+
<dependencies>
12+
<dependency>
13+
<groupId>org.junit.jupiter</groupId>
14+
<artifactId>junit-jupiter-params</artifactId>
15+
<scope>test</scope>
16+
</dependency>
17+
</dependencies>
18+
19+
<artifactId>notification</artifactId>
20+
</project>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.iluwatar;
2+
3+
import java.time.LocalDate;
4+
5+
/**
6+
* The notification pattern captures information passed between layers, validates the information, and returns
7+
* any errors to the presentation layer if needed.
8+
*
9+
* <p>In this code, this pattern is implemented through the example of a form being submitted to register
10+
* a worker. The worker inputs their name, occupation, and date of birth to the RegisterWorkerForm (which acts
11+
* as our presentation layer), and passes it to the RegisterWorker class (our domain layer) which validates it.
12+
* Any errors caught by the domain layer are then passed back to the presentation layer through the
13+
* RegisterWorkerDto.</p>
14+
*/
15+
public class App {
16+
17+
private static final String NAME = "";
18+
private static final String OCCUPATION = "";
19+
private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13);
20+
21+
public static void main(String[] args) {
22+
var form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH);
23+
form.submit();
24+
}
25+
26+
}

0 commit comments

Comments
 (0)