feat: added notification pattern (#2629)

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
This commit is contained in:
sugavanesh
2024-03-09 18:16:46 +05:30
committed by GitHub
parent b2ca49a4e5
commit 249efd1e71
17 changed files with 849 additions and 0 deletions
@@ -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();
}
}