mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-14 14:58:39 +00:00
feat: added notification pattern (#2629)
Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
title: Notification
|
||||
category: Behavioural
|
||||
language: en
|
||||
tags:
|
||||
- Decoupling
|
||||
- Presentation
|
||||
- Domain
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
|
||||
**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 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).
|
||||
|
||||
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.
|
||||
|
||||
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();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
These errors are stored within a simple wrapper class called NotificationError.
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
```
|
||||
|
||||
The form then processes the submission and returns these error messages to the user, showing our notification worked.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the notification pattern when:
|
||||
|
||||
* 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.
|
||||
|
||||
## 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/)
|
||||
|
||||
## Credits
|
||||
|
||||
* [Martin Fowler - Notification Pattern](https://martinfowler.com/eaaDev/Notification.html)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
@@ -0,0 +1,89 @@
|
||||
@startuml
|
||||
package com.iluwatar {
|
||||
class App {
|
||||
- DATE_OF_BIRTH : LocalDate {static}
|
||||
- NAME : String {static}
|
||||
- OCCUPATION : String {static}
|
||||
- App()
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
class DataTransferObject {
|
||||
# notification : Notification
|
||||
+ DataTransferObject()
|
||||
+ getNotification() : Notification
|
||||
}
|
||||
class Notification {
|
||||
- errors : List<NotificationError>
|
||||
+ Notification()
|
||||
+ addError(error : NotificationError)
|
||||
+ getErrors() : List<NotificationError>
|
||||
+ hasErrors() : boolean
|
||||
}
|
||||
class NotificationError {
|
||||
- errorId : int
|
||||
- errorMessage : String
|
||||
+ NotificationError(errorId : int, errorMessage : String)
|
||||
+ getErrorId() : int
|
||||
+ getErrorMessage() : String
|
||||
+ toString() : String
|
||||
}
|
||||
class RegisterWorker {
|
||||
- LOGGER : Logger {static}
|
||||
# RegisterWorker(worker : RegisterWorkerDto)
|
||||
# fail(condition : boolean, error : NotificationError)
|
||||
# failIfNullOrBlank(s : String, error : NotificationError)
|
||||
# isNullOrBlank(s : String) : boolean
|
||||
+ run()
|
||||
- validate()
|
||||
}
|
||||
class RegisterWorkerDto {
|
||||
+ DOB_TOO_SOON : NotificationError {static}
|
||||
+ MISSING_DOB : NotificationError {static}
|
||||
+ MISSING_NAME : NotificationError {static}
|
||||
+ MISSING_OCCUPATION : NotificationError {static}
|
||||
- dateOfBirth : LocalDate
|
||||
- name : String
|
||||
- occupation : String
|
||||
# RegisterWorkerDto()
|
||||
+ getDateOfBirth() : LocalDate
|
||||
+ getName() : String
|
||||
+ getOccupation() : String
|
||||
+ setDateOfBirth(dateOfBirth : LocalDate)
|
||||
+ setName(name : String)
|
||||
+ setOccupation(occupation : String)
|
||||
+ setupWorkerDto(name : String, occupation : String, dateOfBirth : LocalDate)
|
||||
}
|
||||
class RegisterWorkerForm {
|
||||
- LOGGER : Logger {static}
|
||||
~ dateOfBirth : LocalDate
|
||||
~ name : String
|
||||
~ occupation : String
|
||||
~ service : RegisterWorkerService
|
||||
~ worker : RegisterWorkerDto
|
||||
+ RegisterWorkerForm(name : String, occupation : String, dateOfBirth : LocalDate)
|
||||
- checkError(error : NotificationError, info : String)
|
||||
- indicateErrors()
|
||||
- saveToWorker()
|
||||
~ showError(info : String, message : String)
|
||||
+ submit()
|
||||
}
|
||||
class RegisterWorkerService {
|
||||
+ RegisterWorkerService()
|
||||
+ registerWorker(registration : RegisterWorkerDto)
|
||||
}
|
||||
class ServerCommand {
|
||||
# data : DataTransferObject
|
||||
+ ServerCommand(data : DataTransferObject)
|
||||
+ getNotification() : Notification
|
||||
}
|
||||
}
|
||||
Notification --> "-errors" NotificationError
|
||||
DataTransferObject --> "-notification" Notification
|
||||
RegisterWorkerForm --> "-service" RegisterWorkerService
|
||||
RegisterWorkerForm --> "-worker" RegisterWorkerDto
|
||||
ServerCommand --> "-data" DataTransferObject
|
||||
RegisterWorkerDto --> "-MISSING_DOB" NotificationError
|
||||
RegisterWorkerDto --> "-MISSING_NAME" NotificationError
|
||||
RegisterWorker --|> ServerCommand
|
||||
RegisterWorkerDto --|> DataTransferObject
|
||||
@enduml
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.26.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<artifactId>notification</artifactId>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* The notification pattern captures information passed between layers, validates the information, and returns
|
||||
* any errors to the presentation layer if needed.
|
||||
*
|
||||
* <p>In this code, this pattern is implemented through the example of a form being submitted to register
|
||||
* a worker. The worker inputs their name, occupation, and date of birth to the RegisterWorkerForm (which acts
|
||||
* as our presentation layer), and passes it to the RegisterWorker class (our domain layer) which validates it.
|
||||
* Any errors caught by the domain layer are then passed back to the presentation layer through the
|
||||
* RegisterWorkerDto.</p>
|
||||
*/
|
||||
public class App {
|
||||
|
||||
private static final String NAME = "";
|
||||
private static final String OCCUPATION = "";
|
||||
private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13);
|
||||
|
||||
public static void main(String[] args) {
|
||||
var form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Layer super type for all Data Transfer Objects.
|
||||
* Also contains code for accessing our notification.
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class DataTransferObject {
|
||||
|
||||
private final Notification notification = new Notification();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* The notification. Used for storing errors and any other methods
|
||||
* that may be necessary for when we send information back to the
|
||||
* presentation layer.
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class Notification {
|
||||
|
||||
private final List<NotificationError> errors = new ArrayList<>();
|
||||
|
||||
public boolean hasErrors() {
|
||||
return !this.errors.isEmpty();
|
||||
}
|
||||
|
||||
public void addError(NotificationError error) {
|
||||
this.errors.add(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Error class for storing information on the error.
|
||||
* Error ID is not necessary, but may be useful for serialisation.
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class NotificationError {
|
||||
private int errorId;
|
||||
private String errorMessage;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Error " + errorId + ": " + errorMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Class which handles actual 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 {
|
||||
static final int LEGAL_AGE = 18;
|
||||
protected RegisterWorker(RegisterWorkerDto worker) {
|
||||
super(worker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data provided and adds it to the database in the backend.
|
||||
*/
|
||||
public void run() {
|
||||
|
||||
validate();
|
||||
if (!super.getNotification().hasErrors()) {
|
||||
LOGGER.info("Register worker in backend system");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates our data. Checks for any errors and if found, adds to notification.
|
||||
*/
|
||||
private void validate() {
|
||||
var ourData = ((RegisterWorkerDto) this.data);
|
||||
//check if any of submitted data is not given
|
||||
// passing for empty value validation
|
||||
fail(isNullOrBlank(ourData.getName()), RegisterWorkerDto.MISSING_NAME);
|
||||
fail(isNullOrBlank(ourData.getOccupation()), RegisterWorkerDto.MISSING_OCCUPATION);
|
||||
fail(isNullOrBlank(ourData.getDateOfBirth()), RegisterWorkerDto.MISSING_DOB);
|
||||
|
||||
if (isNullOrBlank(ourData.getDateOfBirth())) {
|
||||
// If DOB is null or empty
|
||||
fail(true, RegisterWorkerDto.MISSING_DOB);
|
||||
} else {
|
||||
// Validating age ( should be greater than or equal to 18 )
|
||||
Period age = Period.between(ourData.getDateOfBirth(), LocalDate.now());
|
||||
fail(age.getYears() < LEGAL_AGE, RegisterWorkerDto.DOB_TOO_SOON);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates for null/empty value.
|
||||
*
|
||||
* @param obj any object
|
||||
* @return boolean
|
||||
*/
|
||||
protected boolean isNullOrBlank(Object obj) {
|
||||
if (obj == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof String) {
|
||||
return ((String) obj).trim().isEmpty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a condition is met, adds the error to our notification.
|
||||
*
|
||||
* @param condition condition to check for.
|
||||
* @param error error to add if condition met.
|
||||
*/
|
||||
protected void fail(boolean condition, NotificationError error) {
|
||||
if (condition) {
|
||||
super.getNotification().addError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple set up function for capturing our worker information.
|
||||
*
|
||||
* @param name Name of the worker
|
||||
* @param occupation occupation of the worker
|
||||
* @param dateOfBirth Date of Birth of the worker
|
||||
*/
|
||||
public void setupWorkerDto(String name, String occupation, LocalDate dateOfBirth) {
|
||||
this.name = name;
|
||||
this.occupation = occupation;
|
||||
this.dateOfBirth = dateOfBirth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
RegisterWorkerService service = new RegisterWorkerService();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name Name of the 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to submit the form for registering a worker.
|
||||
*/
|
||||
public void submit() {
|
||||
//Transmit information to our transfer object to communicate between layers
|
||||
saveToWorker();
|
||||
//call the service layer to register our worker
|
||||
service.registerWorker(worker);
|
||||
|
||||
//check for any errors
|
||||
if (worker.getNotification().hasErrors()) {
|
||||
indicateErrors();
|
||||
LOGGER.info("Not registered, see errors");
|
||||
} else {
|
||||
LOGGER.info("Registration Succeeded");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves worker information to the data transfer object.
|
||||
*/
|
||||
private void saveToWorker() {
|
||||
worker = new RegisterWorkerDto();
|
||||
worker.setName(name);
|
||||
worker.setOccupation(occupation);
|
||||
worker.setDateOfBirth(dateOfBirth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for any errors with form submission and show them to the user.
|
||||
*/
|
||||
public void indicateErrors() {
|
||||
worker.getNotification().getErrors().forEach(error -> LOGGER.error(error.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.iluwatar;
|
||||
|
||||
/**
|
||||
* Service used to register a worker.
|
||||
* This represents the basic framework of a service layer which can be built upon.
|
||||
*/
|
||||
public class RegisterWorkerService {
|
||||
/**
|
||||
* Creates and runs a command object to do the work needed,
|
||||
* in this case, register a worker in the system.
|
||||
*
|
||||
* @param registration worker to be registered if possible
|
||||
*/
|
||||
public void registerWorker(RegisterWorkerDto registration) {
|
||||
var cmd = new RegisterWorker(registration);
|
||||
cmd.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
/**
|
||||
* Stores the dto and access the notification within it.
|
||||
* Acting as a layer supertype in this instance for the domain layer.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class ServerCommand {
|
||||
protected DataTransferObject data;
|
||||
|
||||
/**
|
||||
* Basic getter to extract information from our data.
|
||||
*
|
||||
* @return the notification stored within the data
|
||||
*/
|
||||
public Notification getNotification() {
|
||||
return data.getNotification();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
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 java.time.LocalDate;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class RegisterWorkerFormTest {
|
||||
|
||||
private RegisterWorkerForm registerWorkerForm;
|
||||
|
||||
@Test
|
||||
void submitSuccessfully() {
|
||||
// Ensure the worker is null initially
|
||||
registerWorkerForm = new RegisterWorkerForm("John Doe", "Engineer", LocalDate.of(1990, 1, 1));
|
||||
|
||||
assertNull(registerWorkerForm.worker);
|
||||
|
||||
// Submit the form
|
||||
registerWorkerForm.submit();
|
||||
|
||||
// Verify that the worker is not null after submission
|
||||
assertNotNull(registerWorkerForm.worker);
|
||||
|
||||
// Verify that the worker's properties are set correctly
|
||||
assertEquals("John Doe", registerWorkerForm.worker.getName());
|
||||
assertEquals("Engineer", registerWorkerForm.worker.getOccupation());
|
||||
assertEquals(LocalDate.of(1990, 1, 1), registerWorkerForm.worker.getDateOfBirth());
|
||||
}
|
||||
|
||||
@Test
|
||||
void submitWithErrors() {
|
||||
// Set up the worker with a notification containing errors
|
||||
registerWorkerForm = new RegisterWorkerForm(null, null, null);
|
||||
|
||||
// Submit the form
|
||||
registerWorkerForm.submit();
|
||||
|
||||
// Verify that the worker's properties remain unchanged
|
||||
assertNull(registerWorkerForm.worker.getName());
|
||||
assertNull(registerWorkerForm.worker.getOccupation());
|
||||
assertNull(registerWorkerForm.worker.getDateOfBirth());
|
||||
|
||||
// Verify the presence of errors
|
||||
assertEquals(registerWorkerForm.worker.getNotification().getErrors().size(), 4);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Slf4j
|
||||
class RegisterWorkerTest {
|
||||
|
||||
@Test
|
||||
void runSuccessfully() {
|
||||
RegisterWorkerDto validWorkerDto = createValidWorkerDto();
|
||||
validWorkerDto.setupWorkerDto("name", "occupation", LocalDate.of(2000, 12, 1));
|
||||
RegisterWorker registerWorker = new RegisterWorker(validWorkerDto);
|
||||
|
||||
// Run the registration process
|
||||
registerWorker.run();
|
||||
|
||||
// Verify that there are no errors in the notification
|
||||
assertFalse(registerWorker.getNotification().hasErrors());
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWithMissingName() {
|
||||
RegisterWorkerDto workerDto = createValidWorkerDto();
|
||||
workerDto.setupWorkerDto(null, "occupation", LocalDate.of(2000, 12, 1));
|
||||
RegisterWorker registerWorker = new RegisterWorker(workerDto);
|
||||
|
||||
// Run the registration process
|
||||
registerWorker.run();
|
||||
|
||||
// Verify that the notification contains the missing name error
|
||||
assertTrue(registerWorker.getNotification().hasErrors());
|
||||
assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_NAME));
|
||||
assertEquals(registerWorker.getNotification().getErrors().size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWithMissingOccupation() {
|
||||
RegisterWorkerDto workerDto = createValidWorkerDto();
|
||||
workerDto.setupWorkerDto("name", null, LocalDate.of(2000, 12, 1));
|
||||
RegisterWorker registerWorker = new RegisterWorker(workerDto);
|
||||
|
||||
// Run the registration process
|
||||
registerWorker.run();
|
||||
|
||||
// Verify that the notification contains the missing occupation error
|
||||
assertTrue(registerWorker.getNotification().hasErrors());
|
||||
assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_OCCUPATION));
|
||||
assertEquals(registerWorker.getNotification().getErrors().size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWithMissingDOB() {
|
||||
RegisterWorkerDto workerDto = createValidWorkerDto();
|
||||
workerDto.setupWorkerDto("name", "occupation", null);
|
||||
RegisterWorker registerWorker = new RegisterWorker(workerDto);
|
||||
|
||||
// Run the registration process
|
||||
registerWorker.run();
|
||||
|
||||
// Verify that the notification contains the missing DOB error
|
||||
assertTrue(registerWorker.getNotification().hasErrors());
|
||||
assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB));
|
||||
assertEquals(registerWorker.getNotification().getErrors().size(), 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWithUnderageDOB() {
|
||||
RegisterWorkerDto workerDto = createValidWorkerDto();
|
||||
workerDto.setDateOfBirth(LocalDate.now().minusYears(17)); // Under 18
|
||||
workerDto.setupWorkerDto("name", "occupation", LocalDate.now().minusYears(17));
|
||||
RegisterWorker registerWorker = new RegisterWorker(workerDto);
|
||||
|
||||
// Run the registration process
|
||||
registerWorker.run();
|
||||
|
||||
// Verify that the notification contains the underage DOB error
|
||||
assertTrue(registerWorker.getNotification().hasErrors());
|
||||
assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.DOB_TOO_SOON));
|
||||
assertEquals(registerWorker.getNotification().getErrors().size(), 1);
|
||||
}
|
||||
|
||||
private RegisterWorkerDto createValidWorkerDto() {
|
||||
return new RegisterWorkerDto();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user